Unverified Commit 88906f23 authored by Matthias Schiffer's avatar Matthias Schiffer
Browse files

gluon-status-page: reimplement based on gluon-web

This new status page is significantly smaller than the old one. It always
loads its resources from the same host as the page itself, not requiring
cross-origin requests anymore.

It also uses the common i18n infrastructure of gluon-web.

Fixes #914
parent b1aa5390
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))
<%-
http:prepare_content("application/xhtml+xml")
-%>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title><%:Error%></title>
<link rel="stylesheet" href="/static/status-page.css" type="text/css" />
</head>
<body>
<header>
<h1><%:Error%></h1>
</header>
<div class="container">
<div class="frame">
<% renderer.render(content, scope, pkg) %>
</div>
</div>
</body>
</html>
<%-
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('<span data-statistics="%s" data-format="%s"></span>', pcdata(key), pcdata(format or 'id'))
end
local function statisticsTraffic(key)
return string.format('%s<br />%s<br />%s',
statistics(key .. '/packets', 'packetsDiff'),
statistics(key .. '/bytes', 'bytesDiff'),
statistics(key .. '/bytes', 'bytes')
)
end
http:prepare_content("application/xhtml+xml")
-%>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title><%| nodeinfo.hostname %> - <%:Status%></title>
<link rel="stylesheet" href="/static/status-page.css" type="text/css" />
</head>
<body data-node-address="<%| http:getenv('SERVER_ADDR') %>"<%= attr('data-translations', translations) .. attr('data-node-location', nodeinfo.location) %>>
<header>
<h1><%| nodeinfo.hostname %></h1>
</header>
<div class="container">
<div class="frame">
<h2><%:Overview%></h2>
<dl>
<dt><%:Node name%></dt><dd><%| nodeinfo.hostname %></dd>
<dt><%:Model%></dt><dd><%| nodeinfo.hardware.model %></dd>
<dt><%:Primary MAC address%></dt><dd><%| nodeinfo.network.mac %></dd>
<dt><%:IP address%></dt><dd><%= pcdata(table.concat(sorted(nodeinfo.network.addresses), '\n')):gsub('\n', '<br />') %></dd>
<dt><%:Firmware%></dt><dd><%| nodeinfo.software.firmware.release %></dd>
<% if nodeinfo.software.fastd then -%>
<dt><%:Mesh VPN%></dt><dd><%| enabled(nodeinfo.software.fastd.enabled) %></dd>
<%- end %>
<% if nodeinfo.software.autoupdater then -%>
<dt><%:Automatic updates%></dt><dd><%| enabled(nodeinfo.software.autoupdater.enabled) %><%|
nodeinfo.software.autoupdater.enabled and
string.format(' (%s)', nodeinfo.software.autoupdater.branch)
%></dd>
<%- end %>
</dl>
</div>
<div class="frame">
<h2><%:Monitoring%></h2>
<table>
<tr><th><%:Uptime%></th><td><%= statistics('uptime', 'time') %></td></tr>
<tr><th><%:Load average%></th><td><%= statistics('loadavg', 'decimal') %></td></tr>
<tr><th><%:RAM%></th><td><%= statistics('memory', 'memory') %></td></tr>
<tr><th><%:Filesystem%></th><td><%= statistics('rootfs_usage', 'percent') %></td></tr>
<tr><th><%:Gateway%></th><td><%= statistics('gateway') %></td></tr>
<tr><th><%:Clients%></th><td><%= statistics('clients/total') %></td></tr>
</table>
<h3><%:Traffic%></h3>
<table>
<tr><th><%:Transmitted%></th><td><%= statisticsTraffic('traffic/tx') %></td></tr>
<tr><th><%:Received%></th><td><%= statisticsTraffic('traffic/rx') %></td></tr>
<tr><th><%:Forwarded%></th><td><%= statisticsTraffic('traffic/forward') %></td></tr>
</table>
<div id="mesh-vpn" style="display: none">
<h3><%:Mesh VPN%></h3>
<table id="mesh-vpn-peers">
</table>
</div>
</div>
<div class="frame">
<h2><%:Neighbors%></h2>
<%
for _, iface in ipairs(interfaces) do
local wireless = is_wireless(iface)
local address = fs.readfile('/sys/class/net/' .. iface .. '/address')
if address then
%>
<h3><%| iface %></h3>
<div data-interface="<%| iface %>" data-interface-address="<%| util.trim(address) %>"<%= attr('data-interface-wireless', wireless) %>>
<table class="datatable">
<tr>
<th><%:Node%></th>
<th>TQ</th>
<% if wireless then %>
<th>dBm</th>
<th><%:Distance%></th>
<th><%:Last seen%></th>
<% end %>
</tr>
</table>
</div>
<%
end
end
%>
</div>
</div>
<script src="/static/status-page.js"></script>
</body>
</html>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="refresh" content="0; URL=/cgi-bin/status" />
</head>
<body>
</body>
</html>
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}}
"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&&r<t.length-1;)i/=e,r++;return(i=n(i,3))+" "+t[r]}(["","K","M","G","T"],1024,t)}String.prototype.sprintf=function(){var t=0,e=arguments;return this.replace(/%s/g,function(){return e[t++]})};var r={id:function(t){return t},decimal:function(t){return e(t,2)},percent:function(e){return t["%s used"].sprintf(n(100*e,3)+"%")},memory:function(t){var e=1-(t.free+t.buffers+t.cached)/t.total;return r.percent(e)},time:function(e){var n=Math.round(e/60),i=Math.floor(n/1440),r=Math.floor(n%1440/60);n=Math.floor(n%60);var a="";return 1===i?a+=t["1 day"]+", ":i>1&&(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;u<s;u++){var l=r.height-40*u;a.moveTo(0,l-.5),a.lineTo(r.width,l-.5);var h=Math.round((n=l,i=t,o=e,c=r.height,(i*n+o*(c-n))/c))+" dBm";a.save(),a.strokeStyle="rgba(255, 255, 255, 0.9)",a.lineWidth=4,a.miterLimit=2,a.strokeText(h,r.width-5,l-2.5),a.fillText(h,r.width-5,l-2.5),a.restore()}a.stroke(),a.strokeStyle="rgba(0, 0, 0, 0.83)",a.lineWidth=1.5,a.strokeRect(.5,.5,r.width-1,r.height-1),a.restore()}()}}o(),window.addEventListener("resize",c);var s=0;return window.requestAnimationFrame(function t(e){e-s>40&&(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;n<t.length&&n<e.length&&t[n]===e[n];n++);return n}(e,n)),[i,n,t]})).sort(function(t,e){return t[0]<e[0]?1:t[0]>e[0]?-1:t[1]<e[1]?-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
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"PO-Revision-Date: 2018-02-26 00:30+0100\n"
"Last-Translator: <mschiffer@universe-factory.net>\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"
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 ""
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": "Статистика",