regen JS
diff --git a/ui/js/warble.v1.js b/ui/js/warble.v1.js
index a382b23..4058c03 100644
--- a/ui/js/warble.v1.js
+++ b/ui/js/warble.v1.js
@@ -1,5 +1,5 @@
 // Generated by CoffeeScript 1.9.3
-var API, APIVERSION, Chart, HTML, Row, WarbleLogin, WarbleLoginCallback, Widget, aSourceTypes, addSourceType, addSources, addorguser, addsources, affiliate, affiliation, affiliationWizard, altemail, app, badModal, bio, chartOnclick, chartToSvg, chartWrapperButtons, charts_donutchart, charts_gaugechart, charts_linechart, charts_linechart_stacked, charts_linked, charts_radarchart, ciexplorer, clientTypes, clientlist, cog, comShow, comstat, copyCSS, currentSources, dataTable, datepicker, datepickers, defaultOrgChanged, deleteNode, deletesource, doResetPass, donut, downloadBlob, explorer, fScreen, factors, fetch, fetchPhonebook, filterPerson, filterView, findWidget, forumexplorer, gauge, genColors, get, getResetToken, getSourceType, globArgs, hsl2rgb, imexplorer, inviteMember, isArray, isHash, issueexplorer, jsondump, keyValueForm, linechart, loadPageWidgets, logexplorer, login, mailexplorer, make5, makeClientType, makeOrg, manageviews, memberInvited, membershipList, messages, mk, modifyNode, multiviewexplorer, mvp, newview, nodeLocation, orgCreated, orgadmin, orglist, pageID, paragraph, patch, phonebook_cached, post, postPublishLink, preferences, pubWidget, publishWidget, publisher, publisherPublic, publisherWidget, put, pwReset, quickColors, radar, radarIndicators, rcollate, redirs, relationship, remail, remorguser, removeMember, renderAccountInfo, renderPhonebook, report, resetpw, rmview, rotateTable, rowZ, saveNodeLocation, savedNodeLocation, saveprefs, saveview, sendEmail, set, setDefaultOrg, setupPage, setupPhonebook, showClientType, showMore, showType, signout, signup, snap, sourceAdded, sourceTypes, sourceadd, sourceexplorer, sourcelist, sourceret, st, stackChart, subFilter, subFilterGlob, swi, switchChartType, tagList, theme, toFullscreen, toNormal, top5, treemap, trend, trendBox, txt, updateTimeseriesWidgets, updateWidgets, userAccount, validateLogin, validateSignup, viewJS, viewexplorer, widgetCache, widgetexplorer, worldmap, xdelete, xxCharts,
+var API, APIVERSION, Chart, HTML, Row, WarbleLogin, WarbleLoginCallback, Widget, aSourceTypes, addSourceType, addSources, addorguser, addsources, affiliate, affiliation, affiliationWizard, altemail, app, badModal, bio, chartOnclick, chartToSvg, chartWrapperButtons, charts_donutchart, charts_gaugechart, charts_linechart, charts_linechart_stacked, charts_linked, charts_radarchart, ciexplorer, clientTypes, clientlist, cog, comShow, comstat, copyCSS, currentSources, dataTable, datepicker, datepickers, defaultOrgChanged, deleteNode, deletesource, doResetPass, donut, downloadBlob, explorer, fScreen, factors, fetch, fetchPhonebook, filterPerson, filterView, findWidget, forumexplorer, gauge, genColors, get, getResetToken, getSourceType, globArgs, hsl2rgb, imexplorer, inviteMember, isArray, isHash, issueexplorer, jsondump, keyValueForm, linechart, loadPageWidgets, logexplorer, login, mailexplorer, make5, makeClientType, makeOrg, manageviews, memberInvited, membershipList, messages, mk, modifyNode, multiviewexplorer, mvp, newview, nodeVal, orgCreated, orgadmin, orglist, pageID, paragraph, patch, phonebook_cached, post, postPublishLink, preferences, pubWidget, publishWidget, publisher, publisherPublic, publisherWidget, put, pwReset, quickColors, radar, radarIndicators, rcollate, redirs, relationship, remail, remorguser, removeMember, renderAccountInfo, renderPhonebook, report, resetpw, rmview, rotateTable, rowZ, saveNodeValue, savedNodeValue, saveprefs, saveview, sendEmail, set, setDefaultOrg, setupPage, setupPhonebook, showClientType, showMore, showType, signout, signup, snap, sourceAdded, sourceTypes, sourceadd, sourceexplorer, sourcelist, sourceret, st, stackChart, subFilter, subFilterGlob, swi, switchChartType, tagList, theme, toFullscreen, toNormal, top5, treemap, trend, trendBox, txt, updateTimeseriesWidgets, updateWidgets, userAccount, validateLogin, validateSignup, viewJS, viewexplorer, widgetCache, widgetexplorer, worldmap, xdelete, xxCharts,
   indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
 
 signup = function(form) {
@@ -4636,9 +4636,9 @@
   }
 };
 
-nodeLocation = function(id, obj) {
+nodeVal = function(id, obj, t) {
   var ip, loc;
-  if (!document.getElementById("tnodeloc_" + id)) {
+  if (!document.getElementById("node_" + t + "_tmp_" + id)) {
     loc = obj.innerText;
     obj.innerHTML = "";
     ip = new HTML('input', {
@@ -4649,10 +4649,10 @@
         padding: '0px'
       },
       data: loc,
-      id: "tnodeloc_" + id,
+      id: "node_" + t + "_tmp_" + id,
       type: 'text',
-      onkeydown: "saveNodeLocation(" + id + ", this, event);",
-      onblur: "savedNodeLocation({}, {id: " + id + ", location: this.getAttribute('data')});"
+      onkeydown: "saveNodeValue(" + id + ", this, event, '" + t + "');",
+      onblur: "savedNodeValue({}, {id: " + id + ", type: '" + t + "', " + t + ": this.getAttribute('data')});"
     });
     ip.value = loc;
     app(obj, ip);
@@ -4660,34 +4660,43 @@
   }
 };
 
-saveNodeLocation = function(id, obj, e) {
-  var nloc;
+saveNodeValue = function(id, obj, e, t) {
+  var js, jsx, nval;
   if (e.key === 'Enter') {
-    nloc = obj.value;
-    return post('node/modify', {
+    nval = obj.value;
+    js = {
+      id: id
+    };
+    jsx = {
       id: id,
-      location: nloc
-    }, {
-      id: id,
-      location: nloc
-    }, savedNodeLocation);
+      type: t
+    };
+    js[t] = nval;
+    jsx[t] = nval;
+    return post('node/modify', js, jsx, savedNodeValue);
   } else if (e.key === 'Escape') {
-    return savedNodeLocation({}, {
+    js = {
+      id: id
+    };
+    jsx = {
       id: id,
-      location: obj.getAttribute('data')
-    });
+      type: t
+    };
+    js[t] = obj.getAttribute('data');
+    jsx[t] = obj.getAttribute('data');
+    return savedNodeValue(js, jsx);
   }
 };
 
-savedNodeLocation = function(json, state) {
+savedNodeValue = function(json, state) {
   var obj;
-  obj = document.getElementById("nodeloc_" + state.id);
+  obj = document.getElementById("node_" + state.type + "_" + state.id);
   obj.innerHTML = "";
-  return app(obj, txt(state.location));
+  return app(obj, txt(state[state.type]));
 };
 
 clientlist = function(json, state) {
-  var btn, card, d, len, line, q, retval, slist, source, sources, vlist, vrf;
+  var banner, btn, card, d, hn, len, line, lline, lp, q, retval, rline, slist, source, sources, vlist, vrf;
   slist = mk('div');
   vlist = new HTML('div');
   if (json.nodes) {
@@ -4703,41 +4712,39 @@
       card = new HTML('div', {
         "class": 'clientcard'
       });
-      d = new HTML('div', {
-        height: '36px',
-        position: 'relative',
+      banner = new HTML('div', {
+        "class": 'banner'
+      });
+      rline = new HTML('div', {
         style: {
-          marginBottom: '12px'
+          float: 'right',
+          width: '300px',
+          textAlign: 'center'
         }
       });
-      line = new HTML('div', {
+      lline = new HTML('div', {
         style: {
-          position: 'relative',
-          lineHeight: '30px',
-          height: '30px',
           float: 'left',
-          padding: '2px',
-          display: 'inline-block',
-          textAlign: 'center',
-          margin: '-5.25px',
-          width: '225px',
-          borderTopLeftRadius: '6px',
-          background: '#4c8946',
-          marginBottom: '4px'
+          width: '500px',
+          textAlign: 'center'
         }
       });
+      hn = new HTML('span', {
+        title: 'Click to edit',
+        id: "node_hostname_" + source.id,
+        onclick: "nodeVal(" + source.id + ", this, 'hostname');"
+      }, txt(source.hostname || "(unknown)"));
+      lline.inject(hn);
       vrf = [];
       if (!source.verified) {
-        line.style.background = '#bc9621';
-        card.style.borderColor = '#bc9621';
-        line.style.width = '445px';
+        card.setAttribute('class', 'clientcard orange');
         vrf = [
           'Unverified Node', new HTML('button', {
             "class": 'btn btn-sm btn-primary',
             onclick: "modifyNode(" + source.id + ", {verified: true, enabled: true});"
           }, "Verify + Enable")
         ];
-        line.inject(vrf);
+        rline.inject(vrf);
         btn = new HTML('button', {
           title: 'Delete node',
           "class": 'btn btn-square btn-danger',
@@ -4750,28 +4757,11 @@
         }, new HTML('i', {
           "class": 'fa fa-trash'
         }, ''));
-        line.inject(btn);
-        d.inject(line);
+        rline.inject(btn);
       }
       if (source.verified) {
-        line = new HTML('div', {
-          style: {
-            position: 'relative',
-            lineHeight: '30px',
-            height: '30px',
-            float: 'right',
-            padding: '2px',
-            display: 'inline-block',
-            textAlign: 'center',
-            margin: '-5.25px',
-            width: '445px',
-            borderTopRightRadius: '6px',
-            background: '#4c8946',
-            marginBottom: '4px'
-          }
-        });
         vrf = [];
-        card.style.borderColor = '#4c8946';
+        card.setAttribute('class', 'clientcard green');
         if (source.enabled) {
           vrf = [
             'Active', new HTML('button', {
@@ -4780,8 +4770,7 @@
             }, "Disable")
           ];
         } else {
-          line.style.background = '#777';
-          card.style.borderColor = '#777';
+          card.setAttribute('class', 'clientcard grey');
           vrf = [
             'Disabled', new HTML('button', {
               "class": 'btn btn-sm btn-primary',
@@ -4789,8 +4778,7 @@
             }, "Re-enable")
           ];
         }
-        line.inject(vrf);
-        d.inject(line);
+        rline.inject(vrf);
         btn = new HTML('button', {
           title: 'Delete node',
           "class": 'btn btn-square btn-danger',
@@ -4803,9 +4791,11 @@
         }, new HTML('i', {
           "class": 'fa fa-trash'
         }, ''));
-        line.inject(btn);
+        rline.inject(btn);
       }
-      card.inject(d);
+      banner.inject(lline);
+      banner.inject(rline);
+      card.inject(banner);
       vlist.inject(card);
       d = new HTML('p');
       card.inject(d);
@@ -4822,11 +4812,6 @@
       line = new HTML('div', {
         "class": 'clientcardline'
       });
-      line.inject([new HTML('b', {}, "Hostname: "), txt(source.hostname)]);
-      d.inject(line);
-      line = new HTML('div', {
-        "class": 'clientcardline'
-      });
       line.inject([new HTML('b', {}, "Fingerprint: "), new HTML('kbd', {}, source.fingerprint)]);
       d.inject(line);
       line = new HTML('div', {
@@ -4834,14 +4819,18 @@
       });
       line.inject([
         new HTML('b', {}, "Location: "), new HTML('span', {
-          id: "nodeloc_" + source.id,
-          onclick: "nodeLocation(" + source.id + ", this, event);"
+          title: 'Click to edit',
+          id: "node_location_" + source.id,
+          onclick: "nodeVal(" + source.id + ", this, 'location');"
         }, txt(source.location || "(unknown)"))
       ]);
       d.inject(line);
+      line = new HTML('div', {
+        "class": 'clientcardline'
+      });
+      lp = new Date(source.lastping * 1000.0);
+      line.inject([new HTML('b', {}, "Last Active: "), txt(moment(lp).fromNow() + " (" + lp.ISOBare() + ")")]);
       d.inject(line);
-      line.inject(new HTML('br'));
-      line.inject(new HTML('br'));
     }
   }
   state.widget.inject(slist, true);