diff --git a/package/gluon-status-page/Makefile b/package/gluon-status-page/Makefile index 7b496ddd..c283f928 100644 --- a/package/gluon-status-page/Makefile +++ b/package/gluon-status-page/Makefile @@ -1,78 +1,38 @@ include $(TOPDIR)/rules.mk PKG_NAME:=gluon-status-page -PKG_VERSION:=2 -PKG_RELEASE:=1 +PKG_VERSION:=3 -PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) -PKG_BUILD_DEPENDS:=node/host +include ../gluon.mk -include $(INCLUDE_DIR)/package.mk +PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG) -RJS_VERSION:=2.1.10 -RJS:=r-$(RJS_VERSION).js -define Download/rjs - FILE:=$(RJS) - URL:=http://requirejs.org/docs/release/$(RJS_VERSION) - URL_FILE:=r.js - HASH:=d0b7cfd962a7f8ac52a5d528df486341eed856609d9c75fa2566a32900f5b143 -endef -$(eval $(call Download,rjs)) - -BACON_VERSION:=0.7.71 -BACON:=Bacon-$(BACON_VERSION).js -define Download/Bacon - FILE:=$(BACON) - URL:=http://cdnjs.cloudflare.com/ajax/libs/bacon.js/$(BACON_VERSION) - URL_FILE:=Bacon.js - HASH:=93d840d2167964ced7c53598f7d07151c3bfb1d8a7c3e8cff44cadd7dea25f1d -endef -$(eval $(call Download,Bacon)) - -ALMOND_VERSION:=0.3.1 -ALMOND:=almond-$(ALMOND_VERSION).js -define Download/almond - FILE:=$(ALMOND) - URL:=https://raw.githubusercontent.com/jrburke/almond/$(ALMOND_VERSION) - URL_FILE:=almond.js - HASH:=3df2baac13da29dab646f9b9ddd2c5e09d91a49ae3a4f3befb40ce1dd60937f2 -endef -$(eval $(call Download,almond)) - define Package/gluon-status-page SECTION:=gluon CATEGORY:=Gluon - TITLE:=Adds a status page showing information about the node. - DEPENDS:=+gluon-status-page-api -endef - -define Package/gluon-status-page/description - Adds a status page showing information about the node. - Especially useful in combination with the next-node feature. + TITLE:=Status page showing information about the node + DEPENDS:=+gluon-web +gluon-status-page-api endef define Build/Prepare mkdir -p $(PKG_BUILD_DIR) - $(CP) $(DL_DIR)/$(RJS) $(PKG_BUILD_DIR)/r.js - $(CP) $(DL_DIR)/$(BACON) $(PKG_BUILD_DIR)/Bacon.js - $(CP) $(DL_DIR)/$(ALMOND) $(PKG_BUILD_DIR)/almond.js -endef - -define Build/Configure - $(CP) ./src/* $(PKG_BUILD_DIR)/ endef define Build/Compile - cd $(PKG_BUILD_DIR) && \ - node r.js -o build.js && \ - node r.js -o cssIn=css/main.css out=style.css && \ - $(M4) index.html.m4 > index.html + $(call GluonSrcDiet,./luasrc,$(PKG_BUILD_DIR)/luadest/) + $(call GluonBuildI18N,gluon-status-page,i18n) endef define Package/gluon-status-page/install - $(INSTALL_DIR) $(1)/lib/gluon/status-page/www/ - $(INSTALL_DATA) $(PKG_BUILD_DIR)/index.html $(1)/lib/gluon/status-page/www/ + $(CP) ./files/* $(1)/ + $(CP) $(PKG_BUILD_DIR)/luadest/* $(1)/ + + $(INSTALL_DIR) $(1)/lib/gluon/status-page/view/ + $(LN) /lib/gluon/web/i18n $(1)/lib/gluon/status-page/ + $(LN) /lib/gluon/web/view/error $(1)/lib/gluon/status-page/view/ + + $(call GluonInstallI18N,gluon-status-page,$(1)) endef $(eval $(call BuildPackage,gluon-status-page)) diff --git a/package/gluon-status-page/files/lib/gluon/status-page/view/layout.html b/package/gluon-status-page/files/lib/gluon/status-page/view/layout.html new file mode 100644 index 00000000..11969ddc --- /dev/null +++ b/package/gluon-status-page/files/lib/gluon/status-page/view/layout.html @@ -0,0 +1,24 @@ +<%- + http:prepare_content("application/xhtml+xml") +-%> + + + + + + + <%:Error%> + + + + +
+

<%:Error%>

+
+
+
+ <% renderer.render(content, scope, pkg) %> +
+
+ + diff --git a/package/gluon-status-page/files/lib/gluon/status-page/view/status-page.html b/package/gluon-status-page/files/lib/gluon/status-page/view/status-page.html new file mode 100644 index 00000000..2d023af7 --- /dev/null +++ b/package/gluon-status-page/files/lib/gluon/status-page/view/status-page.html @@ -0,0 +1,162 @@ +<%- + local fs = require 'nixio.fs' + local json = require 'jsonc' + local ubus = require 'ubus' + local util = require 'gluon.util' + + local translations = {} + + local function _(v) + translations[v] = translate(v) + end + + -- i18n strings for JavaScript + _('.') -- decimal point + _('connected') + _('not connected') + _('1 day') + _('%s days') + _('%s used') + _('%s packets/s') + + local function get_interfaces() + local uconn = ubus.connect() + if not uconn then + error('failed to connect to ubus') + end + local interfaces = util.get_mesh_devices(uconn) + ubus.close(uconn) + table.sort(interfaces) + return interfaces + end + + local function is_wireless(iface) + while true do + local pattern = '/sys/class/net/' .. iface .. '/lower_*' + local lower = fs.glob(pattern)() + if not lower then break end + + iface = lower:sub(pattern:len()) + end + + return fs.access('/sys/class/net/' .. iface .. '/wireless') ~= nil + end + + local interfaces = get_interfaces() + + local nodeinfo = json.parse(util.exec('exec gluon-neighbour-info -d ::1 -p 1001 -t 1 -c 1 -r nodeinfo')) + + local function sorted(t) + t = {unpack(t)} + table.sort(t) + return t + end + + local function enabled(v) + return v and translate('enabled') or translate('disabled') + end + + local function statistics(key, format) + return string.format('', pcdata(key), pcdata(format or 'id')) + end + + local function statisticsTraffic(key) + return string.format('%s
%s
%s', + statistics(key .. '/packets', 'packetsDiff'), + statistics(key .. '/bytes', 'bytesDiff'), + statistics(key .. '/bytes', 'bytes') + ) + end + + http:prepare_content("application/xhtml+xml") +-%> + + + + + + + <%| nodeinfo.hostname %> - <%:Status%> + + + + > +
+

<%| nodeinfo.hostname %>

+
+
+
+

<%:Overview%>

+
+
<%:Node name%>
<%| nodeinfo.hostname %>
+
<%:Model%>
<%| nodeinfo.hardware.model %>
+
<%:Primary MAC address%>
<%| nodeinfo.network.mac %>
+
<%:IP address%>
<%= pcdata(table.concat(sorted(nodeinfo.network.addresses), '\n')):gsub('\n', '
') %>
+
<%:Firmware%>
<%| nodeinfo.software.firmware.release %>
+ <% if nodeinfo.software.fastd then -%> +
<%:Mesh VPN%>
<%| enabled(nodeinfo.software.fastd.enabled) %>
+ <%- end %> + <% if nodeinfo.software.autoupdater then -%> +
<%:Automatic updates%>
<%| enabled(nodeinfo.software.autoupdater.enabled) %><%| + nodeinfo.software.autoupdater.enabled and + string.format(' (%s)', nodeinfo.software.autoupdater.branch) + %>
+ <%- end %> +
+
+
+

<%:Monitoring%>

+ + + + + + + +
<%:Uptime%><%= statistics('uptime', 'time') %>
<%:Load average%><%= statistics('loadavg', 'decimal') %>
<%:RAM%><%= statistics('memory', 'memory') %>
<%:Filesystem%><%= statistics('rootfs_usage', 'percent') %>
<%:Gateway%><%= statistics('gateway') %>
<%:Clients%><%= statistics('clients/total') %>
+ +

<%:Traffic%>

+ + + + +
<%:Transmitted%><%= statisticsTraffic('traffic/tx') %>
<%:Received%><%= statisticsTraffic('traffic/rx') %>
<%:Forwarded%><%= statisticsTraffic('traffic/forward') %>
+ + +
+
+

<%:Neighbors%>

+ + <% + for _, iface in ipairs(interfaces) do + local wireless = is_wireless(iface) + local address = fs.readfile('/sys/class/net/' .. iface .. '/address') + if address then + %> +

<%| iface %>

+
> + + + + + <% if wireless then %> + + + + <% end %> + +
<%:Node%>TQdBm<%:Distance%><%:Last seen%>
+
+ <% + end + end + %> +
+
+ + + diff --git a/package/gluon-status-page/files/lib/gluon/status-page/www/index.html b/package/gluon-status-page/files/lib/gluon/status-page/www/index.html new file mode 100644 index 00000000..d5da1c84 --- /dev/null +++ b/package/gluon-status-page/files/lib/gluon/status-page/www/index.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/package/gluon-status-page/files/lib/gluon/status-page/www/static/status-page.css b/package/gluon-status-page/files/lib/gluon/status-page/www/static/status-page.css new file mode 100644 index 00000000..a6a974e2 --- /dev/null +++ b/package/gluon-status-page/files/lib/gluon/status-page/www/static/status-page.css @@ -0,0 +1 @@ +html,body,div,span,h1,h2,h3,dl,dt,dd,canvas,header,table,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{background:rgba(0,0,0,0.12);font-family:Roboto, Lucida Grande, sans, Arial;color:rgba(0,0,0,0.87);font-size:14px;line-height:1}a{color:rgba(220,0,103,0.87);text-decoration:none;margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent}a:hover{text-decoration:underline}h1{font-weight:bold}h2{font-size:16px;margin-bottom:16px;color:rgba(0,0,0,0.54)}h3{font-size:15px;margin-top:16px;margin-bottom:8px;color:rgba(0,0,0,0.54)}header{display:flex;padding:0 14px;background:#dc0067;color:rgba(255,255,255,0.98);position:absolute;top:0;width:100%;box-sizing:border-box;height:20vh;z-index:-1;box-shadow:0px 5px 6px rgba(0,0,0,0.16),0px 1.5px 3px rgba(0,0,0,0.23);white-space:nowrap}header h1{font-size:24px;margin:10px 0;padding:6px 0;text-overflow:ellipsis;overflow:hidden;flex:1}.container{display:flex;max-width:90vw;margin:64px auto 24px auto;background:#fdfdfd;box-shadow:0px 5px 20px rgba(0,0,0,0.19),0px 3px 6px rgba(0,0,0,0.23)}.container>.frame{flex:1;border-style:solid;border-color:rgba(0,0,0,0.12);box-sizing:border-box;padding:16px}.container>.frame+.frame{border-width:0 0 0 1px}dt,th{font-weight:bold;color:rgba(0,0,0,0.87)}dt{margin-bottom:4px}th,td{text-align:left;padding:4px 16px 4px 0}th:last-child,td:last-child{padding-right:0}dd,td{font-weight:normal;font-size:0.9em;color:rgba(0,0,0,0.54)}dd{margin-bottom:16px}table{border-collapse:collapse;border-spacing:0}table.datatable{width:100%}table.datatable th,table.datatable td{font-size:1em;white-space:nowrap}table.datatable th:last-child,table.datatable td:last-child{width:100%}table.datatable tr.inactive{opacity:0.33}table.datatable tr.highlight{background:rgba(255,180,0,0.25)}canvas.signalgraph{margin-top:8px;width:100%}@media only screen and (max-width: 1250px){.container{max-width:none;margin:56px 0 0}header{height:56px;z-index:1;position:fixed}}@media only screen and (max-width: 700px){.container{display:block}.container>.frame+.frame{border-width:1px 0 0 0}} diff --git a/package/gluon-status-page/files/lib/gluon/status-page/www/static/status-page.js b/package/gluon-status-page/files/lib/gluon/status-page/www/static/status-page.js new file mode 100644 index 00000000..55ee27a6 --- /dev/null +++ b/package/gluon-status-page/files/lib/gluon/status-page/www/static/status-page.js @@ -0,0 +1 @@ +"use strict";!function(){var t=JSON.parse(document.body.getAttribute("data-translations"));function e(e,n){return e.toFixed(n).replace(/\./,t["."])}function n(t,n){n--;for(var i=t;i>=10&&n>0;i/=10)n--;return e(t,n)}function i(t){return function(t,e,i){for(var r=0;i>e&&r1&&(a+=t["%s days"].sprintf(i)+", "),a+=r+":",n<10&&(a+="0"),a+=n},packetsDiff:function(n,i,r){if(r>0)return a=(n-i)/r,t["%s packets/s"].sprintf(e(a,0));var a},bytesDiff:function(t,e,n){if(n>0)return i(8*((t-e)/n))+"bps"},bytes:function(t){return i(t)+"B"}};function a(t,e){return e.split("/").forEach(function(e){t&&(t=t[e])}),t}function o(t,e){var n=new EventSource(t),i={};n.onmessage=function(t){var n=JSON.parse(t.data);e(n,i),i=n},n.onerror=function(){n.close(),window.setTimeout(function(){o(t,e)},3e3)}}var c,s=document.body.getAttribute("data-node-address");try{c=JSON.parse(document.body.getAttribute("data-node-location"))}catch(t){}var u=document.querySelectorAll("[data-statistics]");o("/cgi-bin/dyn/statistics",function(e,n){var i=e.uptime-n.uptime;u.forEach(function(t){var o=t.getAttribute("data-statistics"),c=t.getAttribute("data-format"),s=a(n,o),u=a(e,o);try{var l=r[c](u,s,i);void 0!==l&&(t.textContent=l)}catch(t){console.error(t)}});try{!function(e){var n=document.getElementById("mesh-vpn");if(e){n.style.display="";for(var i=document.getElementById("mesh-vpn-peers");i.lastChild;)i.removeChild(i.lastChild);var a=function t(e,n){return Object.keys(n.peers||{}).forEach(function(t){e.push([t,n.peers[t]])}),Object.keys(n.groups||{}).forEach(function(i){t(e,n.groups[i])}),e}([],e);a.sort(),a.forEach(function(e){var n=document.createElement("tr"),a=document.createElement("th");a.textContent=e[0],n.appendChild(a);var o=document.createElement("td");e[1]?o.textContent=t.connected+" ("+r.time(e[1].established)+")":o.textContent=t["not connected"],n.appendChild(o),i.appendChild(n)})}else n.style.display="none"}(e.mesh_vpn)}catch(t){console.error(t)}});var l={};function h(t){var e=document.createElement("canvas"),n=e.getContext("2d"),i=null,r=1.2;return{canvas:e,highlight:!1,resize:function(t,i){try{n.getImageData(0,0,t,i)}catch(t){}e.width=t,e.height=i},draw:function(a,o){var c,s,u=o(i);n.clearRect(a,0,5,e.height),u&&(c=a,s=u,n.beginPath(),n.fillStyle=t,n.arc(c,s,r,0,2*Math.PI,!1),n.closePath(),n.fill())},set:function(t){i=t}}}function f(){var t=-100,e=0,n=0,i=[],r=document.createElement("canvas");r.className="signalgraph",r.height=200;var a=r.getContext("2d");function o(){r.width=r.clientWidth,i.forEach(function(t){t.resize(r.width,r.height)})}function c(){if(0!==r.clientWidth){r.width!==r.clientWidth&&o(),a.clearRect(0,0,r.width,r.height);var c=!1;i.forEach(function(t){t.highlight&&(c=!0)}),a.save(),i.forEach(function(i){c&&(a.globalAlpha=.2),i.highlight&&(a.globalAlpha=1),i.draw(n,function(n){return i=n,a=t,o=e,c=r.height,(1-(i-a)/(o-a))*c;var i,a,o,c}),a.drawImage(i.canvas,0,0)}),a.restore(),a.save(),a.beginPath(),a.strokeStyle="rgba(255, 180, 0, 0.15)",a.lineWidth=5,a.moveTo(n+2.5,0),a.lineTo(n+2.5,r.height),a.stroke(),function(){var n,i,o,c,s=Math.floor(r.height/40);a.save(),a.lineWidth=.5,a.strokeStyle="rgba(0, 0, 0, 0.25)",a.fillStyle="rgba(0, 0, 0, 0.5)",a.textAlign="end",a.textBaseline="bottom",a.beginPath();for(var u=0;u40&&(c(),n=(n+1)%r.width,s=e),window.requestAnimationFrame(t)}),{el:r,addSignal:function(t){i.push(t),t.resize(r.width,r.height)},removeSignal:function(t){i.splice(i.indexOf(t),1)}}}function d(t,e,n,i){var r=t.table.insertRow(),a=r.insertCell();if(t.wireless){var o=document.createElement("span");o.textContent="⬤ ",o.style.color=n,a.appendChild(o)}var u=document.createElement("span");u.textContent=e,a.appendChild(u);var l,f,d,g,v,m=r.insertCell();function p(){v&&window.clearTimeout(v),v=window.setTimeout(function(){g&&t.signalgraph.removeSignal(g),r.parentNode.removeChild(r),i()},6e4)}function b(t){var e=function(t){"::"==t.slice(0,2)&&(t="0"+t),"::"==t.slice(-2)&&(t+="0");var e=t.split(":"),n=e.length,i=[];return e.forEach(function(t,e){if(""===t)for(;n++<=8;)i.push(0);else{if(!/^[a-f0-9]{1,4}$/i.test(t))return;i.push(parseInt(t,16))}}),i}(t);if(e){var n="";return e.forEach(function(t){n+=("0000000000000000"+t.toString(2)).slice(-16)}),n}}return m.textContent="-",t.wireless&&((l=r.insertCell()).textContent="-",(f=r.insertCell()).textContent="-",(d=r.insertCell()).textContent="-",g=h(n),t.signalgraph.addSignal(g)),r.onmouseenter=function(){r.classList.add("highlight"),g&&(g.highlight=!0)},r.onmouseleave=function(){r.classList.remove("highlight"),g&&(g.highlight=!1)},p(),{update_nodeinfo:function(t){var e,n,i,r,a,o,l,h,d=function(t){var e=b(s);if(t&&t[0]){(t=t.map(function(t){var n=b(t);if(!n)return[-1];var i=0;return e&&(i=function(t,e){var n;for(n=0;ne[0]?-1:t[1]e[1]?1:0});var n=t[0][2];return n&&!/^fe80:/i.test(n)?n:void 0}}(t.network.addresses);if(d){if("span"===u.nodeName.toLowerCase()){var g=u;u=document.createElement("a"),g.parentNode.replaceChild(u,g)}u.href="http://["+d+"]/"}if(u.textContent=t.hostname,c&&t.location){var v=(e=c.latitude,n=c.longitude,i=t.location.latitude,r=t.location.longitude,a=Math.PI/180,o=(i*=a)-(e*=a),l=(r*=a)-(n*=a),h=Math.sin(o/2)*Math.sin(o/2)+Math.sin(l/2)*Math.sin(l/2)*Math.cos(e)*Math.cos(i),2*Math.asin(Math.sqrt(h))*6372.8);f.textContent=Math.round(1e3*v)+" m"}p()},update_mesh:function(t){m.textContent=Math.round(t.tq/2.55)+" %",p()},update_wifi:function(t){l.textContent=t.signal,d.textContent=Math.round(t.inactive/1e3)+" s",r.classList.toggle("inactive",t.inactive>200),g.set(t.inactive>200?null:t.signal),p()}}}function g(t,e,n){var i,r={};n&&(i=f(),t.appendChild(i.el));var a={table:t.firstElementChild,signalgraph:i,ifname:e,wireless:n},c=!1,s={},u=[];function l(){if(!c){c=!0;var t=new EventSource("/cgi-bin/dyn/neighbours-nodeinfo?"+encodeURIComponent(e));t.addEventListener("neighbour",function(t){try{var e=JSON.parse(t.data);(n=e,i=[],a=n.network.mesh,Object.keys(a).forEach(function(t){var e=a[t].interfaces;Object.keys(e).forEach(function(t){e[t].forEach(function(t){i.push(t)})})}),i).forEach(function(t){var n=r[t];if(n){delete s[t];try{n.update_nodeinfo(e)}catch(t){console.error(t)}}})}catch(t){console.error(t)}var n,i,a},!1),t.onerror=function(){t.close(),c=!1,Object.keys(s).forEach(function(t){s[t]>0&&(s[t]--,l())})}}}function h(t){var e=r[t];return e||(s[t]=3,e=r[t]=d(a,t,(u[0]||(u=["#396AB1","#DA7C30","#3E9651","#CC2529","#535154","#6B4C9A","#922428","#948B3D"]),u.shift()),function(){delete s[t],delete r[t]}),l()),e}return n&&o("/cgi-bin/dyn/stations?"+encodeURIComponent(e),function(t){Object.keys(t).forEach(function(e){var n=t[e];h(e).update_wifi(n)})}),{get_neigh:h}}document.querySelectorAll("[data-interface]").forEach(function(t){var e=t.getAttribute("data-interface"),n=(t.getAttribute("data-interface-address"),!!t.getAttribute("data-interface-wireless"));l[e]=g(t,e,n)}),o("/cgi-bin/dyn/neighbours-batadv",function(t){Object.keys(t).forEach(function(e){var n=t[e],i=l[n.ifname];i&&i.get_neigh(e).update_mesh(n)})})}(); \ No newline at end of file diff --git a/package/gluon-status-page/i18n/de.po b/package/gluon-status-page/i18n/de.po new file mode 100644 index 00000000..b6ff1a78 --- /dev/null +++ b/package/gluon-status-page/i18n/de.po @@ -0,0 +1,113 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"PO-Revision-Date: 2018-02-26 00:30+0100\n" +"Last-Translator: \n" +"Language-Team: German\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "%s days" +msgstr "%s Tage" + +msgid "%s packets/s" +msgstr "%s Pakete/s" + +msgid "%s used" +msgstr "%s belegt" + +msgid "." +msgstr "," + +msgid "1 day" +msgstr "1 Tag" + +msgid "Automatic updates" +msgstr "Automatische Updates" + +msgid "Clients" +msgstr "Clients" + +msgid "Distance" +msgstr "Entfernung" + +msgid "Error" +msgstr "Fehler" + +msgid "Filesystem" +msgstr "Dateisystem" + +msgid "Firmware" +msgstr "Firmware" + +msgid "Forwarded" +msgstr "Weitergeleitet" + +msgid "Gateway" +msgstr "Gateway" + +msgid "IP address" +msgstr "IP-Adresse" + +msgid "Last seen" +msgstr "Zuletzt gesehen" + +msgid "Load average" +msgstr "Systemlast" + +msgid "Mesh VPN" +msgstr "Mesh-VPN" + +msgid "Model" +msgstr "Modell" + +msgid "Monitoring" +msgstr "" + +msgid "Neighbors" +msgstr "Nachbarknoten" + +msgid "Node" +msgstr "Knoten" + +msgid "Node name" +msgstr "Knotenname" + +msgid "Overview" +msgstr "Übersicht" + +msgid "Primary MAC address" +msgstr "Primäre MAC-Adresse" + +msgid "RAM" +msgstr "RAM" + +msgid "Received" +msgstr "Empfangen" + +msgid "Status" +msgstr "Status" + +msgid "Traffic" +msgstr "" + +msgid "Transmitted" +msgstr "Gesendet" + +msgid "Uptime" +msgstr "Laufzeit" + +msgid "connected" +msgstr "verbunden" + +msgid "disabled" +msgstr "deaktiviert" + +msgid "enabled" +msgstr "aktiviert" + +msgid "not connected" +msgstr "nicht verbunden" diff --git a/package/gluon-status-page/i18n/gluon-status-page.pot b/package/gluon-status-page/i18n/gluon-status-page.pot new file mode 100644 index 00000000..b4c3882e --- /dev/null +++ b/package/gluon-status-page/i18n/gluon-status-page.pot @@ -0,0 +1,104 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +msgid "%s days" +msgstr "" + +msgid "%s packets/s" +msgstr "" + +msgid "%s used" +msgstr "" + +msgid "." +msgstr "" + +msgid "1 day" +msgstr "" + +msgid "Automatic updates" +msgstr "" + +msgid "Clients" +msgstr "" + +msgid "Distance" +msgstr "" + +msgid "Error" +msgstr "" + +msgid "Filesystem" +msgstr "" + +msgid "Firmware" +msgstr "" + +msgid "Forwarded" +msgstr "" + +msgid "Gateway" +msgstr "" + +msgid "IP address" +msgstr "" + +msgid "Last seen" +msgstr "" + +msgid "Load average" +msgstr "" + +msgid "Mesh VPN" +msgstr "" + +msgid "Model" +msgstr "" + +msgid "Monitoring" +msgstr "" + +msgid "Neighbors" +msgstr "" + +msgid "Node" +msgstr "" + +msgid "Node name" +msgstr "" + +msgid "Overview" +msgstr "" + +msgid "Primary MAC address" +msgstr "" + +msgid "RAM" +msgstr "" + +msgid "Received" +msgstr "" + +msgid "Status" +msgstr "" + +msgid "Traffic" +msgstr "" + +msgid "Transmitted" +msgstr "" + +msgid "Uptime" +msgstr "" + +msgid "connected" +msgstr "" + +msgid "disabled" +msgstr "" + +msgid "enabled" +msgstr "" + +msgid "not connected" +msgstr "" diff --git a/package/gluon-status-page/i18n/ru.README b/package/gluon-status-page/i18n/ru.README new file mode 100644 index 00000000..3f5101af --- /dev/null +++ b/package/gluon-status-page/i18n/ru.README @@ -0,0 +1,31 @@ +THe previous version of the status page had a Russian translation; +if we ever add Russion to gluon-web, the following strings can be reused: + +"Node": "Узел", +"Distance": "Дальность", +"Inactive": "Не активен", +"Node name": "Имя узла", +"Contact": "Контакт", +"Model": "Модель", +"Primary MAC": "Основной MAC", +"IP Address": "IP Адрес", +"Automatic updates": "Автоматические обновления", +"Overview": "Обзор", +"used": "используется", +"Uptime": "Время работы", +"Load average": "Загрузка системы", +"Gateway": "Шлюз", +"Clients": "Клиенты", +"Transmitted": "Передано", +"Received": "Получено", +"Forwarded": "Переправленно", +"Day": "День", +"Days": "Дней", +"connected": "подключено", +"not connected": "не подключено", +"Packets/s": "Пакетов/c", +"Statistic": "Статистика", +"Traffic": "Трафик", +"Neighbors": "Соседи", +"Firmware": "Прошивка", +"Branch": "Ветка" diff --git a/package/gluon-status-page/iconfont-config.json b/package/gluon-status-page/iconfont-config.json deleted file mode 100644 index af8718cc..00000000 --- a/package/gluon-status-page/iconfont-config.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "name": "statuspage", - "css_prefix_text": "icon-", - "css_use_suffix": false, - "hinting": true, - "units_per_em": 1000, - "ascent": 850, - "glyphs": [ - { - "uid": "12f4ece88e46abd864e40b35e05b11cd", - "css": "ok", - "code": 59397, - "src": "fontawesome" - }, - { - "uid": "5211af474d3a9848f67f945e2ccaf143", - "css": "cancel", - "code": 59399, - "src": "fontawesome" - }, - { - "uid": "e15f0d620a7897e2035c18c80142f6d9", - "css": "link-ext", - "code": 59407, - "src": "fontawesome" - }, - { - "uid": "c76b7947c957c9b78b11741173c8349b", - "css": "attention", - "code": 59403, - "src": "fontawesome" - }, - { - "uid": "559647a6f430b3aeadbecd67194451dd", - "css": "menu", - "code": 59392, - "src": "fontawesome" - }, - { - "uid": "2d6150442079cbda7df64522dc24f482", - "css": "down-dir", - "code": 59393, - "src": "fontawesome" - }, - { - "uid": "80cd1022bd9ea151d554bec1fa05f2de", - "css": "up-dir", - "code": 59394, - "src": "fontawesome" - }, - { - "uid": "9dc654095085167524602c9acc0c5570", - "css": "left-dir", - "code": 59395, - "src": "fontawesome" - }, - { - "uid": "fb1c799ffe5bf8fb7f8bcb647c8fe9e6", - "css": "right-dir", - "code": 59396, - "src": "fontawesome" - }, - { - "uid": "a73c5deb486c8d66249811642e5d719a", - "css": "arrows-cw", - "code": 59400, - "src": "fontawesome" - }, - { - "uid": "750058837a91edae64b03d60fc7e81a7", - "css": "ellipsis-vert", - "code": 59401, - "src": "fontawesome" - }, - { - "uid": "56a21935a5d4d79b2e91ec00f760b369", - "css": "sort", - "code": 59404, - "src": "fontawesome" - }, - { - "uid": "94103e1b3f1e8cf514178ec5912b4469", - "css": "sort-down", - "code": 59405, - "src": "fontawesome" - }, - { - "uid": "65b3ce930627cabfb6ac81ac60ec5ae4", - "css": "sort-up", - "code": 59406, - "src": "fontawesome" - }, - { - "uid": "cda0cdcfd38f5f1d9255e722dad42012", - "css": "spinner", - "code": 59402, - "src": "fontawesome" - } - ] -} \ No newline at end of file diff --git a/package/gluon-status-page/javascript/status-page.js b/package/gluon-status-page/javascript/status-page.js new file mode 100644 index 00000000..7a890bdc --- /dev/null +++ b/package/gluon-status-page/javascript/status-page.js @@ -0,0 +1,737 @@ +/* + Build using: + + uglifyjs javascript/status-page.js -o files/lib/gluon/status-page/www/static/status-page.js -c -m +*/ + +'use strict'; + +(function() { + var _ = JSON.parse(document.body.getAttribute('data-translations')); + + String.prototype.sprintf = function() { + var i = 0; + var args = arguments; + + return this.replace(/%s/g, function() { + return args[i++]; + }); + }; + + function formatNumberFixed(d, digits) { + return d.toFixed(digits).replace(/\./, _['.']) + } + + function formatNumber(d, digits) { + digits--; + + for (var v = d; v >= 10 && digits > 0; v /= 10) + digits--; + + // avoid toPrecision as it might produce strings in exponential notation + return formatNumberFixed(d, digits); + } + + function prettyPackets(d) { + return _['%s packets/s'].sprintf(formatNumberFixed(d, 0)); + } + + function prettyPrefix(prefixes, step, d) { + var prefix = 0; + + while (d > step && prefix < prefixes.length - 1) { + d /= step; + prefix++; + } + + d = formatNumber(d, 3); + return d + " " + prefixes[prefix]; + } + + function prettySize(d) { + return prettyPrefix([ "", "K", "M", "G", "T" ], 1024, d); + } + + function prettyBits(d) { + return prettySize(8 * d) + "bps"; + } + + function prettyBytes(d) { + return prettySize(d) + "B"; + } + + var formats = { + 'id': function(value) { + return value; + }, + 'decimal': function(value) { + return formatNumberFixed(value, 2); + }, + 'percent': function(value) { + return _['%s used'].sprintf(formatNumber(100 * value, 3) + '%'); + }, + 'memory': function(memory) { + var usage = 1 - (memory.free + memory.buffers + memory.cached) / memory.total + return formats.percent(usage); + }, + 'time': function(seconds) { + var minutes = Math.round(seconds / 60); + + var days = Math.floor(minutes / 1440); + var hours = Math.floor((minutes % 1440) / 60); + minutes = Math.floor(minutes % 60); + + var out = ''; + + if (days === 1) + out += _['1 day'] + ', '; + else if (days > 1) + out += _['%s days'].sprintf(days) + ", "; + + out += hours + ":"; + + if (minutes < 10) + out += "0"; + + out += minutes; + + return out; + }, + 'packetsDiff': function(packets, packetsPrev, diff) { + if (diff > 0) + return prettyPackets((packets-packetsPrev) / diff); + + }, + 'bytesDiff': function(bytes, bytesPrev, diff) { + if (diff > 0) + return prettyBits((bytes-bytesPrev) / diff); + }, + 'bytes': function(bytes) { + return prettyBytes(bytes); + }, + } + + + function resolve_key(obj, key) { + key.split('/').forEach(function(part) { + if (obj) + obj = obj[part]; + }); + + return obj; + } + + function add_event_source(url, handler) { + var source = new EventSource(url); + var prev = {}; + source.onmessage = function(m) { + var data = JSON.parse(m.data); + handler(data, prev); + prev = data; + } + source.onerror = function() { + source.close(); + window.setTimeout(function() { + add_event_source(url, handler); + }, 3000); + } + } + + var node_address = document.body.getAttribute('data-node-address'); + + var location; + try { + location = JSON.parse(document.body.getAttribute('data-node-location')); + } catch (e) { + } + + + function update_mesh_vpn(data) { + function add_group(peers, d) { + Object.keys(d.peers || {}).forEach(function(peer) { + peers.push([peer, d.peers[peer]]); + }); + + Object.keys(d.groups || {}).forEach(function(group) { + add_group(peers, d.groups[group]); + }); + + return peers; + } + + var div = document.getElementById('mesh-vpn'); + if (!data) { + div.style.display = 'none'; + return; + } + + div.style.display = ''; + var table = document.getElementById('mesh-vpn-peers'); + while (table.lastChild) + table.removeChild(table.lastChild); + + var peers = add_group([], data); + peers.sort(); + + peers.forEach(function (peer) { + var tr = document.createElement('tr'); + + var th = document.createElement('th'); + th.textContent = peer[0]; + tr.appendChild(th); + + var td = document.createElement('td'); + if (peer[1]) + td.textContent = _['connected'] + ' (' + formats.time(peer[1].established) + ')'; + else + td.textContent = _['not connected']; + tr.appendChild(td); + + table.appendChild(tr); + }); + } + + var statisticsElems = document.querySelectorAll('[data-statistics]'); + + add_event_source('/cgi-bin/dyn/statistics', function(data, dataPrev) { + var diff = data.uptime - dataPrev.uptime; + + statisticsElems.forEach(function(elem) { + var stat = elem.getAttribute('data-statistics'); + var format = elem.getAttribute('data-format'); + + var valuePrev = resolve_key(dataPrev, stat); + var value = resolve_key(data, stat); + try { + var text = formats[format](value, valuePrev, diff); + if (text !== undefined) + elem.textContent = text; + } catch (e) { + console.error(e); + } + }); + + try { + update_mesh_vpn(data.mesh_vpn); + } catch (e) { + console.error(e); + } + }) + + function haversine(lat1, lon1, lat2, lon2) { + var rad = Math.PI / 180; + lat1 *= rad; lon1 *= rad; lat2 *= rad; lon2 *= rad; + + var R = 6372.8; // km + var dLat = lat2 - lat1; + var dLon = lon2 - lon1; + var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2); + var c = 2 * Math.asin(Math.sqrt(a)); + return R * c; + } + + var interfaces = {}; + + function Signal(color) { + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + var value = null; + var radius = 1.2; + + function drawPixel(x, y) { + ctx.beginPath(); + ctx.fillStyle = color; + ctx.arc(x, y, radius, 0, 2 * Math.PI, false); + ctx.closePath(); + ctx.fill(); + } + + return { + 'canvas': canvas, + 'highlight': false, + + 'resize': function(w, h) { + var lastImage; + try { + ctx.getImageData(0, 0, w, h); + } catch (e) {} + canvas.width = w; + canvas.height = h; + if (lastImage) + ctx.putImageData(lastImage, 0, 0); + }, + + 'draw': function(x, scale) { + var y = scale(value); + + ctx.clearRect(x, 0, 5, canvas.height) + + if (y) + drawPixel(x, y) + }, + + 'set': function (d) { + value = d; + }, + }; + } + + function SignalGraph() { + var min = -100, max = 0; + var i = 0; + + var signals = []; + + var canvas = document.createElement('canvas'); + canvas.className = 'signalgraph'; + canvas.height = 200; + + var ctx = canvas.getContext('2d'); + + function scaleInverse(n, min, max, height) { + return (min * n + max * (height - n)) / height; + } + + function scale(n, min, max, height) { + return (1 - (n - min) / (max - min)) * height; + } + + function drawGrid() { + var nLines = Math.floor(canvas.height / 40); + ctx.save(); + ctx.lineWidth = 0.5; + ctx.strokeStyle = 'rgba(0, 0, 0, 0.25)'; + ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; + ctx.textAlign = 'end'; + ctx.textBaseline = 'bottom'; + + ctx.beginPath(); + + for (var i = 0; i < nLines; i++) { + var y = canvas.height - i * 40; + ctx.moveTo(0, y - 0.5); + ctx.lineTo(canvas.width, y - 0.5); + var dBm = Math.round(scaleInverse(y, min, max, canvas.height)) + ' dBm'; + + ctx.save(); + ctx.strokeStyle = 'rgba(255, 255, 255, 0.9)'; + ctx.lineWidth = 4; + ctx.miterLimit = 2; + ctx.strokeText(dBm, canvas.width - 5, y - 2.5); + ctx.fillText(dBm, canvas.width - 5, y - 2.5); + ctx.restore(); + } + + ctx.stroke(); + + ctx.strokeStyle = 'rgba(0, 0, 0, 0.83)'; + ctx.lineWidth = 1.5; + ctx.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1); + + ctx.restore(); + } + + function resize() { + canvas.width = canvas.clientWidth; + signals.forEach(function(signal) { + signal.resize(canvas.width, canvas.height); + }); + } + resize(); + + function draw() { + if (canvas.clientWidth === 0) + return; + + if (canvas.width !== canvas.clientWidth) + resize(); + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + var highlight = false; + signals.forEach(function(signal) { + if (signal.highlight) + highlight = true; + }); + + ctx.save(); + signals.forEach(function(signal) { + if (highlight) + ctx.globalAlpha = 0.2; + + if (signal.highlight) + ctx.globalAlpha = 1; + + signal.draw(i, function(value) { + return scale(value, min, max, canvas.height); + }); + ctx.drawImage(signal.canvas, 0, 0); + }); + ctx.restore(); + + ctx.save(); + ctx.beginPath(); + ctx.strokeStyle = 'rgba(255, 180, 0, 0.15)'; + ctx.lineWidth = 5; + ctx.moveTo(i + 2.5, 0); + ctx.lineTo(i + 2.5, canvas.height); + ctx.stroke(); + + drawGrid(); + } + + window.addEventListener('resize', draw); + + var last = 0; + function step(timestamp) { + var delta = timestamp - last; + + if (delta > 40) { + draw(); + i = (i + 1) % canvas.width; + last = timestamp; + }; + + window.requestAnimationFrame(step); + } + + window.requestAnimationFrame(step); + + return { + 'el': canvas, + + 'addSignal': function(signal) { + signals.push(signal); + signal.resize(canvas.width, canvas.height); + }, + + 'removeSignal': function(signal) { + signals.splice(signals.indexOf(signal), 1); + }, + }; + } + + function Neighbour(iface, addr, color, destroy) { + var el = iface.table.insertRow(); + + var tdHostname = el.insertCell(); + + if (iface.wireless) { + var marker = document.createElement("span"); + marker.textContent = "⬤ "; + marker.style.color = color; + tdHostname.appendChild(marker); + } + + var hostname = document.createElement("span"); + hostname.textContent = addr; + tdHostname.appendChild(hostname); + + var tdTQ = el.insertCell(); + tdTQ.textContent = '-'; + + var tdSignal; + var tdDistance; + var tdInactive; + var signal; + + if (iface.wireless) { + tdSignal = el.insertCell(); + tdSignal.textContent = '-'; + tdDistance = el.insertCell(); + tdDistance.textContent = '-'; + tdInactive = el.insertCell(); + tdInactive.textContent = '-'; + + signal = Signal(color); + iface.signalgraph.addSignal(signal); + } + + el.onmouseenter = function () { + el.classList.add("highlight"); + if (signal) + signal.highlight = true; + } + + el.onmouseleave = function () { + el.classList.remove("highlight") + if (signal) + signal.highlight = false; + } + + var timeout; + + function updated() { + if (timeout) + window.clearTimeout(timeout); + + timeout = window.setTimeout(function() { + if (signal) + iface.signalgraph.removeSignal(signal); + + el.parentNode.removeChild(el); + destroy(); + }, 60000); + } + updated(); + + function address_to_groups(addr) { + if (addr.slice(0, 2) == '::') + addr = '0' + addr; + if (addr.slice(-2) == '::') + addr = addr + '0'; + + var parts = addr.split(':'); + var n = parts.length; + var groups = []; + + parts.forEach(function(part, i) { + if (part === '') { + while (n++ <= 8) + groups.push(0); + } else { + if (!/^[a-f0-9]{1,4}$/i.test(part)) + return; + + groups.push(parseInt(part, 16)); + } + }); + + return groups; + } + + function address_to_binary(addr) { + var groups = address_to_groups(addr); + if (!groups) + return; + + var ret = ''; + groups.forEach(function(group) { + ret += ('0000000000000000' + group.toString(2)).slice(-16); + }); + + return ret; + } + + function common_length(a, b) { + var i; + for (i = 0; i < a.length && i < b.length; i++) { + if (a[i] !== b[i]) + break; + } + return i; + } + + function choose_address(addresses) { + var node_bin = address_to_binary(node_address); + + if (!addresses || !addresses[0]) + return; + + addresses = addresses.map(function(addr) { + var addr_bin = address_to_binary(addr); + if (!addr_bin) + return [-1]; + + var common_prefix = 0; + if (node_bin) + common_prefix = common_length(node_bin, addr_bin); + + return [common_prefix, addr_bin, addr]; + }); + + addresses.sort(function(a, b) { + if (a[0] < b[0]) + return 1; + else if (a[0] > b[0]) + return -1; + else if (a[1] < b[1]) + return -1; + else if (a[1] > b[1]) + return 1; + else + return 0; + + }); + + var address = addresses[0][2]; + if (address && !/^fe80:/i.test(address)) + return address; + } + + return { + 'update_nodeinfo': function(nodeinfo) { + var addr = choose_address(nodeinfo.network.addresses); + if (addr) { + if (hostname.nodeName.toLowerCase() === 'span') { + var oldHostname = hostname; + hostname = document.createElement('a'); + oldHostname.parentNode.replaceChild(hostname, oldHostname); + } + + hostname.href = 'http://[' + addr + ']/'; + } + + hostname.textContent = nodeinfo.hostname; + + if (location && nodeinfo.location) { + var distance = haversine( + location.latitude, location.longitude, + nodeinfo.location.latitude, nodeinfo.location.longitude + ); + tdDistance.textContent = Math.round(distance * 1000) + " m" + } + + updated(); + }, + 'update_mesh': function(mesh) { + tdTQ.textContent = Math.round(mesh.tq / 2.55) + ' %'; + + updated(); + }, + 'update_wifi': function(wifi) { + var inactiveLimit = 200; + + tdSignal.textContent = wifi.signal; + tdInactive.textContent = Math.round(wifi.inactive / 1000) + ' s'; + + el.classList.toggle('inactive', wifi.inactive > inactiveLimit); + signal.set(wifi.inactive > inactiveLimit ? null : wifi.signal); + + updated(); + }, + }; + } + + function Interface(el, ifname, wireless) { + + var neighs = {}; + + var signalgraph; + if (wireless) { + signalgraph = SignalGraph(); + el.appendChild(signalgraph.el); + } + + var info = { + 'table': el.firstElementChild, + 'signalgraph': signalgraph, + 'ifname': ifname, + 'wireless': wireless, + }; + + var nodeinfo_running = false; + var want_nodeinfo = {}; + + var graphColors = []; + function get_color() { + if (!graphColors[0]) + graphColors = ["#396AB1", "#DA7C30", "#3E9651", "#CC2529", "#535154", "#6B4C9A", "#922428", "#948B3D"]; + + return graphColors.shift(); + } + + function neigh_addresses(nodeinfo) { + var addrs = []; + + var mesh = nodeinfo.network.mesh; + Object.keys(mesh).forEach(function(meshif) { + var ifaces = mesh[meshif].interfaces; + Object.keys(ifaces).forEach(function(ifaceType) { + ifaces[ifaceType].forEach(function(addr) { + addrs.push(addr); + }); + }); + }); + + return addrs; + } + + function load_nodeinfo() { + if (nodeinfo_running) + return; + + nodeinfo_running = true; + + var source = new EventSource('/cgi-bin/dyn/neighbours-nodeinfo?' + encodeURIComponent(ifname)); + source.addEventListener("neighbour", function(m) { + try { + var data = JSON.parse(m.data); + neigh_addresses(data).forEach(function(addr) { + var neigh = neighs[addr]; + if (neigh) { + delete want_nodeinfo[addr]; + try { + neigh.update_nodeinfo(data); + } catch (e) { + console.error(e); + } + } + }); + } catch (e) { + console.error(e); + } + }, false); + + source.onerror = function() { + source.close(); + nodeinfo_running = false; + + Object.keys(want_nodeinfo).forEach(function (addr) { + if (want_nodeinfo[addr] > 0) { + want_nodeinfo[addr]--; + load_nodeinfo(); + } + }); + } + } + + + function get_neigh(addr) { + var neigh = neighs[addr]; + if (!neigh) { + want_nodeinfo[addr] = 3; + neigh = neighs[addr] = Neighbour(info, addr, get_color(), function() { + delete want_nodeinfo[addr]; + delete neighs[addr]; + }); + load_nodeinfo(); + } + + return neigh; + } + + if (wireless) { + add_event_source('/cgi-bin/dyn/stations?' + encodeURIComponent(ifname), function(data) { + Object.keys(data).forEach(function (addr) { + var wifi = data[addr]; + + get_neigh(addr).update_wifi(wifi); + }); + }); + } + + return { + 'get_neigh': get_neigh, + }; + } + + document.querySelectorAll('[data-interface]').forEach(function(elem) { + var ifname = elem.getAttribute('data-interface'); + var address = elem.getAttribute('data-interface-address'); + var wireless = !!elem.getAttribute('data-interface-wireless'); + + interfaces[ifname] = Interface(elem, ifname, wireless); + }); + + add_event_source('/cgi-bin/dyn/neighbours-batadv', function(data) { + Object.keys(data).forEach(function (addr) { + var mesh = data[addr]; + var iface = interfaces[mesh.ifname]; + if (!iface) + return; + + iface.get_neigh(addr).update_mesh(mesh); + }); + }); +})(); diff --git a/package/gluon-status-page/luasrc/lib/gluon/status-page/controller/status-page.lua b/package/gluon-status-page/luasrc/lib/gluon/status-page/controller/status-page.lua new file mode 100644 index 00000000..18ea1864 --- /dev/null +++ b/package/gluon-status-page/luasrc/lib/gluon/status-page/controller/status-page.lua @@ -0,0 +1,3 @@ +entry({}, call(function(http, renderer) + renderer.render('status-page', nil, 'gluon-status-page') +end)) diff --git a/package/gluon-status-page/luasrc/lib/gluon/status-page/www/cgi-bin/status b/package/gluon-status-page/luasrc/lib/gluon/status-page/www/cgi-bin/status new file mode 100755 index 00000000..7e5079ac --- /dev/null +++ b/package/gluon-status-page/luasrc/lib/gluon/status-page/www/cgi-bin/status @@ -0,0 +1,8 @@ +#!/usr/bin/lua + +require 'gluon.web.cgi' { + base_path = '/lib/gluon/status-page', + + layout_package = 'gluon-status-page', + layout_template = 'layout', -- only used for error pages +} diff --git a/package/gluon-status-page/sass/status-page.scss b/package/gluon-status-page/sass/status-page.scss new file mode 100644 index 00000000..cfaa561f --- /dev/null +++ b/package/gluon-status-page/sass/status-page.scss @@ -0,0 +1,195 @@ +/* + ATTENTION: This file is not compiled when building gluon. + The compiled version is at ../files/lib/gluon/status-page/www/static/status-page.css + + Use sass like this to update it: + + sass --sourcemap=none -C -t compressed sass/status-page.scss files/lib/gluon/status-page/www/static/status-page.css + + When commiting changes to this file make sure to commit the respective + changes to the compiled version within the same commit! +*/ + +html, body, div, span, +h1, h2, h3, +dl, dt, dd, +canvas, header, +table, tr, th, td { + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-size: 100%; + vertical-align: baseline; + background: transparent; +} + +body { + background: rgba(0, 0, 0, 0.12); + font-family: Roboto, Lucida Grande, sans, Arial; + color: rgba(0, 0, 0, 0.87); + font-size: 14px; + line-height: 1; +} + + +a { + color: rgba(220, 0, 103, 0.87); + text-decoration: none; + + margin: 0; + padding: 0; + font-size: 100%; + vertical-align: baseline; + background: transparent; + + &:hover { + text-decoration: underline; + } +} + +h1 { + font-weight: bold; +} + +h2 { + font-size: 16px; + margin-bottom: 16px; + color: rgba(0, 0, 0, 0.54); +} + +h3 { + font-size: 15px; + margin-top: 16px; + margin-bottom: 8px; + color: rgba(0, 0, 0, 0.54); +} + +header { + display: flex; + padding: 0 14px; + background: #dc0067; + color: rgba(255, 255, 255, 0.98); + position: absolute; + top: 0; + width: 100%; + box-sizing: border-box; + height: 20vh; + z-index: -1; + box-shadow: 0px 5px 6px rgba(0, 0, 0, 0.16), 0px 1.5px 3px rgba(0, 0, 0, 0.23); + white-space: nowrap; + + h1 { + font-size: 24px; + margin: 10px 0; + padding: 6px 0; + + text-overflow: ellipsis; + overflow: hidden; + flex: 1; + } +} + +.container { + display: flex; + + max-width: 90vw; + margin: 64px auto 24px auto; + background: rgb(253, 253, 253); + box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.19), 0px 3px 6px rgba(0, 0, 0, 0.23); + + & > .frame { + flex: 1; + border-style: solid; + border-color: rgba(0, 0, 0, 0.12); + + box-sizing: border-box; + padding: 16px; + + & + .frame { + border-width: 0 0 0 1px; + } + } +} + +dt, th { + font-weight: bold; + color: rgba(0, 0, 0, 0.87); +} + +dt { + margin-bottom: 4px; +} + +th, td { + text-align: left; + padding: 4px 16px 4px 0; + + &:last-child { + padding-right: 0; + } +} + +dd, td { + font-weight: normal; + font-size: 0.9em; + color: rgba(0, 0, 0, 0.54); +} + +dd { + margin-bottom: 16px; +} + +table { + border-collapse: collapse; + border-spacing: 0; + + &.datatable { + width: 100%; + + th, td { + font-size: 1em; + white-space: nowrap; + + &:last-child { + width: 100%; + } + } + + tr.inactive { + opacity: 0.33; + } + + tr.highlight { + background: rgba(255, 180, 0, 0.25); + } + } +} + +canvas.signalgraph { + margin-top: 8px; + width: 100%; +} + +@media only screen and (max-width: 1250px) { + .container { + max-width: none; + margin: 56px 0 0; + } + + header { + height: 56px; + z-index: 1; + position: fixed; + } +} + +@media only screen and (max-width: 700px) { + .container { + display: block; + + & > .frame + .frame { + border-width: 1px 0 0 0; + } + } +} diff --git a/package/gluon-status-page/src/build.js b/package/gluon-status-page/src/build.js deleted file mode 100644 index a1b1d703..00000000 --- a/package/gluon-status-page/src/build.js +++ /dev/null @@ -1,10 +0,0 @@ -({ - paths: { - "bacon": "../Bacon" - }, - baseUrl: "js/", - name: "../almond", - include: "main", - optimize: "uglify2", - out: "app.js", -}) diff --git a/package/gluon-status-page/src/css/animation.css b/package/gluon-status-page/src/css/animation.css deleted file mode 100644 index e6edbf3c..00000000 --- a/package/gluon-status-page/src/css/animation.css +++ /dev/null @@ -1,12 +0,0 @@ -/* - Animation example, for spinners -*/ -.animate-spin { - animation: spin 2s linear infinite; - display: inline-block; -} -@keyframes spin { - 100% { - transform: rotate(360deg); - } -} diff --git a/package/gluon-status-page/src/css/font.css b/package/gluon-status-page/src/css/font.css deleted file mode 100644 index c26ee355..00000000 --- a/package/gluon-status-page/src/css/font.css +++ /dev/null @@ -1,53 +0,0 @@ -@font-face { - font-family: 'statuspage'; - src: url(data:application/font-woff;charset=utf-8;base64,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) format('woff'); - font-weight: normal; - font-style: normal; -} - -[class^="icon-"]:before, [class*=" icon-"]:before { - font-family: "statuspage"; - font-style: normal; - font-weight: normal; - speak: none; - - display: inline-block; - text-decoration: inherit; - width: 1em; - margin-right: .2em; - text-align: center; - /* opacity: .8; */ - - /* For safety - reset parent styles, that can break glyph codes*/ - font-variant: normal; - text-transform: none; - - /* fix buttons height, for twitter bootstrap */ - line-height: 1em; - - /* Animation center compensation - margins should be symmetric */ - /* remove if not needed */ - margin-left: .2em; - - /* you can be more comfortable with increased icons size */ - /* font-size: 120%; */ - - /* Uncomment for 3D effect */ - /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ -} - -.icon-menu:before { content: '\e800'; } /* '' */ -.icon-down-dir:before { content: '\e801'; } /* '' */ -.icon-up-dir:before { content: '\e802'; } /* '' */ -.icon-left-dir:before { content: '\e803'; } /* '' */ -.icon-right-dir:before { content: '\e804'; } /* '' */ -.icon-ok:before { content: '\e805'; } /* '' */ -.icon-cancel:before { content: '\e807'; } /* '' */ -.icon-arrows-cw:before { content: '\e808'; } /* '' */ -.icon-ellipsis-vert:before { content: '\e809'; } /* '' */ -.icon-spinner:before { content: '\e80a'; } /* '' */ -.icon-attention:before { content: '\e80b'; } /* '' */ -.icon-sort:before { content: '\e80c'; } /* '' */ -.icon-sort-down:before { content: '\e80d'; } /* '' */ -.icon-sort-up:before { content: '\e80e'; } /* '' */ -.icon-link-ext:before { content: '\e80f'; } /* '' */ diff --git a/package/gluon-status-page/src/css/main.css b/package/gluon-status-page/src/css/main.css deleted file mode 100644 index 0604802e..00000000 --- a/package/gluon-status-page/src/css/main.css +++ /dev/null @@ -1,171 +0,0 @@ -@import "reset.css"; -@import "font.css"; -@import "menu.css"; -@import "animation.css"; - -body { - background: rgba(0, 0, 0, 0.12); - font-family: Roboto, Lucida Grande, sans, Arial; - color: rgba(0, 0, 0, 0.87); - font-size: 14px; -} - - -a { - color: rgba(220, 0, 103, 0.87); - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -header { - display: flex; - padding: 0 14px; - background: #dc0067; - color: rgba(255, 255, 255, 0.98); - position: absolute; - top: 0; - width: 100%; - box-sizing: border-box; - height: 20vh; - z-index: -1; - box-shadow: 0px 5px 6px rgba(0, 0, 0, 0.16), 0px 1.5px 3px rgba(0, 0, 0, 0.23); - white-space: nowrap; -} - -header h1, header .icons { - font-size: 24px; - margin: 10px 0; - padding: 6px 0; -} - -header h1 { - text-overflow: ellipsis; - overflow: hidden; - flex: 1; -} - -header h1:hover { - text-decoration: underline; - cursor: pointer; -} - -h1 { - font-weight: bold; -} - -h2, h3 { - font-size: 16px; - color: rgba(0, 0, 0, 0.54); -} - -h2 { - padding: 16px 16px; -} - -h3 { - padding: 16px 16px 8px; -} - -.container { - max-width: 90vw; - margin: 64px auto 24px auto; - background: rgb(253, 253, 253); - box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.19), 0px 3px 6px rgba(0, 0, 0, 0.23); -} - -.container .frame { - box-sizing: border-box; -} - -.vertical-split { - display: flex; -} - -.vertical-split > .frame { - flex: 1; - border-style: solid; - border-color: rgba(0, 0, 0, 0.12); -} - -.vertical-split > .frame + .frame { - border-width: 0 0 0 1px; -} - -dl, pre { - padding: 0 16px 16px; -} - -table { - margin: 0 16px; -} - -dt, th { - font-weight: bold; - color: rgba(0, 0, 0, 0.87); -} - -dt { - margin-bottom: 4px; -} - -th { - text-align: left; - padding: 4px 16px 4px 0; -} - -dd, td { - font-weight: normal; - font-size: 0.9em; - color: rgba(0, 0, 0, 0.54); -} - -dd { - padding-bottom: 16px; -} - -table.datatable { - width: calc(100% - 32px); -} - -table.datatable td { - font-size: 1em; - padding: 4px 0; -} - -table.datatable tr.inactive { - opacity: 0.33; -} - -table.datatable tr.highlight { - background: rgba(255, 180, 0, 0.25); -} - -div.signalgraph { - margin: 16px; -} - -@media only screen and (max-width: 1250px) { - .container { - max-width: none; - margin: 56px 0 0; - } - - header { - height: 56px; - z-index: 1; - position: fixed; - } -} - -@media only screen and (max-width: 700px) { - .vertical-split { - display: block; - } - - .vertical-split > .frame + .frame { - border-width: 1px 0 0 0; - } -} diff --git a/package/gluon-status-page/src/css/menu.css b/package/gluon-status-page/src/css/menu.css deleted file mode 100644 index b29e4a41..00000000 --- a/package/gluon-status-page/src/css/menu.css +++ /dev/null @@ -1,50 +0,0 @@ -.noscroll { - overflow: hidden; -} - -.menu-background { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - z-index: 10; -} - -.menu { - background: rgba(255, 255, 255, 1); - position: fixed; - z-index: 11; - padding: 8px 0; - box-shadow: 0 1px 6px rgba(0, 0, 0, 0.12), 0 1px 4px rgba(0, 0, 0, 0.24); - overflow-y: auto; - max-height: 80vh; - - transform-origin: top left; - animation: new-menu-animation .08s ease-out forwards; -} - -@keyframes new-menu-animation { - from { - transform: scaleY(0); - } - to { - transform: scaleY(1); - } -} - -.menu li { - cursor: pointer; - display: block; - font-size: 16px; - padding: 16px 32px 16px 16px; - color: rgba(0, 0, 0, 0.87); -} - -.menu li:hover { - background: rgba(0, 0, 0, 0.07); -} - -.menu li:active { - background: rgba(0, 0, 0, 0.07); -} diff --git a/package/gluon-status-page/src/css/reset.css b/package/gluon-status-page/src/css/reset.css deleted file mode 100644 index f6ca1484..00000000 --- a/package/gluon-status-page/src/css/reset.css +++ /dev/null @@ -1,86 +0,0 @@ -/* -html5doctor.com Reset Stylesheet v1.6.1 -Last Updated: 2010-09-17 -Author: Richard Clark - http://richclarkdesign.com -*/ -html, body, div, span, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -abbr, address, cite, code, -del, dfn, em, img, ins, kbd, q, samp, -small, strong, sub, sup, var, -b, i, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, figcaption, figure, -footer, header, hgroup, menu, nav, section, summary, -time, mark, audio, video { - margin:0; - padding:0; - border:0; - outline:0; - font-size:100%; - vertical-align:baseline; - background:transparent; -} -body { - line-height:1; -} -article,aside,details,figcaption,figure, -footer,header,hgroup,menu,nav,section { - display:block; -} -nav ul { - list-style:none; -} -blockquote, q { - quotes:none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content:''; - content:none; -} -a { - margin:0; - padding:0; - font-size:100%; - vertical-align:baseline; - background:transparent; -} -/* change colours to suit your needs */ -ins { - background-color:#ff9; - color:#000; - text-decoration:none; -} -/* change colours to suit your needs */ -mark { - background-color:#ff9; - color:#000; - font-style:italic; - font-weight:bold; -} -del { - text-decoration: line-through; -} -abbr[title], dfn[title] { - border-bottom:1px dotted; - cursor:help; -} -table { - border-collapse:collapse; - border-spacing:0; -} -/* change border colour to suit your needs */ -hr { - display:block; - height:1px; - border:0; - border-top:1px solid #cccccc; - margin:1em 0; - padding:0; -} -input, select { - vertical-align:middle; -} diff --git a/package/gluon-status-page/src/index.html.m4 b/package/gluon-status-page/src/index.html.m4 deleted file mode 100644 index 2726f2e3..00000000 --- a/package/gluon-status-page/src/index.html.m4 +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - diff --git a/package/gluon-status-page/src/js/lib/gui.js b/package/gluon-status-page/src/js/lib/gui.js deleted file mode 100644 index 3d771005..00000000 --- a/package/gluon-status-page/src/js/lib/gui.js +++ /dev/null @@ -1,157 +0,0 @@ -"use strict" -define([ "lib/gui/nodeinfo" - , "lib/gui/statistics" - , "lib/gui/neighbours" - , "lib/gui/menu" - , "lib/streams" - , "lib/neighbourstream" - ], function ( NodeInfo - , Statistics - , Neighbours - , Menu - , Streams - , NeighbourStream - ) { - - function VerticalSplit(parent) { - var el = document.createElement("div") - el.className = "vertical-split" - parent.appendChild(el) - - el.push = function (child) { - var header = document.createElement("h2") - header.appendChild(child.title) - - var div = document.createElement("div") - div.className = "frame" - div.node = child - div.appendChild(header) - - el.appendChild(div) - - child.render(div) - - return function () { - div.node.destroy() - el.removeChild(div) - } - } - - el.clear = function () { - while (el.firstChild) { - el.firstChild.node.destroy() - el.removeChild(el.firstChild) - } - } - - return el - } - - var h1 - - return function (mgmtBus, nodesBus) { - function setTitle(node, state) { - var title = node ? node.hostname : "(not connected)" - - document.title = title - h1.textContent = title - - var icon = document.createElement("i") - icon.className = "icon-down-dir" - - h1.appendChild(icon) - - switch (state) { - case "connect": - stateIcon.className = "icon-arrows-cw animate-spin" - break - case "fail": - stateIcon.className = "icon-attention" - break - default: - stateIcon.className = "" - break - } - } - - var nodes = [] - - function nodeMenu() { - var myNodes = nodes.slice() - - myNodes.sort(function (a, b) { - a = a.hostname - b = b.hostname - return (a < b) ? -1 : (a > b) - }) - - var menu = myNodes.map(function (d) { - return [d.hostname, function () { - mgmtBus.pushEvent("goto", d) - }] - }) - - new Menu(menu).apply(this) - } - - var header = document.createElement("header") - h1 = document.createElement("h1") - header.appendChild(h1) - - h1.onclick = nodeMenu - - var icons = document.createElement("p") - icons.className = "icons" - header.appendChild(icons) - - var stateIcon = document.createElement("i") - icons.appendChild(stateIcon) - - document.body.appendChild(header) - - var container = document.createElement("div") - container.className = "container" - - document.body.appendChild(container) - - setTitle() - - var content = new VerticalSplit(container) - - function nodeChanged(nodeInfo) { - setTitle(nodeInfo, "connect") - - content.clear() - content.push(new NodeInfo(nodeInfo)) - } - - function nodeNotArrived(nodeInfo) { - setTitle(nodeInfo, "fail") - } - - function nodeArrived(nodeInfo, ip) { - setTitle(nodeInfo) - - var neighbourStream = new NeighbourStream(mgmtBus, nodesBus, ip) - var statisticsStream = new Streams.Statistics(ip) - - content.push(new Statistics(statisticsStream)) - content.push(new Neighbours(nodeInfo, neighbourStream, mgmtBus)) - } - - function newNodes(d) { - nodes = [] - for (var nodeId in d) - nodes.push(d[nodeId]) - } - - mgmtBus.onEvent({ "goto": nodeChanged - , "arrived": nodeArrived - , "gotoFailed": nodeNotArrived - }) - - nodesBus.map(".nodes").onValue(newNodes) - - return this - } -}) diff --git a/package/gluon-status-page/src/js/lib/gui/menu.js b/package/gluon-status-page/src/js/lib/gui/menu.js deleted file mode 100644 index 712f2d0f..00000000 --- a/package/gluon-status-page/src/js/lib/gui/menu.js +++ /dev/null @@ -1,39 +0,0 @@ -"use strict" -define(function () { - return function (menu) { - return function () { - var background = document.createElement("div") - background.className = "menu-background" - document.body.appendChild(background) - document.body.classList.add("noscroll") - - var offset = this.getBoundingClientRect() - var container = document.createElement("ul") - container.className = "menu" - container.style.top = offset.top + "px" - container.style.left = offset.left + "px" - - background.onclick = destroy - - menu.forEach(function (item) { - var li = document.createElement("li") - li.textContent = item[0] - li.action = item[1] - li.onclick = function () { - destroy() - this.action() - } - - container.appendChild(li) - }) - - document.body.appendChild(container) - - function destroy() { - document.body.classList.remove("noscroll") - document.body.removeChild(background) - document.body.removeChild(container) - } - } - } -}) diff --git a/package/gluon-status-page/src/js/lib/gui/neighbours.js b/package/gluon-status-page/src/js/lib/gui/neighbours.js deleted file mode 100644 index f6ce9e42..00000000 --- a/package/gluon-status-page/src/js/lib/gui/neighbours.js +++ /dev/null @@ -1,274 +0,0 @@ -"use strict" -define([ "lib/helper", "lib/gui/signalgraph", "lib/gui/signal"], -function (Helper, SignalGraph, Signal) { - - var graphColors = ["#396AB1", "#DA7C30", "#3E9651", "#CC2529", "#535154", "#6B4C9A", "#922428", "#948B3D"] - //graphColors = ["#7293CB", "#E1974C", "#84BA5B", "#D35E60", "#808585", "#9067A7", "#AB6857", "#CCC210"]; - - var inactiveTime = 200 - - function SignalEntry(graph, color, stream) { - var signal = new Signal(color) - var remove = graph.add(signal) - - var unsubscribe = stream.onValue(update) - - this.destroy = function () { - unsubscribe() - remove() - } - - this.getSignal = function () { - return signal - } - - return this - - function update(d) { - if ("wifi" in d) - signal.set(d.wifi.inactive > inactiveTime ? null : d.wifi.signal) - } - } - - function TableEntry(parent, nodeInfo, color, stream, mgmtBus, signal) { - var el = parent.insertRow() - - var tdHostname = el.insertCell() - var tdTQ = el.insertCell() - var tdSignal = el.insertCell() - var tdDistance = el.insertCell() - var tdInactive = el.insertCell() - - var marker = document.createElement("span") - marker.textContent = "⬤ " - marker.style.color = color - tdHostname.appendChild(marker) - - var hostname = document.createElement("span") - tdHostname.appendChild(hostname) - - var infoSet = false - var unsubscribe = stream.onValue(update) - - el.onmouseenter = function () { - el.classList.add("highlight") - signal.setHighlight(true) - } - - el.onmouseleave = function () { - el.classList.remove("highlight") - signal.setHighlight(false) - } - - el.destroy = function () { - unsubscribe() - parent.tBodies[0].removeChild(el) - } - - return el - - function update(d) { - if ("wifi" in d) { - var signal = d.wifi.signal - var inactive = d.wifi.inactive - - el.classList.toggle("inactive", inactive > inactiveTime) - - tdSignal.textContent = signal - tdInactive.textContent = Math.round(inactive / 1000) + " s" - } - - if ("batadv" in d) - tdTQ.textContent = Math.round(d.batadv.tq / 2.55) + " %" - else - tdTQ.textContent = "‒" - - if (infoSet) - return - - if ("nodeInfo" in d) { - infoSet = true - - var link = document.createElement("a") - link.textContent = d.nodeInfo.hostname - link.href = "#" - link.nodeInfo = d.nodeInfo - link.onclick = function () { - mgmtBus.pushEvent("goto", this.nodeInfo) - return false - } - - while (hostname.firstChild) - hostname.removeChild(hostname.firstChild) - - hostname.appendChild(link) - - try { - var distance = Helper.haversine(nodeInfo.location.latitude, nodeInfo.location.longitude, - d.nodeInfo.location.latitude, d.nodeInfo.location.longitude) - - tdDistance.textContent = Math.round(distance * 1000) + " m" - } catch (e) { - tdDistance.textContent = "‒" - } - } else - hostname.textContent = d.id - } - } - - function Interface(parent, nodeInfo, iface, stream, mgmtBus) { - var colors = graphColors.slice(0) - - var el = document.createElement("div") - el.ifname = iface - parent.appendChild(el) - - var h = document.createElement("h3") - h.textContent = iface - el.appendChild(h) - - var table = document.createElement("table") - var tr = table.insertRow() - table.classList.add("datatable") - - var th = document.createElement("th") - th.textContent = Helper._("Node") - tr.appendChild(th) - - th = document.createElement("th") - th.textContent = "TQ" - tr.appendChild(th) - - th = document.createElement("th") - th.textContent = "dBm" - tr.appendChild(th) - - th = document.createElement("th") - th.textContent = Helper._("Distance") - tr.appendChild(th) - - th = document.createElement("th") - th.textContent = Helper._("Inactive") - tr.appendChild(th) - - el.appendChild(table) - - var wrapper = document.createElement("div") - wrapper.className = "signalgraph" - el.appendChild(wrapper) - - var canvas = document.createElement("canvas") - canvas.className = "signal-history" - canvas.height = 200 - wrapper.appendChild(canvas) - - var graph = new SignalGraph(canvas, -100, 0, true) - - var stopStream = stream.skipDuplicates(sameKeys).onValue(update) - - var managedNeighbours = {} - - function update(d) { - var notUpdated = new Set() - var id - - for (id in managedNeighbours) - notUpdated.add(id) - - for (id in d) { - if (!(id in managedNeighbours)) { - var neighbourStream = stream.map("." + id).filter( function (d) { return d !== undefined }) - var color = colors.shift() - var signal = new SignalEntry(graph, color, neighbourStream) - managedNeighbours[id] = { views: [ signal, - new TableEntry(table, nodeInfo, color, neighbourStream, mgmtBus, signal.getSignal()) - ], - color: color - } - } - - notUpdated.delete(id) - } - - notUpdated.forEach(function (id) { - managedNeighbours[id].views.forEach( function (d) { d.destroy() }) - colors.push(managedNeighbours[id].color) - delete managedNeighbours[id] - }) - } - - - el.destroy = function () { - stopStream() - - for (var id in managedNeighbours) - managedNeighbours[id].views.forEach( function (d) { d.destroy() }) - - el.removeChild(h) - el.removeChild(wrapper) - el.removeChild(table) - } - } - - function sameKeys(a, b) { - a = Object.keys(a).sort() - b = Object.keys(b).sort() - - return !(a < b || a > b) - } - - function getter(k) { - return function(obj) { - return obj[k] - } - } - - return function (nodeInfo, stream, mgmtBus) { - var stopStream, div - - function render(el) { - div = document.createElement("div") - el.appendChild(div) - - stopStream = stream.skipDuplicates(sameKeys).onValue(update) - - function update(d) { - var have = {} - var remove = [] - if (div.hasChildNodes()) { - var children = div.childNodes - for (var i = 0; i < children.length; i++) { - var a = children[i] - if (a.ifname in d) - have[a.ifname] = true - else { - a.destroy() - remove.push(a) - } - } - } - - remove.forEach(function (d) { div.removeChild(d) }) - - for (var k in d) { - if (!(k in have)) - new Interface(div, nodeInfo, k, stream.map(getter(k)), mgmtBus) - } - } - } - - function destroy() { - stopStream() - - while (div.firstChild) { - div.firstChild.destroy() - div.removeChild(div.firstChild) - } - } - - return { title: document.createTextNode(Helper._("Neighbors")) - , render: render - , destroy: destroy - } - } -}) diff --git a/package/gluon-status-page/src/js/lib/gui/nodeinfo.js b/package/gluon-status-page/src/js/lib/gui/nodeinfo.js deleted file mode 100644 index 4fc08123..00000000 --- a/package/gluon-status-page/src/js/lib/gui/nodeinfo.js +++ /dev/null @@ -1,65 +0,0 @@ -"use strict" -define(["lib/helper"], function (Helper) { - return function (nodeInfo) { - var el = document.createElement("div") - - update(nodeInfo) - - function dlEntry(dl, dict, key, prettyName, transform) { - var v = Helper.dictGet(dict, key.split(".")) - - if (v === null) - return - - if (transform) { - v = transform(v) - } - - var dt = document.createElement("dt") - var dd = document.createElement("dd") - - dt.textContent = prettyName - if (v instanceof Array) { - var tn = v.map(function (d) { return document.createTextNode(d) }) - tn.forEach(function (node) { - if (dd.hasChildNodes()) - dd.appendChild(document.createElement("br")) - - dd.appendChild(node) - }) - } else - dd.textContent = v - - dl.appendChild(dt) - dl.appendChild(dd) - } - - function enabledDisabled(v) { - if (v) { - return Helper._("enabled"); - } - return Helper._("disabled"); - } - - function update(nodeInfo) { - var list = document.createElement("dl") - - dlEntry(list, nodeInfo, "hostname", Helper._("Node name")) - dlEntry(list, nodeInfo, "owner.contact", Helper._("Contact")) - dlEntry(list, nodeInfo, "hardware.model", Helper._("Model")) - dlEntry(list, nodeInfo, "network.mac", Helper._("Primary MAC")) - dlEntry(list, nodeInfo, "network.addresses", Helper._("IP Address")) - dlEntry(list, nodeInfo, "software.firmware.release", Helper._("Firmware")) - dlEntry(list, nodeInfo, "software.fastd.enabled", Helper._("Mesh VPN"), enabledDisabled) - dlEntry(list, nodeInfo, "software.autoupdater.enabled", Helper._("Automatic updates"), enabledDisabled) - dlEntry(list, nodeInfo, "software.autoupdater.branch", Helper._("Branch")) - - el.appendChild(list) - } - - return { title: document.createTextNode(Helper._("Overview")) - , render: function (d) { d.appendChild(el) } - , destroy: function () {} - } - } -}) diff --git a/package/gluon-status-page/src/js/lib/gui/signal.js b/package/gluon-status-page/src/js/lib/gui/signal.js deleted file mode 100644 index d91c63fb..00000000 --- a/package/gluon-status-page/src/js/lib/gui/signal.js +++ /dev/null @@ -1,48 +0,0 @@ -"use strict" -define(function () { - return function (color) { - var canvas = document.createElement("canvas") - var ctx = canvas.getContext("2d") - var v = null - var radius = 1.2 - var highlight = false - - function drawPixel(x, y) { - ctx.beginPath() - ctx.fillStyle = color - ctx.arc(x, y, radius, 0, Math.PI * 2, false) - ctx.closePath() - ctx.fill() - } - - this.resize = function (w, h) { - canvas.width = w - canvas.height = h - } - - this.draw = function (x, scale) { - var y = scale(v) - - ctx.clearRect(x, 0, 5, canvas.height) - - if (y) - drawPixel(x, y) - } - - this.canvas = canvas - - this.set = function (d) { - v = d - } - - this.setHighlight = function (d) { - highlight = d - } - - this.getHighlight = function () { - return highlight - } - - return this - } -}) diff --git a/package/gluon-status-page/src/js/lib/gui/signalgraph.js b/package/gluon-status-page/src/js/lib/gui/signalgraph.js deleted file mode 100644 index 231ce628..00000000 --- a/package/gluon-status-page/src/js/lib/gui/signalgraph.js +++ /dev/null @@ -1,137 +0,0 @@ -"use strict" -define(function () { - return function (canvas, min, max) { - var i = 0 - var graphWidth - var last = 0 - - var signals = [] - - var ctx = canvas.getContext("2d") - - resize() - - window.addEventListener("resize", resize, false) - window.requestAnimationFrame(step) - - function step(timestamp) { - var delta = timestamp - last - - if (delta > 40) { - draw() - last = timestamp - } - - window.requestAnimationFrame(step) - } - - function drawGrid() { - var gridctx = ctx - var nLines = Math.floor(canvas.height / 40) - gridctx.save() - gridctx.lineWidth = 0.5 - gridctx.strokeStyle = "rgba(0, 0, 0, 0.25)" - gridctx.fillStyle = "rgba(0, 0, 0, 0.5)" - gridctx.textAlign = "end" - gridctx.textBaseline = "bottom" - - gridctx.beginPath() - - for (var i = 0; i < nLines; i++) { - var y = canvas.height - i * 40 - gridctx.moveTo(0, y - 0.5) - gridctx.lineTo(canvas.width, y - 0.5) - var dBm = Math.round(scaleInverse(y, min, max, canvas.height)) + " dBm" - - gridctx.save() - gridctx.strokeStyle = "rgba(255, 255, 255, 0.9)" - gridctx.lineWidth = 4 - gridctx.miterLimit = 2 - gridctx.strokeText(dBm, canvas.width - 5, y - 2.5) - gridctx.fillText(dBm, canvas.width - 5, y - 2.5) - gridctx.restore() - } - - gridctx.stroke() - - gridctx.strokeStyle = "rgba(0, 0, 0, 0.83)" - gridctx.lineWidth = 1.5 - gridctx.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1) - - gridctx.restore() - } - - function draw() { - var anyHighlight = signals.some( function (d) { return d.getHighlight() }) - - signals.forEach( function (d) { - d.draw(i, function (v) { - return scale(v, min, max, canvas.height) - }) - }) - - ctx.clearRect(0, 0, canvas.width, canvas.height) - - ctx.save() - - signals.forEach( function (d) { - if (anyHighlight) - ctx.globalAlpha = 0.1 - - if (d.getHighlight()) - ctx.globalAlpha = 1 - - ctx.drawImage(d.canvas, 0, 0) - }) - - ctx.restore() - - ctx.save() - ctx.beginPath() - ctx.strokeStyle = "rgba(255, 180, 0, 0.15)" - ctx.lineWidth = 5 - ctx.moveTo(i + 2.5, 0) - ctx.lineTo(i + 2.5, canvas.height) - ctx.stroke() - - drawGrid() - - i = (i + 1) % graphWidth - } - - function scaleInverse(n, min, max, height) { - return (min * n + max * height - max * n) / height - } - - function scale(n, min, max, height) { - return (1 - (n - min) / (max - min)) * height - } - - function resize() { - var newWidth = canvas.parentNode.clientWidth - - if (newWidth === 0 || newWidth === canvas.width) - return - - var lastImage = ctx.getImageData(0, 0, newWidth, canvas.height) - canvas.width = newWidth - graphWidth = canvas.width - ctx.putImageData(lastImage, 0, 0) - - signals.forEach( function (d) { - d.resize(canvas.width, canvas.height) - }) - } - - this.add = function (d) { - signals.push(d) - d.resize(canvas.width, canvas.height) - - return function () { - signals = signals.filter( function (e) { return e !== d } ) - } - } - - return this - } -}) diff --git a/package/gluon-status-page/src/js/lib/gui/statistics.js b/package/gluon-status-page/src/js/lib/gui/statistics.js deleted file mode 100644 index 2f3e6e27..00000000 --- a/package/gluon-status-page/src/js/lib/gui/statistics.js +++ /dev/null @@ -1,282 +0,0 @@ -"use strict" -define(["lib/helper"], function (Helper) { - function streamElement(type, stream) { - var el = document.createElement(type) - el.destroy = stream.onValue(update) - - function update(d) { - el.textContent = d - } - - return el - } - - function streamNode(stream) { - var el = document.createTextNode("") - el.destroy = stream.onValue(update) - - function update(d) { - el.textContent = d - } - - return el - } - - function mkRow(table, label, stream, sorted) { - - var i = -1 - - if (sorted) { - for (i = 0; i < table.rows.length; i++) { - if (label < table.rows[i].firstChild.textContent) - break - } - } - - var tr = table.insertRow(i) - var th = document.createElement("th") - var td = streamElement("td", stream) - th.textContent = label - tr.appendChild(th) - tr.appendChild(td) - - tr.destroy = function () { - td.destroy() - table.tBodies[0].removeChild(tr) - } - - return tr - } - - function mkTrafficRow(table, children, label, stream, selector) { - var tr = table.insertRow() - var th = document.createElement("th") - th.textContent = label - tr.appendChild(th) - var td = tr.insertCell() - - var traffic = stream.slidingWindow(2, 2) - var pkts = streamNode(traffic.map(deltaUptime(selector + ".packets")).map(prettyPackets)) - var bw = streamNode(traffic.map(deltaUptime(selector + ".bytes")).map(prettyBits)) - var bytes = streamNode(stream.map(selector).map(".bytes").map(prettyBytes)) - - td.appendChild(pkts) - td.appendChild(document.createElement("br")) - td.appendChild(bw) - td.appendChild(document.createElement("br")) - td.appendChild(bytes) - - children.push(pkts) - children.push(bw) - children.push(bytes) - } - - function mkMeshVPN(el, stream) { - var children = {} - var init = false - var h = document.createElement("h3") - h.textContent = "Mesh-VPN" - - var table = document.createElement("table") - - var unsubscribe = stream.onValue( function (d) { - function addPeer(peer, path) { - return { peer: peer, path: path } - } - - function addPeers(d, path) { - if (!("peers" in d)) - return [] - - var peers = [] - - for (var peer in d.peers) - peers.push(addPeer(peer, path + ".peers." + peer)) - - return peers - } - - function addGroup(d, path) { - var peers = [] - - peers = peers.concat(addPeers(d, path)) - - if ("groups" in d) - for (var group in d.groups) - peers = peers.concat(addGroup(d.groups[group], path + ".groups." + group)) - - return peers - } - - if (d === undefined) - clear() - - else { - if (!init) { - init = true - el.appendChild(h) - el.appendChild(table) - } - - var peers = addGroup(d, "") - var paths = new Set(peers.map(function (d) { return d.path } )) - - for (var path in children) - if (!paths.has(path)) { - children[path].destroy() - delete children[path] - } - - peers.forEach( function (peer) { - if (!(peer.path in children)) - children[peer.path] = mkRow(table, peer.peer, - stream.startWith(d) - .map(peer.path) - .filter(function (d) { return d !== undefined }) - .map(prettyPeer), true) - }) - } - }) - - function clear() { - if (init) { - init = false - el.removeChild(h) - el.removeChild(table) - } - - for (var peer in children) - children[peer].destroy() - - children = {} - } - - function destroy() { - unsubscribe() - clear() - } - - return { destroy: destroy } - } - - function deltaUptime(selector) { - return function (d) { - var deltaTime = d[1].uptime - d[0].uptime - var d0 = Helper.dictGet(d[0], selector.split(".").splice(1)) - var d1 = Helper.dictGet(d[1], selector.split(".").splice(1)) - - return (d1 - d0) / deltaTime - } - } - - function prettyPeer(d) { - if (d === null) - return Helper._("not connected") - else - return Helper._("connected") + " (" + prettyUptime(d.established) + ")" - } - - function prettyPackets(d) { - var v = Helper.formatNumberFixed(d, 0) - return v + " "+ Helper._("Packets/s") - } - - function prettyPrefix(prefixes, step, d) { - var prefix = 0 - - while (d > step && prefix < prefixes.length - 1) { - d /= step - prefix++ - } - - d = Helper.formatNumber(d, 3) - return d + " " + prefixes[prefix] - } - - function prettySize(d) { - return prettyPrefix([ "", "k", "M", "G", "T" ], 1024, d) - } - - function prettyBits(d) { - return prettySize(d * 8) + "bps" - } - - function prettyBytes(d) { - return prettySize(d) + "B" - } - - function prettyUptime(seconds) { - var minutes = Math.round(seconds / 60) - - var days = Math.floor(minutes / 1440) - var hours = Math.floor((minutes % 1440) / 60) - minutes = Math.floor(minutes % 60) - - var out = "" - - if (days === 1) - out += "1 " + Helper._("Day") + ", " - else if (days > 1) - out += days + " " + Helper._("Days") + ", " - - out += hours + ":" - - if (minutes < 10) - out += "0" - - out += minutes - - return out - } - - function prettyNVRAM(usage) { - return Helper.formatNumber(usage * 100, 3) + "% " + Helper._("used") - } - - function prettyLoad(load) { - return Helper.formatNumberFixed(load, 2) - } - - function prettyRAM(memory) { - var usage = 1 - (memory.free + memory.buffers + memory.cached) / memory.total - return prettyNVRAM(usage) - } - - return function (stream) { - var children = [] - var el = document.createElement("div") - var table = document.createElement("table") - - children.push(mkRow(table, Helper._("Uptime"), stream.map(".uptime").map(prettyUptime))) - children.push(mkRow(table, Helper._("Load average"), stream.map(".loadavg").map(prettyLoad))) - children.push(mkRow(table, "RAM", stream.map(".memory").map(prettyRAM))) - children.push(mkRow(table, "NVRAM", stream.map(".rootfs_usage").map(prettyNVRAM))) - children.push(mkRow(table, Helper._("Gateway"), stream.map(".gateway"))) - children.push(mkRow(table, Helper._("Clients"), stream.map(".clients.total"))) - - el.appendChild(table) - - var h = document.createElement("h3") - h.textContent = Helper._("Traffic") - el.appendChild(h) - - table = document.createElement("table") - - mkTrafficRow(table, children, Helper._("Transmitted"), stream, ".traffic.tx") - mkTrafficRow(table, children, Helper._("Received"), stream, ".traffic.rx") - mkTrafficRow(table, children, Helper._("Forwarded"), stream, ".traffic.forward") - - el.appendChild(table) - - children.push(mkMeshVPN(el, stream.map(".mesh_vpn"))) - - function destroy() { - children.forEach(function (d) {d.destroy()}) - } - - return { title: document.createTextNode(Helper._("Statistic")) - , render: function (d) { d.appendChild(el) } - , destroy: destroy - } - } -}) diff --git a/package/gluon-status-page/src/js/lib/helper.js b/package/gluon-status-page/src/js/lib/helper.js deleted file mode 100644 index 75bace13..00000000 --- a/package/gluon-status-page/src/js/lib/helper.js +++ /dev/null @@ -1,170 +0,0 @@ -"use strict" -define([ "bacon" ], function (Bacon) { - function get(url) { - return Bacon.fromBinder(function(sink) { - var req = new XMLHttpRequest() - req.open("GET", url) - - req.onload = function() { - if (req.status === 200) - sink(new Bacon.Next(req.response)) - else - sink(new Bacon.Error(req.statusText)) - sink(new Bacon.End()) - } - - req.onerror = function() { - sink(new Bacon.Error("network error")) - sink(new Bacon.End()) - } - - req.send() - - return function () {} - }) - } - - function getJSON(url) { - return get(url).map(JSON.parse) - } - - function buildUrl(ip, object, param) { - var url = "http://[" + ip + "]/cgi-bin/" + object - if (param) url += "?" + param - - return url - } - - function request(ip, object, param) { - return getJSON(buildUrl(ip, object, param)) - } - - function dictGet(dict, key) { - var k = key.shift() - - if (!(k in dict)) - return null - - if (key.length === 0) - return dict[k] - - return dictGet(dict[k], key) - } - - function localizeNumber(d) { - var sep = ',' - return d.replace('.', sep) - } - - function formatNumberFixed(d, digits) { - return localizeNumber(d.toFixed(digits)) - } - - function formatNumber(d, digits) { - digits-- - - for (var v = d; v >= 10 && digits > 0; v /= 10) - digits-- - - // avoid toPrecision as it might produce strings in exponential notation - return formatNumberFixed(d, digits) - } - - function haversine() { - var radians = Array.prototype.map.call(arguments, function(deg) { return deg / 180.0 * Math.PI }) - var lat1 = radians[0], lon1 = radians[1], lat2 = radians[2], lon2 = radians[3] - var R = 6372.8 // km - var dLat = lat2 - lat1 - var dLon = lon2 - lon1 - var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2) - var c = 2 * Math.asin(Math.sqrt(a)) - return R * c - } - - function _(s) { - var i, lang, langs, dict = { - "de": { - "Node": "Knoten", - "Distance": "Entfernung", - "Inactive": "Inaktiv", - "Node name": "Knotenname", - "Contact": "Kontakt", - "Model": "Modell", - "Primary MAC": "Primäre MAC", - "IP Address": "IP-Adresse", - "Automatic updates": "Automatische Updates", - "Overview": "Übersicht", - "used": "belegt", - "Uptime": "Laufzeit", - "Load average": "Systemlast", - "Transmitted": "Gesendet", - "Received": "Empfangen", - "Forwarded": "Weitergeleitet", - "Day": "Tag", - "Days": "Tage", - "connected": "verbunden", - "not connected": "nicht verbunden", - "Packets/s": "Pakete/s", - "Statistic": "Statistik", - "Neighbors": "Nachbarknoten", - "Mesh VPN": "Mesh-VPN", - "enabled": "aktiviert", - "disabled": "deaktiviert" - }, - "ru": { - "Node": "Узел", - "Distance": "Дальность", - "Inactive": "Не активен", - "Node name": "Имя узла", - "Contact": "Контакт", - "Model": "Модель", - "Primary MAC": "Основной MAC", - "IP Address": "IP Адрес", - "Automatic updates": "Автоматические обновления", - "Overview": "Обзор", - "used": "используется", - "Uptime": "Время работы", - "Load average": "Загрузка системы", - "Gateway": "Шлюз", - "Clients": "Клиенты", - "Transmitted": "Передано", - "Received": "Получено", - "Forwarded": "Переправленно", - "Day": "День", - "Days": "Дней", - "connected": "подключено", - "not connected": "не подключено", - "Packets/s": "Пакетов/c", - "Statistic": "Статистика", - "Traffic": "Трафик", - "Neighbors": "Соседи", - "Firmware": "Прошивка", - "Branch": "Ветка" - } - } - if (navigator.languages) - langs = navigator.languages - else if (navigator.language) - langs = [navigator.language] - else - langs = [] - for (i=0; i 0) - return [undefined, [new Bacon.Error(acc), ev]] - else if (ev.hasValue()) - return [[], [ev, new Bacon.End()]] - }) - - race.onValue(onEpoch(gotoEpoch, function (d) { - mgmtBus.pushEvent("arrived", [nodeInfo, d]) - })) - - race.onError(onEpoch(gotoEpoch, function () { - mgmtBus.pushEvent("gotoFailed", nodeInfo) - })) - } - - function scanNodeInfo(a, nodeInfo) { - a.nodes[nodeInfo.node_id] = nodeInfo - - var mesh = Helper.dictGet(nodeInfo, ["network", "mesh"]) - - if (mesh) - for (var m in mesh) - for (var ifname in mesh[m].interfaces) - mesh[m].interfaces[ifname].forEach( function (d) { - a.macs[d] = nodeInfo - }) - - return a - } - - var lsavailable = false - try { - localStorage.setItem("t", "t") - localStorage.removeItem("t") - lsavailable = true - } catch(e) { - lsavailable = false - } - - - if ( lsavailable && localStorage.nodes) - JSON.parse(localStorage.nodes).forEach(nodesBusIn.push) - - nodesBus.map(".nodes").onValue(function (nodes) { - var out = [] - - for (var k in nodes) - out.push(nodes[k]) - - if (lsavailable) - localStorage.nodes = JSON.stringify(out) - }) - - var bootstrap = Helper.getJSON(bootstrapUrl) - - bootstrap.onError(function () { - console.log("FIXME bootstrapping failed") - }) - - bootstrap.onValue(function (d) { - mgmtBus.pushEvent("nodeinfo", d) - mgmtBus.pushEvent("goto", d) - }) -})