summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-lldpd/htdocs/luci-static
diff options
context:
space:
mode:
Diffstat (limited to 'applications/luci-app-lldpd/htdocs/luci-static')
-rw-r--r--applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js225
-rw-r--r--applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/details_hide.svg9
-rw-r--r--applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/details_show.svg9
-rw-r--r--applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/lldpd.css111
-rw-r--r--applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js609
-rw-r--r--applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js713
6 files changed, 1676 insertions, 0 deletions
diff --git a/applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js b/applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js
new file mode 100644
index 0000000000..7b0121e548
--- /dev/null
+++ b/applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2018-2020, Tano Systems LLC. All Rights Reserved.
+ * Anton Kikin <a.kikin@tano-systems.com>
+ * Copyright (c) 2023-2024. All Rights Reserved.
+ * Paul Donald <newtwen+github@gmail.com>
+ */
+
+'use strict';
+'require ui';
+'require form';
+'require network';
+'require session';
+'require uci';
+
+/*
+ * Filter neighbors (-H)
+ *
+ * The filter column means that filtering is enabled
+ * The 1proto column tells that only one protocol will be kept.
+ * The 1neigh column tells that only one neighbor will be kept.
+ *
+ * incoming outgoing
+ * filter 1proto 1neigh filter 1proto 1neigh
+ * 0
+ * 1 x x x x
+ * 2 x x
+ * 3 x x
+ * 4 x x
+ * 5 x
+ * 6 x
+ * 7 x x x x x
+ * 8 x x x
+ * 9 x x x x
+ * 10 x x
+ * 11 x x
+ * 12 x x x x
+ * 13 x x x
+ * 14 x x x x
+ * 15 x x x
+ * 16 x x x x x
+ * 17 x x x x
+ * 18 x x x
+ * 19 x x x
+ */
+
+const etitle = _('enable filter');
+const ptitle = _('keep only one protocol');
+const ntitle = _('keep only one neighbor');
+
+var cbiFilterSelect = form.Value.extend({
+ __name__: 'CBI.LLDPD.FilterSelect',
+
+ __init__: function() {
+ this.super('__init__', arguments);
+
+ this.selected = null;
+
+ this.filterVal = [
+ [ 0, 0, 0, 0, 0, 0 ],
+ [ 1, 1, 0, 1, 1, 0 ],
+ [ 1, 1, 0, 0, 0, 0 ],
+ [ 0, 0, 0, 1, 1, 0 ],
+ [ 1, 0, 0, 1, 0, 0 ],
+ [ 1, 0, 0, 0, 0, 0 ],
+ [ 0, 0, 0, 1, 0, 0 ],
+ [ 1, 1, 1, 1, 1, 0 ],
+ [ 1, 1, 1, 0, 0, 0 ],
+ [ 1, 0, 1, 1, 1, 0 ],
+ [ 0, 0, 0, 1, 0, 1 ],
+ [ 1, 0, 1, 0, 0, 0 ],
+ [ 1, 0, 1, 1, 0, 1 ],
+ [ 1, 0, 1, 1, 0, 0 ],
+ [ 1, 1, 0, 1, 0, 1 ],
+ [ 1, 1, 0, 1, 0, 0 ],
+ [ 1, 1, 1, 1, 0, 1 ],
+ [ 1, 1, 1, 1, 0, 0 ],
+ [ 1, 0, 0, 1, 0, 1 ],
+ [ 1, 0, 0, 1, 1, 0 ]
+ ];
+ },
+
+ /** @private */
+ handleRowClick: function(section_id, ev) {
+ var row = ev.currentTarget;
+ var tbody = row.parentNode;
+ var selected = row.getAttribute('data-filter');
+ var input = tbody.querySelector('[id="' + this.cbid(section_id) + '-' + selected + '"]');
+
+ this.selected = selected;
+
+ tbody.querySelectorAll('tr').forEach(function(e) {
+ e.classList.remove('lldpd-filter-selected');
+ });
+
+ input.checked = true;
+ row.classList.add('lldpd-filter-selected');
+ },
+
+ formvalue: function(section_id) {
+ return this.selected || this.cfgvalue(section_id);
+ },
+
+ renderFrame: function(section_id, in_table, option_index, nodes) {
+ var tmp = this.description;
+
+ // Prepend description with table legend
+ this.description =
+ '<ul><li>' + 'E &mdash; ' + etitle + '</li>' +
+ '<li>' + 'P &mdash; ' + ptitle + '</li>' +
+ '<li>' + 'N &mdash; ' + ntitle + '</li>' +
+ '</ul>' + this.description;
+
+ var rendered = this.super('renderFrame', arguments);
+
+ // Restore original description
+ this.description = tmp;
+
+ return rendered;
+ },
+
+ renderWidget: function(section_id, option_index, cfgvalue) {
+ //default value is "15" - rows are zero based
+ var selected = parseInt(cfgvalue) || 15;
+
+ var tbody = [];
+
+ var renderFilterVal = L.bind(function(row, col) {
+ return this.filterVal[row][col] ? '&#x2714;' : '';
+ }, this);
+
+ for (var i = 0; i < this.filterVal.length; i++) {
+ tbody.push(E('tr', {
+ 'class': ((selected == i) ? 'lldpd-filter-selected' : ''),
+ 'click': L.bind(this.handleRowClick, this, section_id),
+ 'data-filter': i,
+ }, [
+ E('td', {}, [
+ E('input', {
+ 'class': 'cbi-input-radio',
+ 'data-update': 'click change',
+ 'type': 'radio',
+ 'id': this.cbid(section_id) + '-' + i,
+ 'name': this.cbid(section_id),
+ 'checked': (selected == i) ? '' : null,
+ 'value': i
+ })
+ ]),
+ E('td', {}, i),
+ E('td', {'title': etitle}, renderFilterVal(i, 0)),
+ E('td', {'title': ptitle}, renderFilterVal(i, 1)),
+ E('td', {'title': ntitle}, renderFilterVal(i, 2)),
+ E('td', {'title': etitle}, renderFilterVal(i, 3)),
+ E('td', {'title': ptitle}, renderFilterVal(i, 4)),
+ E('td', {'title': ntitle}, renderFilterVal(i, 5))
+ ]));
+ };
+
+ var table = E('table', { 'class': 'lldpd-filter', 'id': this.cbid(section_id) }, [
+ E('thead', {}, [
+ E('tr', {}, [
+ E('th', { 'rowspan': 2 }),
+ E('th', { 'rowspan': 2 }, _('Filter')),
+ E('th', { 'colspan': 3 }, _('Incoming')),
+ E('th', { 'colspan': 3 }, _('Outgoing'))
+ ]),
+ E('tr', {}, [
+ E('th', {}, 'E'),
+ E('th', {}, 'P'),
+ E('th', {}, 'N'),
+ E('th', {}, 'E'),
+ E('th', {}, 'P'),
+ E('th', {}, 'N'),
+ ])
+ ]),
+ E('tbody', {}, tbody)
+ ]);
+
+ return table;
+ },
+});
+
+var CBIMultiIOSelect = form.MultiValue.extend({
+ __name__: 'CBI.MultiIOSelect',
+
+ renderWidget: function(section_id, option_index, cfgvalue) {
+ var value = (cfgvalue != null) ? cfgvalue : this.default ? this.default : '',
+ choices = this.transformChoices() ? this.transformChoices() : '';
+
+ var widget = new ui.Dropdown(L.toArray(value), choices, {
+ id: this.cbid(section_id),
+ sort: this.keylist,
+ multiple: true,
+ optional: true,
+ display_items: 5,
+ dropdown_items: -1,
+ create: true,
+ disabled: (this.readonly != null) ? this.readonly : this.map.readonly,
+ validate: L.bind(this.validate, this, section_id),
+ });
+
+ return widget.render();
+ }
+});
+
+function init() {
+ return new Promise(function(resolveFn, rejectFn) {
+ var data = session.getLocalData('luci-app-lldpd');
+ if (data !== null) {
+ return resolveFn();
+ }
+
+ data = {};
+
+ return uci.load('luci').then(function() {
+ session.setLocalData('luci-app-lldpd', data);
+ return resolveFn();
+ });
+ });
+}
+
+return L.Class.extend({
+ cbiFilterSelect: cbiFilterSelect,
+ CBIMultiIOSelect: CBIMultiIOSelect,
+ init: init,
+});
diff --git a/applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/details_hide.svg b/applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/details_hide.svg
new file mode 100644
index 0000000000..0b64b95c78
--- /dev/null
+++ b/applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/details_hide.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<svg width="18" height="11.12" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1">
+ <metadata>image/svg+xml</metadata>
+
+ <g class="layer">
+ <title>Layer 1</title>
+ <path d="m9,11.12l9,-9l-2.12,-2.12l-6.88,6.88l-6.88,-6.88l-2.12,2.12l9,9z" fill="#ff7f00" id="svg_1"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/details_show.svg b/applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/details_show.svg
new file mode 100644
index 0000000000..e51e19ddee
--- /dev/null
+++ b/applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/details_show.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1">
+ <metadata>image/svg+xml</metadata>
+
+ <g class="layer">
+ <title>Layer 1</title>
+ <path d="m4.58,2.83l9.17,9.17l-9.17,9.17l2.83,2.83l12,-12l-12,-12l-2.83,2.83z" fill="#ff7f00" id="svg_1"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/lldpd.css b/applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/lldpd.css
new file mode 100644
index 0000000000..5932e41536
--- /dev/null
+++ b/applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/lldpd.css
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2020, Tano Systems LLC. All Rights Reserved.
+ * Author: Anton Kikin <a.kikin@tano-systems.com>
+ * Copyright (c) 2023-2024. All Rights Reserved.
+ * Paul Donald <newtwen+github@gmail.com>
+ */
+
+/*
+ * Filter select widget
+ */
+table.lldpd-filter td,
+table.lldpd-filter th {
+ border: 1px solid #ccc !important;
+ padding: 2px 10px;
+ text-align: center;
+}
+
+table.lldpd-filter td,
+table.lldpd-filter td input[type="radio"] {
+ vertical-align: middle;
+}
+
+table.lldpd-filter th { font-weight: bold; }
+
+table.lldpd-filter tbody tr {
+ cursor: pointer;
+}
+
+table.lldpd-filter tr.lldpd-filter-selected td {
+ background-color: #EEE;
+}
+
+/*
+ * Parameters
+ */
+.lldpd-params {
+ column-count: 2;
+ column-gap: 24px;
+}
+
+@media only screen and (max-width: 850px) {
+ .lldpd-params {
+ column-count: 1;
+ }
+}
+
+.lldpd-params > div {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ border-bottom: 1px solid #e6e6e6;
+ padding: 0 8px;
+/* column-break-inside: avoid;*/
+}
+
+.td .lldpd-params > div:last-of-type {
+ border-bottom: none;
+}
+
+.lldpd-params .lldpd-param {
+ margin-right: 10px;
+ font-weight: bold;
+}
+
+.lldpd-params .lldpd-param::after {
+ content: ':';
+}
+
+.lldpd-params .lldpd-param-value {
+ white-space: normal;
+ font-weight: normal;
+ text-align: right;
+}
+
+.td .lldpd-params {
+ column-count: 1;
+}
+
+.td .lldpd-params > div {
+ padding: 0;
+}
+
+/*
+ * Status table
+ */
+.lldpd-folded,
+.lldpd-unfolded {
+ width: 100%;
+}
+
+.lldpd-table .tr .td { cursor: pointer; }
+
+.lldpd-protocol-badge {
+ display: inline-block;
+ width: auto !important;
+ width: fit-content !important;
+ box-shadow: 0 1px 3px 0 grey;
+ padding: 0px 8px;
+ border-radius: 5px;
+ width: 100%;
+ background-color: #e6e6e6;
+ border: 0;
+ margin-right: 5px;
+ margin-bottom: 5px;
+ color: #595959;
+}
+
+.lldpd-protocol-badge.lldpd-protocol-lldp { background-color: #b7efcf; border-color: #2abd69; color: #165e34; }
+.lldpd-protocol-badge.lldpd-protocol-cdp { background-color: #b2daf3; border-color: #46a6e2; color: #1a74ac; }
+.lldpd-protocol-badge.lldpd-protocol-fdp { background-color: #f9e3b3; border-color: #b7820f; color: #b7820f; }
+.lldpd-protocol-badge.lldpd-protocol-edp { background-color: #f9e3f9; border-color: #e380e3; color: #b70f82; }
+.lldpd-protocol-badge.lldpd-protocol-sonmp { background-color: #f4ffc4; border-color: #a7ce00; color: #7e9b00; }
diff --git a/applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js b/applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js
new file mode 100644
index 0000000000..a8e32b4275
--- /dev/null
+++ b/applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js
@@ -0,0 +1,609 @@
+/*
+ * Copyright (c) 2020 Tano Systems LLC. All Rights Reserved.
+ * Author: Anton Kikin <a.kikin@tano-systems.com>
+ * Copyright (c) 2023-2024. All Rights Reserved.
+ * Paul Donald <newtwen+github@gmail.com>
+ */
+
+'use strict';
+'require rpc';
+'require form';
+'require lldpd';
+'require network';
+'require uci';
+'require tools.widgets as widgets';
+
+var callInitList = rpc.declare({
+ object: 'luci',
+ method: 'getInitList',
+ params: [ 'name' ],
+ expect: { '': {} },
+ filter: function(res) {
+ for (var k in res)
+ return +res[k].enabled;
+ return null;
+ }
+});
+
+var callInitAction = rpc.declare({
+ object: 'luci',
+ method: 'setInitAction',
+ params: [ 'name', 'action' ],
+ expect: { result: false }
+});
+
+var usage = _('See syntax <a %s>here</a>.').format('href=https://lldpd.github.io/usage.html target="_blank"');
+
+const validateioentries = function(section_id, value) {
+ if (value) {
+ const emsg = _('Cannot have both interface %s and its exclusion %s');
+ const a = value.split(' ');
+ const noex = a.filter(el=> !el.startsWith('!'));
+ const ex = a.filter(el=> el.startsWith('!') && !el.startsWith('!!'));
+ for (var i of noex) {
+ for (var e of ex) {
+ if ('!'+i == e){
+ return emsg.format(i, e);
+ }
+ }
+ }
+ }
+ return true;
+};
+
+return L.view.extend({
+ __init__: function() {
+ this.super('__init__', arguments);
+
+ // 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([
+ callInitList('lldpd'),
+ lldpd.init(),
+ uci.load('lldpd'),
+ network.getDevices()
+ ]);
+ },
+
+ // -----------------------------------------------------------------------------------------
+ //
+ // Basic Options
+ //
+ // -----------------------------------------------------------------------------------------
+
+ /** @private */
+ populateBasicOptions: function(s, tab, data) {
+ var o;
+ var serviceEnabled = data[0];
+ var net_devices = data[3];
+
+ // Service enable/disable
+ o = s.taboption(tab, form.Flag, 'enabled', _('Enable service'));
+ o.optional = false;
+ o.rmempty = false;
+
+ o.cfgvalue = function() {
+ return serviceEnabled ? this.enabled : this.disabled;
+ };
+
+ o.write = function(section_id, value) {
+ uci.set('lldpd', section_id, 'enabled', value);
+
+ if (value == '1') {
+ // Enable and start
+ callInitAction('lldpd', 'enable').then(function() {
+ return callInitAction('lldpd', 'start');
+ });
+ }
+ else {
+ // Stop and disable
+ callInitAction('lldpd', 'stop').then(function() {
+ return callInitAction('lldpd', 'disable');
+ });
+ }
+ };
+
+ // System description
+ o = s.taboption(tab, form.Value, 'lldp_description',
+ _('System description'),
+ _('Override %s.').format('<code>system description</code>'));
+
+ o.placeholder = 'System description';
+
+ // System hostname
+ o = s.taboption(tab, form.Value, 'lldp_hostname',
+ _('System hostname'),
+ _('Override %s.').format('<code>system hostname</code>'));
+
+ o.placeholder = 'System hostname';
+
+ // Host location
+ o = s.taboption(tab, form.Value, 'lldp_location',
+ _('Host location'),
+ _('Override the announced location of the host.') + '<br />' +
+ usage);
+ // multiple syntaxes alert for location parameter
+ o.placeholder = 'address country EU';
+ o.rmempty = true;
+ o.validate = function(section_id, value) {
+ if (value) {
+ if (!value.match(/^coordinate |^address |^elin /))
+ return _("Must start: 'coordinate ...', 'address ...' or 'elin ...'");
+ }
+ return true;
+ };
+
+
+ // Platform
+ o = s.taboption(tab, form.Value, 'lldp_platform',
+ _('System platform description'),
+ _('Override %s.').format('<code>system platform</code>') + '<br />' +
+ _('The default description is the kernel name (Linux).'));
+
+ o.placeholder = 'System platform description';
+
+ o = s.taboption(tab, form.Flag, 'lldp_capability_advertisements', _('System capability advertisements'));
+ o.default = '1'; //lldpd internal default
+
+ // Capabilities override
+ o = s.taboption(tab, form.MultiValue, 'lldp_syscapabilities',
+ _('System capabilities'),
+ _('Override %s.').format('<code>system capabilities</code>') + '<br />' +
+ _('The default is derived from kernel information.'));
+ o.depends({lldp_capability_advertisements: '1'});
+ o.value('bridge');
+ o.value('docsis');
+ o.value('other');
+ o.value('repeater');
+ o.value('router');
+ o.value('station');
+ o.value('telephone');
+ o.value('wlan');
+ o.cfgvalue = function(section_id) {
+ return String(this.super('load', [section_id]) || this.default).split(',');
+ };
+ o.write = function(section_id, value) {
+ return this.super('write', [ section_id, L.toArray(value).join(',') ]);
+ };
+
+ o = s.taboption(tab, form.Flag, 'lldp_mgmt_addr_advertisements', _('System management IO advertisements'));
+ o.default = '1'; //lldpd internal default
+
+ // Management addresses of this system
+ // This value: lldpd.init handles as a single value, and needs a CSV for lldpd.conf: 'configure system ip management pattern'
+ o = s.taboption(tab, lldpd.CBIMultiIOSelect, 'lldp_mgmt_ip',
+ _('System management IO'),
+ _('Defaults to the first IPv4 and IPv6. ' +
+ 'If an exact IP address is provided, it is used ' +
+ 'as a management address without any check. To ' +
+ 'blacklist IPv6 addresses, use <code>!*:*</code>.') + '<br />' +
+ usage);
+ o.placeholder = 'Addresses and interfaces';
+ o.depends({lldp_mgmt_addr_advertisements: '1'});
+ o.cfgvalue = function(section_id) {
+ const opt = uci.get(this.config, section_id, this.option);
+ return opt ? opt.split(',') : '';
+ };
+ net_devices.forEach(nd => {
+ o.value(nd.getName());
+ o.value('!'+nd.getName());
+ nd.getIPAddrs().forEach(addr => o.value(addr.split('/')[0], E([], [addr.split('/')[0], ' (', E('strong', {}, nd.getName()), ')'])));
+ nd.getIP6Addrs().forEach(addr => o.value(addr.split('/')[0], E([], [addr.split('/')[0], ' (', E('strong', {}, nd.getName()), ')'])));
+ });
+ o.value('!*:*');
+ o.validate = validateioentries;
+ o.write = function(section_id, value, sep) {
+ return this.super('write', [ section_id, value.join(',') ]);
+ }
+
+ // LLDP tx interval
+ o = s.taboption(tab, form.Value, 'lldp_tx_interval',
+ _('Transmit delay'),
+ _('The delay between ' +
+ 'transmissions of LLDP PDU. The default value ' +
+ 'is 30 seconds.') + '<br />' +
+ _('Suffix %s for millisecond values.').format('<code>ms</code>'));
+ o.default = 30;
+ o.placeholder = 30;
+ o.rmempty = false;
+
+ o.validate = function(section_id, value) {
+ const pattern = /^(\d+)(?:ms)?$/;
+ if (!value.match(pattern) || parseInt(value) <= 0)
+ return _('Must be a greater than zero number optionally suffixed with "ms"');
+ return true;
+ };
+
+ // LLDP tx hold
+ o = s.taboption(tab, form.Value, 'lldp_tx_hold',
+ _('Transmit hold value'),
+ _('Determines the transmitted ' +
+ 'packet TTL (== this value * transmit delay). ' +
+ 'The default value is 4 &therefore; ' +
+ 'the default TTL is 120 seconds.'));
+
+ o.datatype = 'uinteger';
+ o.default = 4;
+ o.placeholder = 4;
+ o.rmempty = false;
+
+ o.validate = function(section_id, value) {
+ if (value != parseInt(value))
+ return _('Must be a number');
+ else if (value <= 0)
+ return _('Transmit hold value must be greater than 0');
+ return true;
+ };
+
+ // Received-only mode (-r)
+ o = s.taboption(tab, form.Flag, 'readonly_mode',
+ _('Receive-only mode'),
+ _("LLDPd won't send any frames; " +
+ 'only listen to neighbors.'));
+
+ o.rmempty = false;
+ o.optional = false;
+ o.default = '0';
+ },
+
+ // -----------------------------------------------------------------------------------------
+ //
+ // Network Interfaces
+ //
+ // -----------------------------------------------------------------------------------------
+
+ /** @private */
+ populateIfacesOptions: function(s, tab, data) {
+ var o;
+ var net_devices = data[3];
+
+ // Interfaces to listen on
+ // This value: lldpd.init handles as a list value, and produces a CSV for lldpd.conf: 'configure system interface pattern'
+ o = s.taboption(tab, lldpd.CBIMultiIOSelect, 'interface',
+ _('Network IO'),
+ _('Specify which interface (not) to listen upon and send LLDPDU from. ' +
+ 'Absent any value, LLDPd uses all available physical interfaces.'));
+
+ o.value('*');
+ net_devices.forEach(nd => {
+ o.value(nd.getName());
+ o.value('!'+nd.getName());
+ o.value('!!'+nd.getName());
+ });
+ o.value('!*:*');
+ o.validate = validateioentries;
+
+ // ChassisID interfaces
+ // This value: lldpd.init handles as a list value, and produces a CSV for the -C param
+ o = s.taboption(tab, lldpd.CBIMultiIOSelect, 'cid_interface',
+ _('Network IO for chassis ID'),
+ _('Specify which interfaces (not) to use for computing chassis ID. ' +
+ 'Absent any value, all interfaces are considered. ' +
+ 'LLDPd takes the first MAC address from all the considered ' +
+ 'interfaces to compute the chassis ID.'));
+
+ o.value('*');
+ o.value('!*');
+ net_devices.forEach(nd => {
+ o.value(nd.getName());
+ o.value('!'+nd.getName());
+ o.value('!!'+nd.getName());
+ });
+ o.value('!*:*');
+ o.validate = validateioentries;
+
+ },
+
+ // -----------------------------------------------------------------------------------------
+ //
+ // Advanced Options
+ //
+ // -----------------------------------------------------------------------------------------
+
+ /** @private */
+ populateAdvancedOptions: function(s, tab, data) {
+ var o;
+
+ // SNMP agentX socket
+ // **Note**: lldpd is compiled in OpenWrt without SNMP support by default. Setting this action will then cause the lldpd daemon to stop starting and thus lldpd will stop working. To fix this, the value must then be deleted and lldpd restarted.
+ // o = s.taboption(tab, form.Value, 'agentxsocket',
+ // _('SNMP agentX socket path'),
+ // _('If the path to the socket is set, then LLDPd will enable an ' +
+ // 'SNMP subagent using AgentX protocol. This allows you to get ' +
+ // 'information about local system and remote systems through SNMP.'));
+
+ // o.rmempty = true;
+ // o.placeholder = '/var/run/agentx.sock';
+ // o.default = '';
+
+ // LLDP-MED class
+ o = s.taboption(tab, form.ListValue, 'lldp_class',
+ _('LLDP-MED device class'));
+
+ o.value('1', _('Generic Endpoint (Class I)'));
+ o.value('2', _('Media Endpoint (Class II)'));
+ o.value('3', _('Communication Device Endpoints (Class III)'));
+ o.value('4', _('Network Connectivity Device (Class IV)'));
+
+ o.default = '4';
+
+ // LLDP-MED policy
+ o = s.taboption(tab, form.Value, 'lldp_policy',
+ _('LLDP-MED policy'));
+ o.depends({lldp_class: '2'});
+ o.depends({lldp_class: '3'});
+
+ o.rmempty = true;
+ o.placeholder = 'application streaming-video';
+ o.value('application voice');
+ o.value('application voice unknown');
+ o.value('application voice-signaling');
+ o.value('application voice-signaling unknown');
+ o.value('application guest-voice');
+ o.value('application guest-voice unknown');
+ o.value('application guest-voice-signaling');
+ o.value('application guest-voice-signaling unknown');
+ o.value('application softphone-voice');
+ o.value('application softphone-voice unknown');
+ o.value('application video-conferencing');
+ o.value('application video-conferencing unknown');
+ o.value('application streaming-video');
+ o.value('application streaming-video unknown');
+ o.value('application video-signaling');
+ o.value('application video-signaling unknown');
+
+ o.validate = function(section_id, value) {
+ if (value && !value.startsWith('application '))
+ return _('Must start: application ...');
+ return true;
+ };
+
+ // LLDP-MED fast-start
+ o = s.taboption(tab, form.Flag, 'lldpmed_fast_start',
+ _('LLDP-MED fast-start'));
+
+ // LLDP-MED fast-start
+ o = s.taboption(tab, form.Value, 'lldpmed_fast_start_tx_interval',
+ _('LLDP-MED fast-start tx-interval'));
+ o.depends({lldpmed_fast_start: '1'});
+ o.datatype = 'uinteger';
+ o.placeholder = '10';
+ o.rmempty = true;
+
+ // LLDP-MED inventory TLV transmission (-i)
+ o = s.taboption(tab, form.Flag, 'lldpmed_no_inventory',
+ _('Disable LLDP-MED inventory TLV transmission'),
+ _('LLDPd will still receive (and publish using SNMP if enabled) ' +
+ 'those LLDP-MED TLV but will not send them. Use this option ' +
+ 'if you do not want to transmit sensitive information like serial numbers.'));
+
+ o.default = '0';
+
+ // Disable advertising of kernel release, version and machine. (-k)
+ o = s.taboption(tab, form.Flag, 'lldp_no_version',
+ _('Disable advertising of kernel release, version and machine'),
+ _('Kernel name (ie: Linux) will still be shared, and Inventory ' +
+ 'software version will be set to %s.').format('<code>Unknown</code>'));
+
+ o.default = '0';
+
+ // Filter neighbors (-H)
+ o = s.taboption(tab, lldpd.cbiFilterSelect, 'filter',
+ _('Specify the behaviour when detecting multiple neighbors'),
+ _('The default filter is 15. Refer to &quot;FILTERING NEIGHBORS&quot;.') + '<br />' +
+ usage);
+
+ o.default = 15;
+
+ // Force port ID subtype
+ o = s.taboption(tab, form.ListValue, 'lldp_portidsubtype',
+ _('Force port ID subtype'),
+ _('With this option, you can force the port identifier ' +
+ 'to be the interface name or the MAC address.'));
+
+ o.value('macaddress', _('Interface MAC address'));
+ o.value('ifname', _('Interface name'));
+
+ o.default = 'macaddress';
+
+ // The destination MAC address used to send LLDPDU
+ o = s.taboption(tab, form.ListValue, 'lldp_agenttype',
+ _('LLDPDU destination MAC'),
+ _('Allows an agent ' +
+ 'to control the propagation of LLDPDUs. By default, the ' +
+ 'MAC address %s is used and limits the propagation ' +
+ 'of the LLDPDU to the nearest bridge.').format('<code>01:80:c2:00:00:0e</code>'));
+
+ o.value('nearest-bridge', '01:80:c2:00:00:0e (nearest-bridge)');
+ o.value('nearest-nontpmr-bridge', '01:80:c2:00:00:03 (nearest-nontpmr-bridge)');
+ o.value('nearest-customer-bridge', '01:80:c2:00:00:00 (nearest-customer-bridge)');
+
+ o.default = 'nearest-bridge';
+ },
+
+ // -----------------------------------------------------------------------------------------
+ //
+ // Protocols Support
+ //
+ // -----------------------------------------------------------------------------------------
+
+ /** @private */
+ populateProtocolsOptions: function(s, tab, data) {
+ var o;
+
+ o = s.taboption(tab, form.SectionValue, '_protocols', form.TypedSection, 'lldpd');
+ var ss = o.subsection;
+ ss.anonymous = true;
+ ss.addremove = false;
+
+ //
+ // LLDPD
+ // Link Layer Discovery Protocol
+ //
+ ss.tab('lldp', _('LLDP'));
+ o = ss.taboption('lldp', form.Flag, 'enable_lldp',
+ _('Enable LLDP'));
+
+ o.default = '1';
+ o.rmempty = true;
+
+ o = ss.taboption('lldp', form.Flag, 'force_lldp',
+ _('Force sending LLDP packets'),
+ _('Even when there is no LLDP peer ' +
+ 'detected but there is a peer speaking another protocol detected.') + '<br />' +
+ _('By default, LLDP packets are sent when there is a peer speaking ' +
+ 'LLDP detected or when there is no peer at all.'));
+
+ o.default = '0';
+ o.rmempty = true;
+ o.depends('enable_lldp', '1');
+
+ //
+ // CDP
+ // Cisco Discovery Protocol
+ //
+ ss.tab('cdp', _('CDP'));
+ o = ss.taboption('cdp', form.Flag, 'enable_cdp',
+ _('Enable CDP'),
+ _('Enable the support of CDP protocol to deal with Cisco routers ' +
+ 'that do not speak LLDP'));
+
+ o.default = '1';
+ o.rmempty = false;
+
+ o = ss.taboption('cdp', form.ListValue, 'cdp_version',
+ _('CDP version'));
+
+ o.value('cdpv1v2', _('CDPv1 and CDPv2'));
+ o.value('cdpv2', _('Only CDPv2'));
+ o.depends('enable_cdp', '1');
+
+ o.default = 'cdpv1v2';
+
+ o = ss.taboption('cdp', form.Flag, 'force_cdp',
+ _('Send CDP packets even if no CDP peer detected'));
+
+ o.default = '0';
+ o.rmempty = true;
+ o.depends('enable_cdp', '1');
+
+ o = ss.taboption('cdp', form.Flag, 'force_cdpv2',
+ _('Force sending CDPv2 packets'));
+
+ o.default = '0';
+ o.rmempty = true;
+ o.depends({
+ force_cdp: '1',
+ enable_cdp: '1',
+ cdp_version: 'cdpv1v2'
+ });
+
+ //
+ // FDP
+ // Foundry Discovery Protocol
+ //
+ ss.tab('fdp', _('FDP'));
+ o = ss.taboption('fdp', form.Flag, 'enable_fdp',
+ _('Enable FDP'),
+ _('Enable the support of FDP protocol to deal with Foundry routers ' +
+ 'that do not speak LLDP'));
+
+ o.default = '1';
+ o.rmempty = false;
+
+ o = ss.taboption('fdp', form.Flag, 'force_fdp',
+ _('Send FDP packets even if no FDP peer detected'));
+
+ o.default = '0';
+ o.rmempty = true;
+ o.depends('enable_fdp', '1');
+
+ //
+ // EDP
+ // Extreme Discovery Protocol
+ //
+ ss.tab('edp', _('EDP'));
+ o = ss.taboption('edp', form.Flag, 'enable_edp',
+ _('Enable EDP'),
+ _('Enable the support of EDP protocol to deal with Extreme routers ' +
+ 'and switches that do not speak LLDP.'));
+
+ o.default = '1';
+ o.rmempty = false;
+
+ o = ss.taboption('edp', form.Flag, 'force_edp',
+ _('Send EDP packets even if no EDP peer detected'));
+
+ o.default = '0';
+ o.rmempty = true;
+ o.depends('enable_edp', '1');
+
+ //
+ // SONMP
+ // SynOptics Network Management Protocol
+ //
+ // a.k.a.
+ // Nortel Topology Discovery Protocol (NTDP)
+ // Nortel Discovery Protocol (NDP)
+ // Bay Network Management Protocol (BNMP)
+ // Bay Discovery Protocol (BDP)
+ //
+ ss.tab('sonmp', _('SONMP (NTDP, NDP, BNMP, BDP)'));
+ o = ss.taboption('sonmp', form.Flag, 'enable_sonmp',
+ _('Enable SONMP'),
+ _('Enable the support of SONMP protocol to deal with Nortel ' +
+ 'routers and switches that do not speak LLDP.'));
+
+ o.default = '1';
+ o.rmempty = false;
+
+ o = ss.taboption('sonmp', form.Flag, 'force_sonmp',
+ _('Send SONMP packets even if no SONMP peer detected'));
+
+ o.default = '0';
+ o.rmempty = true;
+ o.depends('enable_sonmp', '1');
+ },
+
+ /** @private */
+ populateOptions: function(s, data) {
+ var o;
+
+ s.tab('basic', _('Basic Settings'));
+ this.populateBasicOptions(s, 'basic', data);
+
+ s.tab('ifaces', _('Network Interfaces'));
+ this.populateIfacesOptions(s, 'ifaces', data);
+
+ s.tab('advanced', _('Advanced Settings'));
+ this.populateAdvancedOptions(s, 'advanced', data);
+
+ s.tab('protocols', _('Protocols Support'));
+ this.populateProtocolsOptions(s, 'protocols', data);
+ },
+
+ render: function(data) {
+ var m, s;
+
+ m = new form.Map('lldpd', _('LLDPd Settings'),
+ _('LLDPd is an implementation of IEEE 802.1ab') + ' ' +
+ '(<abbr title="Link Layer Discovery Protocol">LLDP</abbr>).' + ' ' +
+ _('On this page you may configure LLDPd parameters.'));
+
+ s = m.section(form.TypedSection, 'lldpd');
+ s.addremove = false;
+ s.anonymous = true;
+
+ this.populateOptions(s, data);
+
+ return m.render();
+ },
+});
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..b3fa30b3cb
--- /dev/null
+++ b/applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js
@@ -0,0 +1,713 @@
+/*
+ * Copyright (c) 2020 Tano Systems. All Rights Reserved.
+ * Author: Anton Kikin <a.kikin@tano-systems.com>
+ * Copyright (c) 2023-2024. All Rights Reserved.
+ * Paul Donald <newtwen+github@gmail.com>
+ */
+
+'use strict';
+'require rpc';
+'require form';
+'require lldpd';
+'require dom';
+'require poll';
+
+var callLLDPStatus = rpc.declare({
+ object: 'luci.lldpd',
+ method: 'getStatus',
+ expect: {}
+});
+
+var dataMap = {
+ local: {
+ localChassis: null,
+ },
+ remote: {
+ neighbors: 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 "&#8211;";
+
+ return E('nobr', {}, v);
+ },
+
+ /** @private */
+ renderIdType: function(v) {
+ if (typeof v === 'undefined')
+ return "&#8211;";
+
+ 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 "&#8211;";
+
+ 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 '&#8211;';
+
+ 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 '&#8211;';
+ },
+
+ /** @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 "&#8211;";
+
+ 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 "&#8211;";
+ },
+
+ /** @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
+});