=== modified file 'lava_scheduler_app/models.py'
--- lava_scheduler_app/models.py	2011-08-19 03:24:11 +0000
+++ lava_scheduler_app/models.py	2011-08-22 03:08:56 +0000
@@ -26,11 +26,13 @@
     OFFLINE = 0
     IDLE = 1
     RUNNING = 2
+    OFFLINING = 3
 
     STATUS_CHOICES = (
         (OFFLINE, 'Offline'),
         (IDLE, 'Idle'),
         (RUNNING, 'Running'),
+        (OFFLINING, 'Going offline'),
     )
 
     hostname = models.CharField(
@@ -51,6 +53,20 @@
         verbose_name = _(u"Device status"),
     )
 
+    def can_admin(self, user):
+        return user.has_perm('lava_scheduler_app.change_device')
+
+    def put_into_maintenance_mode(self):
+        if self.status == self.RUNNING:
+            self.status = self.OFFLINING
+        else:
+            self.status = self.OFFLINE
+        self.save()
+
+    def put_into_online_mode(self):
+        self.status = self.IDLE
+        self.save()
+
     def __unicode__(self):
         return self.hostname
 

=== added file 'lava_scheduler_app/templates/lava_scheduler_app/device.html'
--- lava_scheduler_app/templates/lava_scheduler_app/device.html	1970-01-01 00:00:00 +0000
+++ lava_scheduler_app/templates/lava_scheduler_app/device.html	2011-08-22 03:22:22 +0000
@@ -0,0 +1,98 @@
+{% extends "lava_scheduler_app/_content.html" %}
+
+{% block extrahead %}
+{{ block.super }}
+<style type="text/css">
+.column {
+    position: relative;
+    float: left;
+    padding-right: 2em;
+    padding-bottom: 1em;
+}
+</style>
+{% endblock %}
+
+{% block content %}
+<h2>Device {{ device.hostname }}</h2>
+
+{% if show_maintenance %}
+<form style="display:inline; float:right" method="POST"
+      action="{% url lava_scheduler_app.views.device_maintenance_mode device.pk %}">
+  {% csrf_token %}
+  <button id="maintenance-button">Put into maintenance mode</button>
+</form>
+{% endif %}
+{% if show_online %}
+<form style="display:inline; float:right" method="POST"
+      action="{% url lava_scheduler_app.views.device_online device.pk %}">
+  {% csrf_token %}
+  <button id="online-button">Put online</button>
+</form>
+{% endif %}
+
+<div id="columns">
+  <div class="column">
+    <dt>Hostname:</dt>
+    <dd>{{ device.hostname }}</dd>
+
+    <dt>Device type:</dt>
+    <dd>{{ device.device_type }}</dd>
+  </div>
+  <div class="column">
+    <dt>Status:</dt>
+    <dd>{{ device.get_status_display }}</dd>
+
+{% if device.current_job %}
+    <dt>Currently running:</dt>
+    <dd>
+      <a href="{% url lava_scheduler_app.views.job pk=device.current_job.pk %}">
+        Job {{ device.current_job.id }}
+      </a>
+    </dd>
+{% endif %}
+  </div>
+  <div style="clear: both"></div>
+</div>
+
+<table class="data display">
+  <thead>
+    <tr>
+      <th class="id">ID</th>
+      <th>Status</th>
+      <th>Device</th>
+      <th>Submitter</th>
+      <th>Start Time</th>
+      <th>Finish Time</th>
+    </tr>
+  </thead>
+  <tbody>
+    {% for job in recent_jobs %}
+    <tr>
+      <td><a href="{% url lava_scheduler_app.views.job pk=job.pk %}">{{ job.id }}</a></td>
+      <td>{{ job.get_status_display }}</td>
+      <td>{{ job.actual_device }}</td>
+      <td>{{ job.submitter }}</td>
+      <td>{{ job.start_time }}</td>
+      <td>{{ job.end_time|default:"not finished" }}</td>
+    </tr>
+    {% endfor %}
+  </tbody>
+</table>
+
+<script>
+$(document).ready(
+  function() {
+    $("table.data").dataTable({
+      "bJQueryUI": true,
+      "aoColumnDefs": [
+        { "sType": "num-html", "aTargets": [ "id" ] }
+      ],
+      "aaSorting": [[4, 'desc', 4]]
+    });
+    $("#maintenance-button").button();
+    $("#online-button").button();
+  }
+);
+</script>
+
+{% endblock %}

=== modified file 'lava_scheduler_app/templates/lava_scheduler_app/index.html'
--- lava_scheduler_app/templates/lava_scheduler_app/index.html	2011-08-04 09:28:39 +0000
+++ lava_scheduler_app/templates/lava_scheduler_app/index.html	2011-08-22 03:24:30 +0000
@@ -15,7 +15,11 @@
     {% for device in devices %}
     <tr>
       <td>{{ device.device_type }}</td>
-      <td>{{ device.hostname }}</td>
+      <td>
+        <a href="{% url lava_scheduler_app.views.device pk=device.pk %}">
+          {{ device.hostname }}
+        </a>
+      </td>
       <td>{{ device.get_status_display }}</td>
     </tr>
     {% endfor %}

=== modified file 'lava_scheduler_app/urls.py'
--- lava_scheduler_app/urls.py	2011-08-19 03:24:11 +0000
+++ lava_scheduler_app/urls.py	2011-08-22 03:08:56 +0000
@@ -4,6 +4,9 @@
     'lava_scheduler_app.views',
     url(r'^$', 'index'),
     url(r'^alljobs$', 'alljobs'),
+    url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)$', 'device'),
+    url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/maintenance$', 'device_maintenance_mode'),
+    url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/online$', 'device_online'),
     url(r'^job/(?P<pk>[0-9]+)$', 'job'),
     url(r'^job/(?P<pk>[0-9]+)/output$', 'job_output'),
     url(r'^job/(?P<pk>[0-9]+)/cancel$', 'job_cancel'),

=== modified file 'lava_scheduler_app/views.py'
--- lava_scheduler_app/views.py	2011-08-22 03:01:17 +0000
+++ lava_scheduler_app/views.py	2011-08-22 03:26:03 +0000
@@ -94,3 +94,42 @@
     else:
         return HttpResponseForbidden(
             "you cannot cancel this job", content_type="text/plain")
+
+
+def device(request, pk):
+    device = Device.objects.get(pk=pk)
+    recent_jobs = TestJob.objects.all().filter(
+        actual_device=device).order_by('-start_time')
+    return render_to_response(
+        "lava_scheduler_app/device.html",
+        {
+            'device': device,
+            'recent_jobs': recent_jobs,
+            'show_maintenance': device.can_admin(request.user) and \
+                device.status in [Device.IDLE, Device.RUNNING],
+            'show_online': device.can_admin(request.user) and \
+                device.status in [Device.OFFLINE, Device.OFFLINING],
+        },
+        RequestContext(request))
+
+
+@post_only
+def device_maintenance_mode(request, pk):
+    device = Device.objects.get(pk=pk)
+    if device.can_admin(request.user):
+        device.put_into_maintenance_mode()
+        return redirect('lava_scheduler_app.views.device', pk=device.pk)
+    else:
+        return HttpResponseForbidden(
+            "you cannot administer this device", content_type="text/plain")
+
+
+@post_only
+def device_online(request, pk):
+    device = Device.objects.get(pk=pk)
+    if device.can_admin(request.user):
+        device.put_into_online_mode()
+        return redirect('lava_scheduler_app.views.device', pk=device.pk)
+    else:
+        return HttpResponseForbidden(
+            "you cannot administer this device", content_type="text/plain")

=== modified file 'lava_scheduler_daemon/dbjobsource.py'
--- lava_scheduler_daemon/dbjobsource.py	2011-08-19 02:38:22 +0000
+++ lava_scheduler_daemon/dbjobsource.py	2011-08-22 03:20:17 +0000
@@ -124,7 +124,14 @@
     def jobCompleted_impl(self, board_name):
         self.logger.debug('marking job as complete on %s', board_name)
         device = Device.objects.get(hostname=board_name)
-        device.status = Device.IDLE
+        if device.status == Device.RUNNING:
+            device.status = Device.IDLE
+        elif device.status == Device.OFFLINING:
+            device.status = Device.OFFLINE
+        else:
+            self.logger.error(
+                "Unexpected device state in jobCompleted: %s" % device.status)
+            device.status = Device.IDLE
         job = device.current_job
         device.current_job = None
         if job.status == TestJob.RUNNING:

