diff options
Diffstat (limited to 'applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js')
-rw-r--r-- | applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js | 711 |
1 files changed, 711 insertions, 0 deletions
diff --git a/applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js b/applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js new file mode 100644 index 0000000000..96847e11c3 --- /dev/null +++ b/applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js @@ -0,0 +1,711 @@ +/* + * Copyright (c) 2020 Tano Systems. All Rights Reserved. + * Author: Anton Kikin <a.kikin@tano-systems.com> + */ + +'use strict'; +'require rpc'; +'require form'; +'require lldpd'; +'require dom'; +'require poll'; + +var callLLDPStatus = rpc.declare({ + object: 'lldpd', + method: 'getStatus', + expect: {} +}); + +var dataMap = { + local: { + localChassis: null, + }, + remote: { + neightbors: null, + statistics: null, + }, +}; + +return L.view.extend({ + __init__: function() { + this.super('__init__', arguments); + + this.rowsUnfolded = {}; + + this.tableNeighbors = E('div', { 'class': 'table lldpd-table' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th left top' }, _('Local interface')), + E('div', { 'class': 'th left top' }, _('Protocol')), + E('div', { 'class': 'th left top' }, _('Discovered chassis')), + E('div', { 'class': 'th left top' }, _('Discovered port')), + ]), + E('div', { 'class': 'tr center placeholder' }, [ + E('div', { 'class': 'td' }, E('em', { 'class': 'spinning' }, + _('Collecting data...'))), + ]) + ]); + + this.tableStatistics = E('div', { 'class': 'table lldpd-table' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th left top' }, _('Local interface')), + E('div', { 'class': 'th left top' }, _('Protocol')), + E('div', { 'class': 'th left top' }, _('Administrative Status')), + E('div', { 'class': 'th right top' }, _('Tx')), + E('div', { 'class': 'th right top' }, _('Rx')), + E('div', { 'class': 'th right top' }, _('Tx discarded')), + E('div', { 'class': 'th right top' }, _('Rx unrecognized')), + E('div', { 'class': 'th right top' }, _('Ageout count')), + E('div', { 'class': 'th right top' }, _('Insert count')), + E('div', { 'class': 'th right top' }, _('Delete count')), + ]), + E('div', { 'class': 'tr center placeholder' }, [ + E('div', { 'class': 'td' }, E('em', { 'class': 'spinning' }, + _('Collecting data...'))), + ]) + ]); + + // Inject CSS + var head = document.getElementsByTagName('head')[0]; + var css = E('link', { 'href': + L.resource('lldpd/lldpd.css') + + '?v=#PKG_VERSION', 'rel': 'stylesheet' }); + + head.appendChild(css); + }, + + load: function() { + return Promise.all([ + L.resolveDefault(callLLDPStatus(), {}), + lldpd.init(), + ]); + }, + + /** @private */ + renderParam: function(param, value) { + if (typeof value === 'undefined') + return ''; + + return E('div', {}, [ + E('span', { 'class': 'lldpd-param' }, param), + E('span', { 'class': 'lldpd-param-value' }, value) + ]); + }, + + /** @private */ + renderAge: function(v) { + if (typeof v === 'undefined') + return "–"; + + return E('nobr', {}, v); + }, + + /** @private */ + renderIdType: function(v) { + if (typeof v === 'undefined') + return "–"; + + if (v == 'mac') + return _('MAC address'); + else if (v == 'ifname') + return _('Interface name'); + else if (v == 'local') + return _('Local ID'); + else if (v == 'ip') + return _('IP address'); + + return v; + }, + + /** @private */ + renderProtocol: function(v) { + if (typeof v === 'undefined' || v == 'unknown') + return "–"; + + if (v == 'LLDP') + return E('span', { 'class': 'lldpd-protocol-badge lldpd-protocol-lldp' }, v); + else if ((v == 'CDPv1') || (v == 'CDPv2')) + return E('span', { 'class': 'lldpd-protocol-badge lldpd-protocol-cdp' }, v); + else if (v == 'FDP') + return E('span', { 'class': 'lldpd-protocol-badge lldpd-protocol-fdp' }, v); + else if (v == 'EDP') + return E('span', { 'class': 'lldpd-protocol-badge lldpd-protocol-edp' }, v); + else if (v == 'SONMP') + return E('span', { 'class': 'lldpd-protocol-badge lldpd-protocol-sonmp' }, v); + else + return E('span', { 'class': 'lldpd-protocol-badge' }, v); + }, + + /** @private */ + renderAdminStatus: function(status) { + if ((typeof status === 'undefined') || !Array.isArray(status)) + return '–'; + + if (status[0].value === 'RX and TX') + return _('Rx and Tx'); + else if (status[0].value === 'RX only') + return _('Rx only'); + else if (status[0].value === 'TX only') + return _('Tx only'); + else if (status[0].value === 'disabled') + return _('Disabled'); + else + return _('Unknown'); + }, + + /** @private */ + renderNumber: function(v) { + if (parseInt(v)) + return v; + + return '–'; + }, + + /** @private */ + renderPort: function(port) { + if (typeof port.port !== 'undefined') + { + if (typeof port.port[0].descr !== 'undefined' && + typeof port.port[0].id[0].value !== 'undefined' && + port.port[0].descr[0].value !== port.port[0].id[0].value) + { + return [ + E('strong', {}, port.port[0].descr[0].value), + E('br', {}), + port.port[0].id[0].value + ]; + } + else + { + if (typeof port.port[0].descr !== 'undefined') + return port.port[0].descr[0].value; + else + return port.port[0].id[0].value; + } + } + else + { + return '%s'.format(port.name); + } + }, + + /** @private */ + renderPortParamTableShort: function(port) { + var items = []; + + items.push(this.renderParam(_('Name'), port.name)); + items.push(this.renderParam(_('Age'), this.renderAge(port.age))); + + return E('div', { 'class': 'lldpd-params' }, items); + }, + + /** @private */ + renderPortParamTable: function(port, only_id_and_ttl) { + var items = []; + + if (!only_id_and_ttl) { + items.push(this.renderParam(_('Name'), port.name)); + items.push(this.renderParam(_('Age'), this.renderAge(port.age))); + } + + if (typeof port.port !== 'undefined') + { + if (typeof port.port[0].id !== 'undefined') + { + items.push(this.renderParam(_('Port ID'), + port.port[0].id[0].value)); + + items.push(this.renderParam(_('Port ID type'), + this.renderIdType(port.port[0].id[0].type))); + } + + if (typeof port.port[0].descr !== 'undefined') + items.push(this.renderParam(_('Port description'), + port.port[0].descr[0].value)); + + if (typeof port.ttl !== 'undefined') + items.push(this.renderParam(_('TTL'), port.ttl[0].ttl)); + else if (port.port[0].ttl !== 'undefined') + items.push(this.renderParam(_('TTL'), port.port[0].ttl[0].value)); + + if (typeof port.port[0].mfs !== 'undefined') + items.push(this.renderParam(_('MFS'), port.port[0].mfs[0].value)); + } + + return E('div', { 'class': 'lldpd-params' }, items); + }, + + /** @private */ + renderChassis: function(ch) { + if (typeof ch.name !== 'undefined' && + typeof ch.descr !== 'undefined' && + typeof ch.name[0].value !== 'undefined' && + typeof ch.descr[0].value !== 'undefined') + { + return [ + E('strong', {}, ch.name[0].value), + E('br', {}), + ch.descr[0].value + ]; + } + else if (typeof ch.name !== 'undefined' && + typeof ch.name[0].value !== 'undefined') + return E('strong', {}, ch.name[0].value); + else if (typeof ch.descr !== 'undefined' && + typeof ch.descr[0].value !== 'undefined') + return ch.descr[0].value; + else if (typeof ch.id !== 'undefined' && + typeof ch.id[0].value !== 'undefined') + return ch.id[0].value; + else + return _('Unknown'); + }, + + /** @private */ + renderChassisParamTable: function(ch) { + var items = []; + + if (typeof ch.name !== 'undefined') + items.push(this.renderParam(_('Name'), ch.name[0].value)); + + if (typeof ch.descr !== 'undefined') + items.push(this.renderParam(_('Description'), ch.descr[0].value)); + + if (typeof ch.id !== 'undefined') { + items.push(this.renderParam(_('ID'), ch.id[0].value)); + items.push(this.renderParam(_('ID type'), + this.renderIdType(ch.id[0].type))); + } + + // Management addresses + if (typeof ch['mgmt-ip'] !== 'undefined') { + var ips = ''; + + if (ch['mgmt-ip'].length > 0) { + // Array of addresses + for (var ip = 0; ip < ch["mgmt-ip"].length; ip++) + ips += ch['mgmt-ip'][ip].value + '<br />'; + } + else { + // One address + ips += ch['mgmt-ip'][0].value; + } + + items.push(this.renderParam(_('Management IP(s)'), ips)); + } + + if (typeof ch.capability !== 'undefined') { + var caps = ''; + + if (ch.capability.length > 0) + { + // Array of capabilities + for (var cap = 0; cap < ch.capability.length; cap++) { + caps += ch.capability[cap].type; + caps += ' (' + (ch.capability[cap].enabled + ? _('enabled') : _('disabled')) + ')'; + caps += '<br />'; + } + } + else + { + // One capability + caps += ch.capability[0].type; + caps += ' (' + (ch.capability[0].enabled + ? _('enabled') : _('disabled')) + ')'; + } + + items.push(this.renderParam(_('Capabilities'), caps)); + } + + return E('div', { 'class': 'lldpd-params' }, items); + }, + + /** @private */ + getFoldingImage: function(unfolded) { + return L.resource('lldpd/details_' + + (unfolded ? 'hide' : 'show') + '.svg'); + }, + + /** @private */ + generateRowId: function(str) { + return str.replace(/[^a-z0-9]/gi, '-'); + }, + + /** @private */ + handleToggleFoldingRow: function(row, row_id) { + var e_img = row.querySelector('img'); + var e_folded = row.querySelectorAll('.lldpd-folded'); + var e_unfolded = row.querySelectorAll('.lldpd-unfolded'); + + if (e_folded.length != e_unfolded.length) + return; + + var do_unfold = (e_folded[0].style.display !== 'none'); + this.rowsUnfolded[row_id] = do_unfold; + + for (var i = 0; i < e_folded.length; i++) + { + if (do_unfold) + { + e_folded[i].style.display = 'none'; + e_unfolded[i].style.display = 'block'; + } + else + { + e_folded[i].style.display = 'block'; + e_unfolded[i].style.display = 'none'; + } + } + + e_img.src = this.getFoldingImage(do_unfold); + }, + + /** @private */ + makeFoldingTableRow: function(row, unfolded) { + // + // row[0] - row id + // row[1] - contents for first cell in row + // row[2] - contents for second cell in row + // ... + // row[N] - contents for N-th cell in row + // + if (row.length < 2) + return row; + + for (let i = 1; i < row.length; i++) { + if (i == 1) { + // Fold/unfold image appears only in first column + var dImg = E('div', { 'style': 'padding: 0 8px 0 0;' }, [ + E('img', { 'width': '16px', 'src': this.getFoldingImage(unfolded) }), + ]); + } + + if (Array.isArray(row[i])) { + // row[i][0] = folded contents + // row[i][1] = unfolded contents + + // Folded cell data + let dFolded = E('div', { + 'class': 'lldpd-folded', + 'style': unfolded ? 'display: none;' : 'display: block;' + }, row[i][0]); + + // Unfolded cell data + let dUnfolded = E('div', { + 'class': 'lldpd-unfolded', + 'style': unfolded ? 'display: block;' : 'display: none;' + }, row[i][1]); + + if (i == 1) { + row[i] = E('div', { + 'style': 'display: flex; flex-wrap: nowrap;' + }, [ dImg, dFolded, dUnfolded ]); + } + else { + row[i] = E('div', {}, [ dFolded, dUnfolded ]); + } + } + else { + // row[i] = same content for folded and unfolded states + + if (i == 1) { + row[i] = E('div', { + 'style': 'display: flex; flex-wrap: nowrap;' + }, [ dImg, E('div', row[i]) ]); + } + } + } + + return row; + }, + + /** @private */ + makeNeighborsTableRow: function(obj) { + if (typeof obj === 'undefined') + obj.name = 'Unknown'; + + var new_id = obj.name + '-' + obj.rid; + + if (typeof obj.port !== 'undefined') { + if (typeof obj.port[0].id !== 'undefined') + new_id += "-" + obj.port[0].id[0].value; + + if (typeof obj.port[0].descr !== 'undefined') + new_id += "-" + obj.port[0].descr[0].value; + } + + var row_id = this.generateRowId(new_id); + + return this.makeFoldingTableRow([ + row_id, + [ + '%s'.format(obj.name), + this.renderPortParamTableShort(obj) + ], + this.renderProtocol(obj.via), + [ + this.renderChassis(obj.chassis[0]), + this.renderChassisParamTable(obj.chassis[0]) + ], + [ + this.renderPort(obj), + this.renderPortParamTable(obj, true) + ] + ], this.rowsUnfolded[row_id] || false); + }, + + /** @private */ + renderInterfaceProtocols: function(iface, neighbors) { + if ((typeof iface === 'undefined') || + (typeof neighbors == 'undefined') || + (typeof neighbors.lldp[0] === 'undefined') || + (typeof neighbors.lldp[0].interface === 'undefined')) + return "–"; + + var name = iface.name; + var protocols = []; + + /* Search protocols for interface <name> */ + neighbors.lldp[0].interface.forEach(function(n) { + if (n.name !== name) + return; + + protocols.push(this.renderProtocol(n.via)); + }.bind(this)); + + if (protocols.length > 0) + return E('span', {}, protocols); + else + return "–"; + }, + + /** @private */ + makeStatisticsTableRow: function(sobj, iobj, neighbors) { + var row_id = this.generateRowId(iobj.name); + + return this.makeFoldingTableRow([ + row_id, + [ + this.renderPort(iobj), // folded + this.renderPortParamTable(iobj, false) // unfolded + ], + this.renderInterfaceProtocols(iobj, neighbors), + this.renderAdminStatus(iobj.status), + this.renderNumber(sobj.tx[0].tx), + this.renderNumber(sobj.rx[0].rx), + this.renderNumber(sobj.rx_discarded_cnt[0].rx_discarded_cnt), + this.renderNumber(sobj.rx_unrecognized_cnt[0].rx_unrecognized_cnt), + this.renderNumber(sobj.ageout_cnt[0].ageout_cnt), + this.renderNumber(sobj.insert_cnt[0].insert_cnt), + this.renderNumber(sobj.delete_cnt[0].delete_cnt) + ], this.rowsUnfolded[row_id] || false); + }, + + /** @private */ + updateTable: function(table, data, placeholder) { + var target = isElem(table) ? table : document.querySelector(table); + + if (!isElem(target)) + return; + + target.querySelectorAll( + '.tr.table-titles, .cbi-section-table-titles').forEach(L.bind(function(thead) { + var titles = []; + + thead.querySelectorAll('.th').forEach(function(th) { + titles.push(th); + }); + + if (Array.isArray(data)) { + var n = 0, rows = target.querySelectorAll('.tr'); + + data.forEach(L.bind(function(row) { + var id = row[0]; + var trow = E('div', { 'class': 'tr', 'click': L.bind(function(ev) { + this.handleToggleFoldingRow(ev.currentTarget, id); + // lldpd_folding_toggle(ev.currentTarget, id); + }, this) }); + + for (var i = 0; i < titles.length; i++) { + var text = (titles[i].innerText || '').trim(); + var td = trow.appendChild(E('div', { + 'class': titles[i].className, + 'data-title': (text !== '') ? text : null + }, row[i + 1] || '')); + + td.classList.remove('th'); + td.classList.add('td'); + } + + trow.classList.add('cbi-rowstyle-%d'.format((n++ % 2) ? 2 : 1)); + + if (rows[n]) + target.replaceChild(trow, rows[n]); + else + target.appendChild(trow); + }, this)); + + while (rows[++n]) + target.removeChild(rows[n]); + + if (placeholder && target.firstElementChild === target.lastElementChild) { + var trow = target.appendChild( + E('div', { 'class': 'tr placeholder' })); + + var td = trow.appendChild( + E('div', { 'class': 'center ' + titles[0].className }, placeholder)); + + td.classList.remove('th'); + td.classList.add('td'); + } + } else { + thead.parentNode.style.display = 'none'; + + thead.parentNode.querySelectorAll('.tr, .cbi-section-table-row').forEach(function(trow) { + if (trow !== thead) { + var n = 0; + trow.querySelectorAll('.th, .td').forEach(function(td) { + if (n < titles.length) { + var text = (titles[n++].innerText || '').trim(); + if (text !== '') + td.setAttribute('data-title', text); + } + }); + } + }); + + thead.parentNode.style.display = ''; + } + }, this)); + }, + + /** @private */ + startPolling: function() { + poll.add(L.bind(function() { + return callLLDPStatus().then(L.bind(function(data) { + this.renderData(data); + }, this)); + }, this)); + }, + + /** @private */ + renderDataLocalChassis: function(data) { + if (data && + typeof data !== 'undefined' && + typeof data['local-chassis'] !== 'undefined' && + typeof data['local-chassis'][0].chassis[0].name !== 'undefined') { + return this.renderChassisParamTable(data['local-chassis'][0].chassis[0]); + } + else { + return E('div', { 'class': 'alert-message warning' }, + _('No data to display')); + } + }, + + /** @private */ + renderDataNeighbors: function(neighbors) { + var rows = []; + + if (neighbors && + typeof neighbors !== 'undefined' && + typeof neighbors.lldp !== 'undefined') + { + var ifaces = neighbors.lldp[0].interface; + + // Fill table rows + if (typeof ifaces !== 'undefined') { + for (i = 0; i < ifaces.length; i++) + rows.push(this.makeNeighborsTableRow(ifaces[i])); + } + } + + return rows; + }, + + /** @private */ + renderDataStatistics: function(statistics, interfaces, neighbors) { + var rows = []; + + if (statistics && + interfaces && + typeof statistics !== 'undefined' && + typeof interfaces !== 'undefined' && + typeof statistics.lldp !== 'undefined' && + typeof interfaces.lldp !== 'undefined') + { + var sifaces = statistics.lldp[0].interface; + var ifaces = interfaces.lldp[0].interface; + + if ((typeof sifaces !== 'undefined') && + (typeof ifaces !== 'undefined')) { + for (var i = 0; i < sifaces.length; i++) + rows.push(this.makeStatisticsTableRow(sifaces[i], ifaces[i], neighbors)); + } + } + + return rows; + }, + + /** @private */ + renderData: function(data) { + var r; + + r = this.renderDataLocalChassis(data.chassis); + dom.content(document.getElementById('lldpd-local-chassis'), r); + + r = this.renderDataNeighbors(data.neighbors); + this.updateTable(this.tableNeighbors, r, + _('No data to display')); + + r = this.renderDataStatistics(data.statistics, data.interfaces, data.neighbors); + this.updateTable(this.tableStatistics, r, + _('No data to display')); + }, + + render: function(data) { + var m, s, ss, o; + + m = new form.JSONMap(dataMap, + _('LLDP Status'), + _('This page allows you to see discovered LLDP neighbors, ' + + 'local interfaces statistics and local chassis information.')); + + s = m.section(form.NamedSection, 'local', 'local', + _('Local Chassis')); + + o = s.option(form.DummyValue, 'localChassis'); + o.render = function() { + return E('div', { 'id': 'lldpd-local-chassis' }, [ + E('em', { 'class': 'spinning' }, _('Collecting data...')) + ]); + }; + + s = m.section(form.NamedSection, 'remote', 'remote'); + + s.tab('neighbors', _('Discovered Neighbors')); + s.tab('statistics', _('Interface Statistics')); + + o = s.taboption('neighbors', form.DummyValue, 'neighbors'); + o.render = L.bind(function() { + return E('div', { 'class': 'table-wrapper' }, [ + this.tableNeighbors + ]); + }, this); + + o = s.taboption('statistics', form.DummyValue, 'statistics'); + o.render = L.bind(function() { + return E('div', { 'class': 'table-wrapper' }, [ + this.tableStatistics + ]); + }, this); + + return m.render().then(L.bind(function(rendered) { + this.startPolling(); + return rendered; + }, this)); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); |