diff --git a/app.js b/app.js index 5390671..84316b2 100644 --- a/app.js +++ b/app.js @@ -37,5 +37,7 @@ require.config({ }); require(['main'], function (main) { - main(jsonData); + /** global: config */ + window.config = jsonData; + main(); }); diff --git a/config.default.json b/config.default.json index 39f1abc..f59737d 100644 --- a/config.default.json +++ b/config.default.json @@ -6,10 +6,69 @@ "nodeZoom": 18, "labelZoom": 13, "clientZoom": 15, - "nodeInfobox": { - "contact": false, - "hardwareUsage": true - }, + "nodeAttr": [ + // value can be a node attribute (1 depth) or a a function in utils/node with prefix show + { + "name": "node.status", + "value": "Status" + }, + { + "name": "node.gateway", + "value": "Gateway" + }, + { + "name": "node.coordinates", + "value": "GeoURI" + }, +// { +// "name": "node.contact", +// "value": "owner" +// }, + { + "name": "node.hardware", + "value": "model" + }, + { + "name": "node.primaryMac", + "value": "mac" + }, + { + "name": "node.firmware", + "value": "Firmware" + }, + { + "name": "node.uptime", + "value": "Uptime" + }, + { + "name": "node.firstSeen", + "value": "FirstSeen" + }, + { + "name": "node.systemLoad", + "value": "Load" + }, + { + "name": "node.ram", + "value": "RAM" + }, + { + "name": "node.ipAddresses", + "value": "IPs" + }, + { + "name": "node.update", + "value": "Autoupdate" + }, + { + "name": "node.site", + "value": "Site" + }, + { + "name": "node.clients", + "value": "Clients" + } + ], "supportedLocale": [ "en", "de", diff --git a/lib/forcegraph.js b/lib/forcegraph.js index ceecedc..b96010c 100644 --- a/lib/forcegraph.js +++ b/lib/forcegraph.js @@ -2,7 +2,7 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'd3-timer', 'd3-ease', function (d3Selection, d3Force, d3Zoom, d3Drag, d3Timer, d3Ease, d3Interpolate, math, draw) { 'use strict'; - return function (config, linkScale, sidebar, router) { + return function (linkScale, sidebar) { var self = this; var el; var canvas; diff --git a/lib/gui.js b/lib/gui.js index 01736b9..a6d5639 100644 --- a/lib/gui.js +++ b/lib/gui.js @@ -7,7 +7,7 @@ function (d3Interpolate, Map, Sidebar, Tabs, Container, Legend, Linklist, Title, About, DataDistributor, FilterGUI, HostnameFilter) { 'use strict'; - return function (config, router, language) { + return function (language) { var self = this; var content; var contentDiv; @@ -38,7 +38,7 @@ function (d3Interpolate, Map, Sidebar, Tabs, Container, Legend, Linklist, function addContent(K) { removeContent(); - content = new K(config, linkScale, sidebar.getWidth, router, buttons); + content = new K(linkScale, sidebar.getWidth, buttons); content.render(contentDiv); fanout.add(content); @@ -77,18 +77,18 @@ function (d3Interpolate, Map, Sidebar, Tabs, Container, Legend, Linklist, buttons.appendChild(buttonToggle); - var title = new Title(config); + var title = new Title(); var header = new Container('header'); - var infobox = new Infobox(config, sidebar, router, linkScale); + var infobox = new Infobox(sidebar, linkScale); var tabs = new Tabs(); var overview = new Container(); - var legend = new Legend(config, language); - var newnodeslist = new SimpleNodelist('new', 'firstseen', router, _.t('node.new')); - var lostnodeslist = new SimpleNodelist('lost', 'lastseen', router, _.t('node.missing')); - var nodelist = new Nodelist(router); - var linklist = new Linklist(linkScale, router); - var statistics = new Proportions(config, fanout); + var legend = new Legend(language); + var newnodeslist = new SimpleNodelist('new', 'firstseen', _.t('node.new')); + var lostnodeslist = new SimpleNodelist('lost', 'lastseen', _.t('node.missing')); + var nodelist = new Nodelist(); + var linklist = new Linklist(linkScale); + var statistics = new Proportions(fanout); var about = new About(); fanoutUnfiltered.add(legend); diff --git a/lib/infobox/link.js b/lib/infobox/link.js index 521a7f3..27e3827 100644 --- a/lib/infobox/link.js +++ b/lib/infobox/link.js @@ -13,7 +13,7 @@ define(['helper', 'snabbdom'], function (helper, V) { return helper.showStat(V, o, subst); } - return function (config, el, router, d, linkScale) { + return function (el, d, linkScale) { var self = this; var header = document.createElement('div'); var table = document.createElement('table'); diff --git a/lib/infobox/location.js b/lib/infobox/location.js index e313f83..cc4bcf5 100644 --- a/lib/infobox/location.js +++ b/lib/infobox/location.js @@ -1,7 +1,7 @@ define(['helper'], function (helper) { 'use strict'; - return function (config, el, router, d) { + return function (el, d) { var sidebarTitle = document.createElement('h2'); sidebarTitle.textContent = _.t('location.location'); el.appendChild(sidebarTitle); diff --git a/lib/infobox/main.js b/lib/infobox/main.js index f2984b7..c2076e4 100644 --- a/lib/infobox/main.js +++ b/lib/infobox/main.js @@ -1,7 +1,7 @@ define(['infobox/link', 'infobox/node', 'infobox/location'], function (Link, Node, location) { 'use strict'; - return function (config, sidebar, router, linkScale) { + return function (sidebar, linkScale) { var self = this; var el; var node; @@ -41,19 +41,19 @@ define(['infobox/link', 'infobox/node', 'infobox/location'], function (Link, Nod self.gotoNode = function gotoNode(d, nodeDict) { create(); - node = new Node(config, el, router, d, linkScale, nodeDict); + node = new Node(el, d, linkScale, nodeDict); node.render(); }; self.gotoLink = function gotoLink(d) { create(); - link = new Link(config, el, router, d, linkScale); + link = new Link(el, d, linkScale); link.render(); }; self.gotoLocation = function gotoLocation(d) { create(); - location(config, el, router, d); + location(el, d); }; self.setData = function setData(d) { diff --git a/lib/infobox/node.js b/lib/infobox/node.js index b08c8f9..e997106 100644 --- a/lib/infobox/node.js +++ b/lib/infobox/node.js @@ -1,137 +1,8 @@ -define(['sorttable', 'snabbdom', 'd3-interpolate', 'moment', 'helper'], - function (SortTable, V, d3Interpolate, moment, helper) { +define(['sorttable', 'snabbdom', 'd3-interpolate', 'moment', 'helper', 'utils/node'], + function (SortTable, V, d3Interpolate, moment, helper, nodef) { 'use strict'; V = V.default; - function showGeoURI(d) { - if (!helper.hasLocation(d)) { - return undefined; - } - - return V.h('td', - V.h('a', - { props: { href: 'geo:' + d.location.latitude + ',' + d.location.longitude } }, - Number(d.location.latitude.toFixed(6)) + ', ' + Number(d.location.longitude.toFixed(6)) - ) - ); - } - - function showStatus(d) { - return V.h('td', - { props: { className: d.is_online ? 'online' : 'offline' } }, - _.t((d.is_online ? 'node.lastOnline' : 'node.lastOffline'), { - time: d.lastseen.fromNow(), - date: d.lastseen.format('DD.MM.YYYY, H:mm:ss') - })); - } - - function showFirmware(d) { - return [ - helper.dictGet(d, ['firmware', 'release']), - helper.dictGet(d, ['firmware', 'base']) - ].filter(function (n) { - return n !== null; - }).join(' / ') || undefined; - } - - function showSite(d, config) { - var rt = d.site_code; - if (config.siteNames) { - config.siteNames.forEach(function (t) { - if (d.site_code === t.site) { - rt = t.name; - } - }); - } - return rt; - } - - function showClients(d) { - if (!d.is_online) { - return undefined; - } - - var clients = [ - V.h('span', [ - d.clients > 0 ? d.clients : _.t('none'), - V.h('br'), - V.h('i', { props: { className: 'ion-people', title: _.t('node.clients') } }) - ]), - V.h('span', - { props: { className: 'legend-24ghz' } }, - [ - d.clients_wifi24, - V.h('br'), - V.h('span', { props: { className: 'symbol', title: '2,4 Ghz' } }) - ]), - V.h('span', - { props: { className: 'legend-5ghz' } }, - [ - d.clients_wifi5, - V.h('br'), - V.h('span', { props: { className: 'symbol', title: '5 Ghz' } }) - ]), - V.h('span', - { props: { className: 'legend-others' } }, - [ - d.clients_other, - V.h('br'), - V.h('span', { props: { className: 'symbol', title: _.t('others') } }) - ]) - ]; - - return V.h('td', { props: { className: 'clients' } }, clients); - } - - function showIPs(d) { - var string = []; - var ips = d.network.addresses; - ips.sort(); - ips.forEach(function (ip, i) { - if (i > 0) { - string.push(V.h('br')); - } - - if (ip.indexOf('fe80:') !== 0) { - string.push(V.h('a', { props: { href: 'http://[' + ip + ']/', target: '_blank' } }, ip)); - } else { - string.push(ip); - } - }); - return V.h('td', string); - } - - function showBar(v, width, warning) { - return V.h('span', - { props: { className: 'bar' + (warning ? ' warning' : '') } }, - [ - V.h('span', - { - style: { width: (width * 100) + '%' } - }), - V.h('label', v) - ] - ); - } - - function showLoad(d) { - if (!('loadavg' in d)) { - return undefined; - } - return showBar(d.loadavg.toFixed(2), d.loadavg % 1, d.loadavg >= d.nproc); - } - - function showRAM(d) { - if (!('memory_usage' in d)) { - return undefined; - } - return showBar(Math.round(d.memory_usage * 100) + ' %', d.memory_usage, d.memory_usage >= 0.8); - } - - function showAutoupdate(d) { - return d.autoupdater.enabled ? _.t('node.activated', { branch: d.autoupdater.branch }) : _.t('node.deactivated'); - } - function showStatImg(o, d) { var subst = {}; subst['{NODE_ID}'] = d.node_id; @@ -141,7 +12,7 @@ define(['sorttable', 'snabbdom', 'd3-interpolate', 'moment', 'helper'], return helper.showStat(V, o, subst); } - return function (config, el, router, d, linkScale, nodeDict) { + return function (el, d, linkScale, nodeDict) { function nodeLink(node) { return V.h('a', { props: { @@ -249,23 +120,24 @@ define(['sorttable', 'snabbdom', 'd3-interpolate', 'moment', 'helper'], var children = []; - children.push(helper.attributeEntry(V, 'node.status', showStatus(d))); - children.push(helper.attributeEntry(V, 'node.gateway', d.is_gateway ? 'ja' : undefined)); - children.push(helper.attributeEntry(V, 'node.coordinates', showGeoURI(d))); - children.push(helper.attributeEntry(V, 'node.contact', d.owner)); - children.push(helper.attributeEntry(V, 'node.hardware', d.model)); - children.push(helper.attributeEntry(V, 'node.primaryMac', d.network.mac)); - children.push(helper.attributeEntry(V, 'node.firmware', showFirmware(d))); - children.push(helper.attributeEntry(V, 'node.site', showSite(d, config))); - children.push(helper.attributeEntry(V, 'node.uptime', moment.utc(d.uptime).local().fromNow(true))); - children.push(helper.attributeEntry(V, 'node.firstSeen', d.firstseen.fromNow(true))); - if (config.nodeInfobox && config.nodeInfobox.hardwareUsage) { - children.push(helper.attributeEntry(V, 'node.systemLoad', showLoad(d))); - children.push(helper.attributeEntry(V, 'node.ram', showRAM(d))); - } - children.push(helper.attributeEntry(V, 'node.ipAddresses', showIPs(d))); - children.push(helper.attributeEntry(V, 'node.update', showAutoupdate(d))); - children.push(helper.attributeEntry(V, 'node.clients', showClients(d))); + config.nodeAttr.forEach(function (row) { + var field = d[row.value]; + if (nodef['show' + row.value] !== undefined) { + field = nodef['show' + row.value](d); + } + + if (field) { + if (typeof field !== 'object') { + field = V.h('td', field); + } + + children.push(V.h('tr', [ + V.h('th', _.t(row.name)), + field + ])); + } + }); + children.push(helper.attributeEntry(V, 'node.gateway', showGateway(d))); var elNew = V.h('table', children); diff --git a/lib/legend.js b/lib/legend.js index 549b87a..71c1011 100644 --- a/lib/legend.js +++ b/lib/legend.js @@ -1,7 +1,7 @@ define(['helper'], function (helper) { 'use strict'; - return function (config, language) { + return function (language) { var self = this; var stats = document.createTextNode(''); var timestamp = document.createTextNode(''); diff --git a/lib/linklist.js b/lib/linklist.js index b6d2cf3..20cc364 100644 --- a/lib/linklist.js +++ b/lib/linklist.js @@ -28,7 +28,7 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) { reverse: true }]; - return function (linkScale, router) { + return function (linkScale) { var table = new SortTable(headings, 2, renderRow); V = V.default; diff --git a/lib/main.js b/lib/main.js index 45bf4f0..bcd9a8b 100644 --- a/lib/main.js +++ b/lib/main.js @@ -2,7 +2,7 @@ define(['moment', 'utils/router', 'leaflet', 'gui', 'helper', 'utils/language'], function (moment, Router, L, GUI, helper, Language) { 'use strict'; - return function (config) { + return function () { function handleData(data) { var timestamp; var nodes = []; @@ -72,8 +72,8 @@ define(['moment', 'utils/router', 'leaflet', 'gui', 'helper', 'utils/language'], }; } - var language = new Language(config); - var router = new Router(language); + var language = new Language(); + window.router = new Router(language); config.dataPath.forEach(function (d, i) { config.dataPath[i] += 'meshviewer.json'; @@ -88,7 +88,7 @@ define(['moment', 'utils/router', 'leaflet', 'gui', 'helper', 'utils/language'], update() .then(function (d) { - var gui = new GUI(config, router, language); + var gui = new GUI(language); gui.setData(d); router.setData(d); router.resolve(); diff --git a/lib/map.js b/lib/map.js index 9502ad6..6b1d9fd 100644 --- a/lib/map.js +++ b/lib/map.js @@ -8,7 +8,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'], minZoom: 0 }; - return function (config, linkScale, sidebar, router, buttons) { + return function (linkScale, sidebar, buttons) { var self = this; var savedView; @@ -57,7 +57,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'], baseLayers[d.name] = d.layer; }); - var button = new Button(config, map, router, buttons); + var button = new Button(map, buttons); map.on('locationfound', button.locationFound); map.on('locationerror', button.locationError); @@ -175,7 +175,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'], linkDict = {}; clientLayer.setData(data); - labelLayer.setData(data, map, nodeDict, linkDict, linkScale, router, config); + labelLayer.setData(data, map, nodeDict, linkDict, linkScale); updateView(true); }; diff --git a/lib/map/button.js b/lib/map/button.js index b7020eb..b0a2f33 100644 --- a/lib/map/button.js +++ b/lib/map/button.js @@ -63,7 +63,7 @@ define(['map/clientlayer', 'map/labellayer', 'leaflet', 'map/locationmarker'], } }); - return function (config, map, router, buttons) { + return function (map, buttons) { var userLocation; var locateUserButton = new LocateButton(function (d) { diff --git a/lib/map/labellayer.js b/lib/map/labellayer.js index fae9c20..fdd4742 100644 --- a/lib/map/labellayer.js +++ b/lib/map/labellayer.js @@ -76,7 +76,7 @@ define(['leaflet', 'rbush', 'helper', 'moment'], return { minX: x, minY: y, maxX: x + width, maxY: y + height }; } - function mkMarker(dict, iconFunc, router) { + function mkMarker(dict, iconFunc) { return function (d) { var m = L.circleMarker([d.location.latitude, d.location.longitude], iconFunc(d)); @@ -95,7 +95,7 @@ define(['leaflet', 'rbush', 'helper', 'moment'], }; } - function addLinksToMap(dict, linkScale, graph, router) { + function addLinksToMap(dict, linkScale, graph) { graph = graph.filter(function (d) { return 'distance' in d && d.type.indexOf('vpn') !== 0; }); @@ -125,7 +125,7 @@ define(['leaflet', 'rbush', 'helper', 'moment'], }); } - function getIcon(config, color) { + function getIcon(color) { return Object.assign({}, config.icon.base, config.icon[color]); } @@ -136,12 +136,12 @@ define(['leaflet', 'rbush', 'helper', 'moment'], this.prepareLabels(); } }, - setData: function (data, map, nodeDict, linkDict, linkScale, router, config) { - var iconOnline = getIcon(config, 'online'); - var iconOffline = getIcon(config, 'offline'); - var iconLost = getIcon(config, 'lost'); - var iconAlert = getIcon(config, 'alert'); - var iconNew = getIcon(config, 'new'); + setData: function (data, map, nodeDict, linkDict, linkScale) { + var iconOnline = getIcon('online'); + var iconOffline = getIcon('offline'); + var iconLost = getIcon('lost'); + var iconAlert = getIcon('alert'); + var iconNew = getIcon('new'); // Check if init or data is already set if (groupLines) { groupOffline.clearLayers(); @@ -151,7 +151,7 @@ define(['leaflet', 'rbush', 'helper', 'moment'], groupLines.clearLayers(); } - var lines = addLinksToMap(linkDict, linkScale, data.links, router); + var lines = addLinksToMap(linkDict, linkScale, data.links); groupLines = L.featureGroup(lines).addTo(map); var nodesOnline = helper.subtract(data.nodes.online, data.nodes.new).filter(helper.hasLocation); @@ -161,15 +161,15 @@ define(['leaflet', 'rbush', 'helper', 'moment'], var markersOnline = nodesOnline.map(mkMarker(nodeDict, function () { return iconOnline; - }, router)); + })); var markersOffline = nodesOffline.map(mkMarker(nodeDict, function () { return iconOffline; - }, router)); + })); var markersNew = nodesNew.map(mkMarker(nodeDict, function () { return iconNew; - }, router)); + })); var markersLost = nodesLost.map(mkMarker(nodeDict, function (d) { var age = moment(data.now).diff(d.lastseen, 'days', true); @@ -180,7 +180,7 @@ define(['leaflet', 'rbush', 'helper', 'moment'], return iconLost; } return null; - }, router)); + })); groupOffline = L.featureGroup(markersOffline).addTo(map); groupLost = L.featureGroup(markersLost).addTo(map); diff --git a/lib/nodelist.js b/lib/nodelist.js index f55fc81..383e15e 100644 --- a/lib/nodelist.js +++ b/lib/nodelist.js @@ -47,7 +47,7 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) { reverse: true }]; - return function (router) { + return function () { function renderRow(d) { var td0Content = ''; if (helper.hasLocation(d)) { diff --git a/lib/proportions.js b/lib/proportions.js index f0acdf6..44afa8e 100644 --- a/lib/proportions.js +++ b/lib/proportions.js @@ -2,7 +2,7 @@ define(['d3-interpolate', 'snabbdom', 'filters/genericnode', 'helper'], function (d3Interpolate, V, Filter, helper) { 'use strict'; - return function (config, filterManager) { + return function (filterManager) { var self = this; var scale = d3Interpolate.interpolate('#770038', '#dc0067'); V = V.default; diff --git a/lib/simplenodelist.js b/lib/simplenodelist.js index e7f710e..25cb7c4 100644 --- a/lib/simplenodelist.js +++ b/lib/simplenodelist.js @@ -2,7 +2,7 @@ define(['moment', 'snabbdom', 'helper'], function (moment, V, helper) { 'use strict'; V = V.default; - return function (nodes, field, router, title) { + return function (nodes, field, title) { var self = this; var el; var tbody; diff --git a/lib/title.js b/lib/title.js index b3855a3..b161973 100644 --- a/lib/title.js +++ b/lib/title.js @@ -1,7 +1,7 @@ define(function () { 'use strict'; - return function (config) { + return function () { function setTitle(d) { var title = [config.siteName]; diff --git a/lib/utils/helper.js b/lib/utils/helper.js index 8d4da14..6dd04b5 100644 --- a/lib/utils/helper.js +++ b/lib/utils/helper.js @@ -118,7 +118,6 @@ define({ value ]); }, - showStat: function showStat(V, o, subst) { var content; subst = typeof subst !== 'undefined' ? subst : {}; diff --git a/lib/utils/language.js b/lib/utils/language.js index 66f1685..1435406 100644 --- a/lib/utils/language.js +++ b/lib/utils/language.js @@ -1,6 +1,6 @@ define(['polyglot', 'moment', 'helper'], function (Polyglot, moment, helper) { 'use strict'; - return function (config) { + return function () { var router; function languageSelect(el) { @@ -60,6 +60,7 @@ define(['polyglot', 'moment', 'helper'], function (Polyglot, moment, helper) { function init(r) { router = r; + /** global: _ */ window._ = new Polyglot({ locale: getLocale(router.getLang()), allowMissing: true }); helper.getJSON('locale/' + _.locale() + '.json?' + config.cacheBreaker).then(setTranslation); document.querySelector('html').setAttribute('lang', _.locale()); diff --git a/lib/utils/node.js b/lib/utils/node.js new file mode 100644 index 0000000..114decb --- /dev/null +++ b/lib/utils/node.js @@ -0,0 +1,149 @@ +define(['snabbdom', 'helper', 'moment'], function (V, helper, moment) { + 'use strict'; + V = V.default; + + var self = {}; + + function showBar(v, width, warning) { + return V.h('span', + { props: { className: 'bar' + (warning ? ' warning' : '') } }, + [ + V.h('span', + { + style: { width: (width * 100) + '%' } + }), + V.h('label', v) + ] + ); + } + + self.showStatus = function showStatus(d) { + return V.h('td', + { props: { className: d.is_online ? 'online' : 'offline' } }, + _.t((d.is_online ? 'node.lastOnline' : 'node.lastOffline'), { + time: d.lastseen.fromNow(), + date: d.lastseen.format('DD.MM.YYYY, H:mm:ss') + })); + }; + + self.showGeoURI = function showGeoURI(d) { + if (!helper.hasLocation(d)) { + return undefined; + } + + return V.h('td', + V.h('a', + { props: { href: 'geo:' + d.location.latitude + ',' + d.location.longitude } }, + Number(d.location.latitude.toFixed(6)) + ', ' + Number(d.location.longitude.toFixed(6)) + ) + ); + }; + + self.showGateway = function showGateway(d) { + return d.is_gateway ? _.t('yes') : undefined; + }; + + self.showFirmware = function showFirmware(d) { + return [ + helper.dictGet(d, ['firmware', 'release']), + helper.dictGet(d, ['firmware', 'base']) + ].filter(function (n) { + return n !== null; + }).join(' / ') || undefined; + }; + + self.showUptime = function showUptime(d) { + return moment.utc(d.uptime).local().fromNow(true); + }; + + self.showFirstSeen = function showFirstSeen(d) { + return d.firstseen.fromNow(true); + }; + + self.showLoad = function showLoad(d) { + if (!d.loadavg) { + return undefined; + } + return showBar(d.loadavg.toFixed(2), d.loadavg % 1, d.loadavg >= d.nproc); + }; + + self.showRAM = function showRAM(d) { + if (!d.memory_usage) { + return undefined; + } + return showBar(Math.round(d.memory_usage * 100) + ' %', d.memory_usage, d.memory_usage >= 0.8); + }; + + self.showSite = function showSite(d) { + var rt = d.site_code; + if (config.siteNames) { + config.siteNames.forEach(function (t) { + if (d.site_code === t.site) { + rt = t.name; + } + }); + } + return rt; + }; + + self.showClients = function showClients(d) { + if (!d.is_online) { + return undefined; + } + + var clients = [ + V.h('span', [ + d.clients > 0 ? d.clients : _.t('none'), + V.h('br'), + V.h('i', { props: { className: 'ion-people', title: _.t('node.clients') } }) + ]), + V.h('span', + { props: { className: 'legend-24ghz' } }, + [ + d.clients_wifi24, + V.h('br'), + V.h('span', { props: { className: 'symbol', title: '2,4 Ghz' } }) + ]), + V.h('span', + { props: { className: 'legend-5ghz' } }, + [ + d.clients_wifi5, + V.h('br'), + V.h('span', { props: { className: 'symbol', title: '5 Ghz' } }) + ]), + V.h('span', + { props: { className: 'legend-others' } }, + [ + d.clients_other, + V.h('br'), + V.h('span', { props: { className: 'symbol', title: _.t('others') } }) + ]) + ]; + + return V.h('td', { props: { className: 'clients' } }, clients); + }; + + self.showIPs = function showIPs(d) { + var string = []; + var ips = d.network.addresses; + ips.sort(); + ips.forEach(function (ip, i) { + if (i > 0) { + string.push(V.h('br')); + } + + if (ip.indexOf('fe80:') !== 0) { + string.push(V.h('a', { props: { href: 'http://[' + ip + ']/', target: '_blank' } }, ip)); + } else { + string.push(ip); + } + }); + return V.h('td', string); + }; + + self.showAutoupdate = function showAutoupdate(d) { + return d.autoupdater.enabled ? _.t('node.activated', { branch: d.autoupdater.branch }) : _.t('node.deactivated'); + }; + + return self; +});