Merge branch 'lldp_app'
authorPaul Donald <newtwen+github@gmail.com>
Mon, 22 Apr 2024 21:56:06 +0000 (23:56 +0200)
committerPaul Donald <newtwen+github@gmail.com>
Mon, 22 Apr 2024 21:56:06 +0000 (23:56 +0200)
12 files changed:
applications/luci-app-lldpd/Makefile [new file with mode: 0644]
applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js [new file with mode: 0644]
applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/details_hide.svg [new file with mode: 0644]
applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/details_show.svg [new file with mode: 0644]
applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd/lldpd.css [new file with mode: 0644]
applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js [new file with mode: 0644]
applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js [new file with mode: 0644]
applications/luci-app-lldpd/po/ru/lldpd.po [new file with mode: 0644]
applications/luci-app-lldpd/po/templates/lldpd.pot [new file with mode: 0644]
applications/luci-app-lldpd/root/usr/share/luci/menu.d/luci-app-lldpd.json [new file with mode: 0644]
applications/luci-app-lldpd/root/usr/share/rpcd/acl.d/luci-app-lldpd.json [new file with mode: 0644]
applications/luci-app-lldpd/root/usr/share/rpcd/ucode/luci.lldpd [new file with mode: 0644]

diff --git a/applications/luci-app-lldpd/Makefile b/applications/luci-app-lldpd/Makefile
new file mode 100644 (file)
index 0000000..7007659
--- /dev/null
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2018, Tano Systems. All Rights Reserved.
+# Anton Kikin <a.kikin@tano-systems.com>
+# Copyright (c) 2023-2024. All Rights Reserved.
+# Paul Donald <newtwen+github@gmail.com>
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI support for LLDP daemon
+LUCI_DEPENDS:=+lldpd +rpcd-mod-ucode
+
+PKG_LICENSE:=MIT
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
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 (file)
index 0000000..7b0121e
--- /dev/null
@@ -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 (file)
index 0000000..0b64b95
--- /dev/null
@@ -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 (file)
index 0000000..e51e19d
--- /dev/null
@@ -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 (file)
index 0000000..5932e41
--- /dev/null
@@ -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 (file)
index 0000000..a8e32b4
--- /dev/null
@@ -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 (file)
index 0000000..b3fa30b
--- /dev/null
@@ -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
+});
diff --git a/applications/luci-app-lldpd/po/ru/lldpd.po b/applications/luci-app-lldpd/po/ru/lldpd.po
new file mode 100644 (file)
index 0000000..32998ff
--- /dev/null
@@ -0,0 +1,652 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: luci-app-lldpd\n"
+"PO-Revision-Date: 2021-04-06 15:55+0300\n"
+"Last-Translator: Anton Kikin <a.kikin@tano-systems.com>\n"
+"Language-Team: Tano Systems LLC\n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n"
+"%100<10 || n%100>=20) ? 1 : 2);\n"
+"POT-Creation-Date: 2018-05-23 18:25+0300\n"
+"X-Generator: Poedit 2.3\n"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:52
+msgid "Administrative Status"
+msgstr "Административное состояние"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:584
+msgid "Advanced Settings"
+msgstr "Дополнительные настройки"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:196
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:207
+msgid "Age"
+msgstr "Возраст"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:57
+msgid "Ageout count"
+msgstr "Устарело"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:418
+msgid ""
+"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."
+msgstr ""
+"Данный параметр позволяет агенту контролировать распространение LLDPDU. По "
+"умолчанию используется MAC-адрес %s и ограничивает распространение LLDPDU до "
+"ближайшего моста."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:578
+msgid "Basic Settings"
+msgstr "Основные настройки"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:460
+msgid ""
+"By default, LLDP packets are sent when there is a peer speaking LLDP "
+"detected or when there is no peer at all."
+msgstr ""
+"По умолчанию пакеты LLDP отправляются, когда обнаружен сосед с поддержкой "
+"LLDP или когда вообще нет обнаруженных соседей."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:471
+msgid "CDP"
+msgstr "CDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:481
+msgid "CDP version"
+msgstr "Версия протокола CDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:483
+msgid "CDPv1 and CDPv2"
+msgstr "CDPv1 и CDPv2"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:37
+msgid "Cannot have both interface %s and its exclusion %s"
+msgstr "Оба и интерфейс %s и его исключение %s нельзя"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:317
+msgid "Capabilities"
+msgstr "Возможности"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:44
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:63
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:679
+msgid "Collecting data..."
+msgstr "Сбор данных..."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:331
+msgid "Communication Device Endpoints (Class III)"
+msgstr "Communication Device Endpoints (класс III)"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:182
+msgid ""
+"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>."
+msgstr ""
+"Если не указано, используется первый IPv4 и первый IPv6 адреса. Если указан "
+"конкретный IP-адрес, он будет использован как адрес управления без какой-"
+"либо проверки. Если вы хотите занести в черный список IPv6-адреса, вы можете "
+"использовать шаблон <code>!*:*</code>."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:59
+msgid "Delete count"
+msgstr "Удалено"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:271
+msgid "Description"
+msgstr "Описание"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:226
+msgid ""
+"Determines the transmitted packet TTL (== this value * transmit delay). The "
+"default value is 4 &therefore; the default TTL is 120 seconds."
+msgstr ""
+"Это значение используется для вычисления TTL переданных пакетов, которое "
+"является произведением этого значения и задержки передачи. Значение по "
+"умолчанию равно 4. Соответственно, значение TTL по умолчанию составляет 120 "
+"секунд."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:381
+msgid "Disable LLDP-MED inventory TLV transmission"
+msgstr "Отключить отправку инвентарной информации LLDP-MED TLV"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:390
+msgid "Disable advertising of kernel release, version and machine"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:150
+msgid "Disabled"
+msgstr "Отключено"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:685
+msgid "Discovered Neighbors"
+msgstr "Обнаруженные соседи"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:39
+msgid "Discovered chassis"
+msgstr "Обнаруженное шасси"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:40
+msgid "Discovered port"
+msgstr "Обнаруженный порт"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:531
+msgid "EDP"
+msgstr "EDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:473
+msgid "Enable CDP"
+msgstr "Включить CDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:533
+msgid "Enable EDP"
+msgstr "Включить EDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:513
+msgid "Enable FDP"
+msgstr "Включить FDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:451
+msgid "Enable LLDP"
+msgstr "Включить LLDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:559
+msgid "Enable SONMP"
+msgstr "Включить SONMP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:87
+msgid "Enable service"
+msgstr "Включить службу"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:474
+msgid ""
+"Enable the support of CDP protocol to deal with Cisco routers that do not "
+"speak LLDP"
+msgstr ""
+"Включить поддержку протокола CDP для работы с маршрутизаторами Cisco, "
+"которые не имеют поддержки LLDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:534
+msgid ""
+"Enable the support of EDP protocol to deal with Extreme routers and switches "
+"that do not speak LLDP."
+msgstr ""
+"Включить поддержку протокола EDP для работы с маршрутизаторами и "
+"коммутаторам Extreme, которые не имеют поддержки LLDP."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:514
+msgid ""
+"Enable the support of FDP protocol to deal with Foundry routers that do not "
+"speak LLDP"
+msgstr ""
+"Включить поддержку протокола FDP для работы с маршрутизаторами Foundry, "
+"которые не имеют поддержки LLDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:560
+msgid ""
+"Enable the support of SONMP protocol to deal with Nortel routers and "
+"switches that do not speak LLDP."
+msgstr ""
+"Включить поддержку протокола SONMP для работы с маршрутизаторами и "
+"коммутаторами Nortel, которые не имеют поддержки LLDP."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:458
+msgid ""
+"Even when there is no LLDP peer detected but there is a peer speaking "
+"another protocol detected."
+msgstr ""
+"Даже если соседей с поддержкой LLDP не обнаружено, но есть соседи, с "
+"поддержкой другого протокола."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:511
+msgid "FDP"
+msgstr "FDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js:160
+msgid "Filter"
+msgstr "Фильтр"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:406
+msgid "Force port ID subtype"
+msgstr "Использовать в качестве ID порта"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:497
+msgid "Force sending CDPv2 packets"
+msgstr "Форсировать отправку CDPv2 пакетов"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:457
+msgid "Force sending LLDP packets"
+msgstr "Форсировать отправку LLDP пакетов"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:329
+msgid "Generic Endpoint (Class I)"
+msgstr "Generic Endpoint (класс I)"
+
+#: applications/luci-app-lldpd/root/usr/share/rpcd/acl.d/luci-app-lldpd.json:18
+msgid "Grant access for LLDP configuration"
+msgstr "Предоставить доступ к конфигурации LLDP"
+
+#: applications/luci-app-lldpd/root/usr/share/rpcd/acl.d/luci-app-lldpd.json:3
+msgid "Grant access for LLDP status information"
+msgstr "Предоставить доступ к информации о состоянии LLDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:128
+msgid "Host location"
+msgstr "Местоположение хоста"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:274
+msgid "ID"
+msgstr "ID"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:275
+msgid "ID type"
+msgstr "Тип ID"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:114
+msgid "IP address"
+msgstr "IP-адрес"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js:161
+msgid "Incoming"
+msgstr "Входящие"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:58
+msgid "Insert count"
+msgstr "Обнаружено"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:410
+msgid "Interface MAC address"
+msgstr "MAC-адрес интерфейса"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:686
+msgid "Interface Statistics"
+msgstr "Статистика интерфейсов"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:411
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:110
+msgid "Interface name"
+msgstr "Имя интерфейса"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:391
+msgid ""
+"Kernel name (ie: Linux) will still be shared, and Inventory software version "
+"will be set to %s."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:449
+#: applications/luci-app-lldpd/root/usr/share/luci/menu.d/luci-app-lldpd.json:3
+msgid "LLDP"
+msgstr "LLDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:669
+msgid "LLDP Status"
+msgstr "Состояние LLDP"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:327
+msgid "LLDP-MED device class"
+msgstr "Класс устройства LLDP-MED"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:369
+msgid "LLDP-MED fast-start"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:373
+msgid "LLDP-MED fast-start tx-interval"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:338
+msgid "LLDP-MED policy"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:417
+msgid "LLDPDU destination MAC"
+msgstr "MAC-адрес назначения, используемый для отправки LLDPDU."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:594
+msgid "LLDPd Settings"
+msgstr "Настройки LLDPd"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:595
+msgid "LLDPd is an implementation of IEEE 802.1ab"
+msgstr "LLDPd это программная реализация стандарта IEEE 802.1ab"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:382
+msgid ""
+"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."
+msgstr ""
+"LLDPd будет по прежнему получать (и публиковать, используя SNMP, если он "
+"включен) информацию LLDP-MED TLV, но не будет отправлять. Используйте эту "
+"опцию, если вы не хотите передавать важную информацию, такую как серийные "
+"номера."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:247
+msgid "LLDPd won't send any frames; only listen to neighbors."
+msgstr ""
+"С этой опцией LLDPd не будет отправлять какие-либо пакеты. LLDPd будет "
+"только прослушивать сеть для обнаружения соседей."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:674
+msgid "Local Chassis"
+msgstr "Локальное шасси"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:112
+msgid "Local ID"
+msgstr "Локально заданный ID"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:37
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:50
+msgid "Local interface"
+msgstr "Локальный интерфейс"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:108
+msgid "MAC address"
+msgstr "MAC-адрес"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:231
+msgid "MFS"
+msgstr "MFS"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:293
+msgid "Management IP(s)"
+msgstr "IP-адреса"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:330
+msgid "Media Endpoint (Class II)"
+msgstr "Media Endpoint (класс II)"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:219
+msgid "Must be a greater than zero number optionally suffixed with \"ms\""
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:238
+msgid "Must be a number"
+msgstr "Должно быть числом"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:137
+msgid "Must start: 'coordinate ...', 'address ...' or 'elin ...'"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:363
+msgid "Must start: application ..."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:195
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:206
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:268
+msgid "Name"
+msgstr "Имя"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:332
+msgid "Network Connectivity Device (Class IV)"
+msgstr "Network Connectivity Device (класс IV)"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:269
+msgid "Network IO"
+msgstr "Сетевые интерфейсы"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:285
+msgid "Network IO for chassis ID"
+msgstr "Сетевые интерфейсы, используемые для вычисления ID шасси (chassis ID)"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:581
+msgid "Network Interfaces"
+msgstr "Сетевые интерфейсы"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:601
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:658
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:662
+msgid "No data to display"
+msgstr "Нет данных для отображения"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:597
+msgid "On this page you may configure LLDPd parameters."
+msgstr "На данной странице можно настроить параметры LLDPd."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:484
+msgid "Only CDPv2"
+msgstr "Только CDPv2"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js:162
+msgid "Outgoing"
+msgstr "Исходящие"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:115
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:122
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:146
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:157
+msgid "Override %s."
+msgstr "Переопределить %s."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:129
+msgid "Override the announced location of the host."
+msgstr "Переопределите объявленное местоположение хоста."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:214
+msgid "Port ID"
+msgstr "ID порта"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:217
+msgid "Port ID type"
+msgstr "Тип ID порта"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:222
+msgid "Port description"
+msgstr "Описание порта"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:38
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:51
+msgid "Protocol"
+msgstr "Протокол"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:587
+msgid "Protocols Support"
+msgstr "Поддержка протоколов"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:246
+msgid "Receive-only mode"
+msgstr "Режим «только приём»"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:54
+msgid "Rx"
+msgstr "Rx"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:144
+msgid "Rx and Tx"
+msgstr "Rx и Tx"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:146
+msgid "Rx only"
+msgstr "Только Rx"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:56
+msgid "Rx unrecognized"
+msgstr "Rx (не распознано)"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:557
+msgid "SONMP (NTDP, NDP, BNMP, BDP)"
+msgstr "SONMP (NTDP, NDP, BNMP, BDP)"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:33
+msgid "See syntax <a %s>here</a>."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:490
+msgid "Send CDP packets even if no CDP peer detected"
+msgstr "Отправлять CDP пакеты даже если не обнаружено соседей с CDP протоколом"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:541
+msgid "Send EDP packets even if no EDP peer detected"
+msgstr "Отправлять EDP пакеты даже если не обнаружено соседей с EDP протоколом"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:521
+msgid "Send FDP packets even if no FDP peer detected"
+msgstr "Отправлять FDP пакеты даже если не обнаружено соседей с FDP протоколом"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:567
+msgid "Send SONMP packets even if no SONMP peer detected"
+msgstr ""
+"Отправлять SONMP пакеты даже если не обнаружено соседей с SONMP протоколом"
+
+#: applications/luci-app-lldpd/root/usr/share/luci/menu.d/luci-app-lldpd.json:28
+msgid "Settings"
+msgstr "Настройки"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:398
+msgid "Specify the behaviour when detecting multiple neighbors"
+msgstr "Поведение при обнаружении нескольких соседей"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:270
+msgid ""
+"Specify which interface (not) to listen upon and send LLDPDU from. Absent "
+"any value, LLDPd uses all available physical interfaces."
+msgstr ""
+"Укажите какие интерфейсы должны прослушивать и отправлять LLDPDU. Если "
+"интерфейсы не указаны, LLDPd будет использовать все доступные физические "
+"интерфейсы."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:286
+msgid ""
+"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."
+msgstr ""
+"Укажите какие интерфейсы необходимо использовать для вычисления "
+"идентификатора шасси (chassis ID). Если интерфейсы не указаны, будут "
+"иcпользованы все интерфейсы. LLDPd получит первый MAC-адрес из всех "
+"доступных интерфейсов для вычисления ID шасси."
+
+#: applications/luci-app-lldpd/root/usr/share/luci/menu.d/luci-app-lldpd.json:16
+msgid "Status"
+msgstr "Состояние"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:211
+msgid "Suffix %s for millisecond values."
+msgstr "Прибавляй суффикс %s для значений миллисекунд."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:156
+msgid "System capabilities"
+msgstr "Системные возможности"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:151
+msgid "System capability advertisements"
+msgstr "Объявления системных возможност"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:114
+msgid "System description"
+msgstr "Описание системы"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:121
+msgid "System hostname"
+msgstr "Имя системы"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:181
+msgid "System management IO"
+msgstr "Адреса управления данной системы"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:175
+msgid "System management IO advertisements"
+msgstr "Объявления адресов управления данной системы"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:145
+msgid "System platform description"
+msgstr "Описание платформы"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:226
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:228
+msgid "TTL"
+msgstr "TTL"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:147
+msgid "The default description is the kernel name (Linux)."
+msgstr "По умолчанию используется имя используемого ядра (Linux)."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:399
+msgid "The default filter is 15. Refer to &quot;FILTERING NEIGHBORS&quot;."
+msgstr ""
+"По умолчанию используется фильтр 15. Более подробную информацию о данной "
+"опции можно посмотреть в разделе «FILTERING NEIGHBORS»."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:158
+msgid "The default is derived from kernel information."
+msgstr "Значение по умолчанию получено из информации о ядре."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:208
+msgid ""
+"The delay between transmissions of LLDP PDU. The default value is 30 seconds."
+msgstr ""
+"Задержка между двумя передачами LLDP PDU. Значение по умолчанию составляет "
+"30 секунд."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:670
+msgid ""
+"This page allows you to see discovered LLDP neighbors, local interfaces "
+"statistics and local chassis information."
+msgstr ""
+"На данной странице вы можете посмотреть таблицу обнаруженных соседей LLDP, "
+"статистику локальных интерфейсов и информацию о локальном шасси."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:207
+msgid "Transmit delay"
+msgstr "Задержка отправки"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:225
+msgid "Transmit hold value"
+msgstr "Значение «transmit hold»"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:240
+msgid "Transmit hold value must be greater than 0"
+msgstr "Значение «transmit hold» должно быть больше 0"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:53
+msgid "Tx"
+msgstr "Tx"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:55
+msgid "Tx discarded"
+msgstr "Tx (отброшено)"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:148
+msgid "Tx only"
+msgstr "Только Tx"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:152
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:260
+msgid "Unknown"
+msgstr "Неизвестно"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:407
+msgid ""
+"With this option, you can force the port identifier to be the interface name "
+"or the MAC address."
+msgstr ""
+"Данная настройка позволяет принудительно использовать в качестве "
+"идентификатор порта имя интерфейса или MAC-адрес."
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:305
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:314
+msgid "disabled"
+msgstr "выключено"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js:44
+msgid "enable filter"
+msgstr "включить фильтр"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:305
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:314
+msgid "enabled"
+msgstr "включено"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js:46
+msgid "keep only one neighbor"
+msgstr "хранить только одного соседа"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js:45
+msgid "keep only one protocol"
+msgstr "хранить только один протокол"
diff --git a/applications/luci-app-lldpd/po/templates/lldpd.pot b/applications/luci-app-lldpd/po/templates/lldpd.pot
new file mode 100644 (file)
index 0000000..e751596
--- /dev/null
@@ -0,0 +1,595 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:52
+msgid "Administrative Status"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:584
+msgid "Advanced Settings"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:196
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:207
+msgid "Age"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:57
+msgid "Ageout count"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:418
+msgid ""
+"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."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:578
+msgid "Basic Settings"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:460
+msgid ""
+"By default, LLDP packets are sent when there is a peer speaking LLDP "
+"detected or when there is no peer at all."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:471
+msgid "CDP"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:481
+msgid "CDP version"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:483
+msgid "CDPv1 and CDPv2"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:37
+msgid "Cannot have both interface %s and its exclusion %s"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:317
+msgid "Capabilities"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:44
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:63
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:679
+msgid "Collecting data..."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:331
+msgid "Communication Device Endpoints (Class III)"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:182
+msgid ""
+"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>."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:59
+msgid "Delete count"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:271
+msgid "Description"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:226
+msgid ""
+"Determines the transmitted packet TTL (== this value * transmit delay). The "
+"default value is 4 &therefore; the default TTL is 120 seconds."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:381
+msgid "Disable LLDP-MED inventory TLV transmission"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:390
+msgid "Disable advertising of kernel release, version and machine"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:150
+msgid "Disabled"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:685
+msgid "Discovered Neighbors"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:39
+msgid "Discovered chassis"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:40
+msgid "Discovered port"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:531
+msgid "EDP"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:473
+msgid "Enable CDP"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:533
+msgid "Enable EDP"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:513
+msgid "Enable FDP"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:451
+msgid "Enable LLDP"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:559
+msgid "Enable SONMP"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:87
+msgid "Enable service"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:474
+msgid ""
+"Enable the support of CDP protocol to deal with Cisco routers that do not "
+"speak LLDP"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:534
+msgid ""
+"Enable the support of EDP protocol to deal with Extreme routers and switches "
+"that do not speak LLDP."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:514
+msgid ""
+"Enable the support of FDP protocol to deal with Foundry routers that do not "
+"speak LLDP"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:560
+msgid ""
+"Enable the support of SONMP protocol to deal with Nortel routers and "
+"switches that do not speak LLDP."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:458
+msgid ""
+"Even when there is no LLDP peer detected but there is a peer speaking "
+"another protocol detected."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:511
+msgid "FDP"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js:160
+msgid "Filter"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:406
+msgid "Force port ID subtype"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:497
+msgid "Force sending CDPv2 packets"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:457
+msgid "Force sending LLDP packets"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:329
+msgid "Generic Endpoint (Class I)"
+msgstr ""
+
+#: applications/luci-app-lldpd/root/usr/share/rpcd/acl.d/luci-app-lldpd.json:18
+msgid "Grant access for LLDP configuration"
+msgstr ""
+
+#: applications/luci-app-lldpd/root/usr/share/rpcd/acl.d/luci-app-lldpd.json:3
+msgid "Grant access for LLDP status information"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:128
+msgid "Host location"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:274
+msgid "ID"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:275
+msgid "ID type"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:114
+msgid "IP address"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js:161
+msgid "Incoming"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:58
+msgid "Insert count"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:410
+msgid "Interface MAC address"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:686
+msgid "Interface Statistics"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:411
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:110
+msgid "Interface name"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:391
+msgid ""
+"Kernel name (ie: Linux) will still be shared, and Inventory software version "
+"will be set to %s."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:449
+#: applications/luci-app-lldpd/root/usr/share/luci/menu.d/luci-app-lldpd.json:3
+msgid "LLDP"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:669
+msgid "LLDP Status"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:327
+msgid "LLDP-MED device class"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:369
+msgid "LLDP-MED fast-start"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:373
+msgid "LLDP-MED fast-start tx-interval"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:338
+msgid "LLDP-MED policy"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:417
+msgid "LLDPDU destination MAC"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:594
+msgid "LLDPd Settings"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:595
+msgid "LLDPd is an implementation of IEEE 802.1ab"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:382
+msgid ""
+"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."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:247
+msgid "LLDPd won't send any frames; only listen to neighbors."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:674
+msgid "Local Chassis"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:112
+msgid "Local ID"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:37
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:50
+msgid "Local interface"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:108
+msgid "MAC address"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:231
+msgid "MFS"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:293
+msgid "Management IP(s)"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:330
+msgid "Media Endpoint (Class II)"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:219
+msgid "Must be a greater than zero number optionally suffixed with \"ms\""
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:238
+msgid "Must be a number"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:137
+msgid "Must start: 'coordinate ...', 'address ...' or 'elin ...'"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:363
+msgid "Must start: application ..."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:195
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:206
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:268
+msgid "Name"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:332
+msgid "Network Connectivity Device (Class IV)"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:269
+msgid "Network IO"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:285
+msgid "Network IO for chassis ID"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:581
+msgid "Network Interfaces"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:601
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:658
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:662
+msgid "No data to display"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:597
+msgid "On this page you may configure LLDPd parameters."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:484
+msgid "Only CDPv2"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js:162
+msgid "Outgoing"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:115
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:122
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:146
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:157
+msgid "Override %s."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:129
+msgid "Override the announced location of the host."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:214
+msgid "Port ID"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:217
+msgid "Port ID type"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:222
+msgid "Port description"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:38
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:51
+msgid "Protocol"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:587
+msgid "Protocols Support"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:246
+msgid "Receive-only mode"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:54
+msgid "Rx"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:144
+msgid "Rx and Tx"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:146
+msgid "Rx only"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:56
+msgid "Rx unrecognized"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:557
+msgid "SONMP (NTDP, NDP, BNMP, BDP)"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:33
+msgid "See syntax <a %s>here</a>."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:490
+msgid "Send CDP packets even if no CDP peer detected"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:541
+msgid "Send EDP packets even if no EDP peer detected"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:521
+msgid "Send FDP packets even if no FDP peer detected"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:567
+msgid "Send SONMP packets even if no SONMP peer detected"
+msgstr ""
+
+#: applications/luci-app-lldpd/root/usr/share/luci/menu.d/luci-app-lldpd.json:28
+msgid "Settings"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:398
+msgid "Specify the behaviour when detecting multiple neighbors"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:270
+msgid ""
+"Specify which interface (not) to listen upon and send LLDPDU from. Absent "
+"any value, LLDPd uses all available physical interfaces."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:286
+msgid ""
+"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."
+msgstr ""
+
+#: applications/luci-app-lldpd/root/usr/share/luci/menu.d/luci-app-lldpd.json:16
+msgid "Status"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:211
+msgid "Suffix %s for millisecond values."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:156
+msgid "System capabilities"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:151
+msgid "System capability advertisements"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:114
+msgid "System description"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:121
+msgid "System hostname"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:181
+msgid "System management IO"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:175
+msgid "System management IO advertisements"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:145
+msgid "System platform description"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:226
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:228
+msgid "TTL"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:147
+msgid "The default description is the kernel name (Linux)."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:399
+msgid "The default filter is 15. Refer to &quot;FILTERING NEIGHBORS&quot;."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:158
+msgid "The default is derived from kernel information."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:208
+msgid ""
+"The delay between transmissions of LLDP PDU. The default value is 30 seconds."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:670
+msgid ""
+"This page allows you to see discovered LLDP neighbors, local interfaces "
+"statistics and local chassis information."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:207
+msgid "Transmit delay"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:225
+msgid "Transmit hold value"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:240
+msgid "Transmit hold value must be greater than 0"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:53
+msgid "Tx"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:55
+msgid "Tx discarded"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:148
+msgid "Tx only"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:152
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:260
+msgid "Unknown"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/config.js:407
+msgid ""
+"With this option, you can force the port identifier to be the interface name "
+"or the MAC address."
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:305
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:314
+msgid "disabled"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js:44
+msgid "enable filter"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:305
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/view/lldpd/status.js:314
+msgid "enabled"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js:46
+msgid "keep only one neighbor"
+msgstr ""
+
+#: applications/luci-app-lldpd/htdocs/luci-static/resources/lldpd.js:45
+msgid "keep only one protocol"
+msgstr ""
diff --git a/applications/luci-app-lldpd/root/usr/share/luci/menu.d/luci-app-lldpd.json b/applications/luci-app-lldpd/root/usr/share/luci/menu.d/luci-app-lldpd.json
new file mode 100644 (file)
index 0000000..6944faf
--- /dev/null
@@ -0,0 +1,38 @@
+{
+       "admin/services/lldpd": {
+               "title": "LLDP",
+               "order": 80,
+               "action": {
+                       "type": "firstchild"
+               },
+               "depends": {
+                       "uci": {
+                               "lldpd": true
+                       }
+               }
+       },
+
+       "admin/services/lldpd/status": {
+               "title": "Status",
+               "order": 10,
+               "action": {
+                       "type": "view",
+                       "path": "lldpd/status"
+               },
+               "depends": {
+                       "acl": [ "luci-app-lldpd-status" ]
+               }
+       },
+
+       "admin/services/lldpd/config": {
+               "title": "Settings",
+               "order": 20,
+               "action": {
+                       "type": "view",
+                       "path": "lldpd/config"
+               },
+               "depends": {
+                       "acl": [ "luci-app-lldpd-config" ]
+               }
+       }
+}
diff --git a/applications/luci-app-lldpd/root/usr/share/rpcd/acl.d/luci-app-lldpd.json b/applications/luci-app-lldpd/root/usr/share/rpcd/acl.d/luci-app-lldpd.json
new file mode 100644 (file)
index 0000000..240cd24
--- /dev/null
@@ -0,0 +1,43 @@
+{
+       "luci-app-lldpd-status": {
+               "description": "Grant access for LLDP status information",
+               "read": {
+                       "ubus": {
+                               "luci.lldpd": [
+                                       "getStatus"
+                               ]
+                       },
+                       "uci": [
+                               "lldpd",
+                               "luci"
+                       ]
+               }
+       },
+
+       "luci-app-lldpd-config": {
+               "description": "Grant access for LLDP configuration",
+               "read": {
+                       "uci": [
+                               "lldpd"
+                       ],
+                       "ubus": {
+                               "luci.lldpd": [
+                                       "getStatus"
+                               ],
+                               "luci": [
+                                       "getInitList"
+                               ]
+                       }
+               },
+               "write": {
+                       "uci": [
+                               "lldpd"
+                       ],
+                       "ubus": {
+                               "luci": [
+                                       "setInitAction"
+                               ]
+                       }
+               }
+       }
+}
diff --git a/applications/luci-app-lldpd/root/usr/share/rpcd/ucode/luci.lldpd b/applications/luci-app-lldpd/root/usr/share/rpcd/ucode/luci.lldpd
new file mode 100644 (file)
index 0000000..fff7ac5
--- /dev/null
@@ -0,0 +1,22 @@
+'use strict';
+
+import { popen } from 'fs';
+
+function lldpcli_json(section) {
+       return json(popen(`lldpcli -f json0 show ${section}`, 'r'));
+}
+
+return {
+       'luci.lldpd': {
+               getStatus: {
+                       call: function() {
+                               return {
+                                       statistics: lldpcli_json("statistics"),
+                                       neighbors:  lldpcli_json("neighbors details"),
+                                       interfaces: lldpcli_json("interfaces"),
+                                       chassis:    lldpcli_json("chassis")
+                               };
+                       }
+               }
+       }
+};