luci-app-lldpd: Create from Tano Systems 6456/head
authorMarek Küthe <m.k@mk16.de>
Tue, 4 Jul 2023 09:42:23 +0000 (11:42 +0200)
committerMarek Küthe <m.k@mk16.de>
Sat, 21 Oct 2023 12:50:19 +0000 (14:50 +0200)
This is a copy of https://github.com/tano-systems/luci-app-tn-lldpd, which is licensed under the MIT License.

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: Rename luci-app-tn-lldpd to luci-app-lldpd

The original version of Tanosystem has a naming scheme which does not correspond to the standard naming scheme in OpenWrt LuCi. Therefore the renaming.

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: Fix bug not getting the current lldpd status

Specifying the arguments in the wrong order can (and has in my tests) resulted in errors. So you can see in the man page that flags like -f come first and then the command.

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: Disable option to enable SNMP agent

SNMP agent support is not enabled by default in lldpd. This can (and has in my tests) cause LLDP to stop working. See comment in source code.

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: Add option to disable sending sensitive information

Added an option that sets the "-k" flag, which results in less sensitive information being sent. See man pages and description in source code.

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: Add license information to Makefile

All of Tano Systems source code for the app is licensed under the MIT License. This has now been indicated accordingly in the Makefile.

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: Remove old footer from Tano Systems

The app from Tano Systems appears to include a footer. Since most LuCi apps do not, I have removed it.

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: Bug fix caused that the management IP address could not be set.

The TanoWrt fork calls the option to set the management IP addresses "lldp_sys_mgmt_ip". However, in OpenWrt it is called "lldp_mgmt_ip".

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: Improve style

Remove double space

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: Adding the Lua dependency

The rpcd script used by the Luci app is written in Lua. Therefore, runtime dependencies for Lua must exist when using the Luci app.

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: Remove old files

Since the app is called luci-app-lldpd and not luci-app-tn-lldpd, the files for the TanoWrt app are no longer necessary.

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: RPCd backend change from Lua to ucode

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: Update filter matrix

The old filter matrix was based on the man pages, but the selected value in the range 1-20 was also assigned to the configuration and thus lldpd was configured incorrectly. lldpd expects a value in the range 0-19. The webapp matrix has now been changed so that the values 0-19 also appear there.
This leads to a unification of configuration and webapp.

Signed-off-by: Marek Küthe <m.k@mk16.de>
luci-app-lldpd: Add location parameter

Signed-off-by: Marek Küthe <m.k@mk16.de>
13 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/etc/uci-defaults/40_luci-lldpd [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/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..e5ab43e
--- /dev/null
@@ -0,0 +1,15 @@
+#
+# Copyright (c) 2018, Tano Systems. All Rights Reserved.
+# Anton Kikin <a.kikin@tano-systems.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..bc39ed5
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018-2020, Tano Systems LLC. All Rights Reserved.
+ * Anton Kikin <a.kikin@tano-systems.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
+ */
+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; enable filter') + '</li>' +
+                           '<li>' + _('P &mdash; keep only one protocol') + '</li>' +
+                           '<li>' + _('N &mdash; keep only one neighbor') + '</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) {
+               var selected = parseInt(cfgvalue) - 1;
+
+               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', {}, renderFilterVal(i, 0)),
+                               E('td', {}, renderFilterVal(i, 1)),
+                               E('td', {}, renderFilterVal(i, 2)),
+                               E('td', {}, renderFilterVal(i, 3)),
+                               E('td', {}, renderFilterVal(i, 4)),
+                               E('td', {}, 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;
+       },
+});
+
+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,
+       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..7607870
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.1" viewBox="0 0 18 11.12" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<metadata>
+<rdf:RDF>
+<cc:Work rdf:about="">
+<dc:format>image/svg+xml</dc:format>
+<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+</cc:Work>
+</rdf:RDF>
+</metadata>
+<path d="m 9,11.12 9,-9 L 15.88,0 9,6.88 2.12,0 0,2.12 Z"/>
+<path d="m-9-12h36v36h-36z" fill="none"/>
+</svg>
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..867ef4b
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<metadata>
+<rdf:RDF>
+<cc:Work rdf:about="">
+<dc:format>image/svg+xml</dc:format>
+<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+<dc:title/>
+</cc:Work>
+</rdf:RDF>
+</metadata>
+<path d="m4.585 2.83 9.17 9.17-9.17 9.17 2.83 2.83 12-12-12-12z"/>
+<path d="m0-24h48v48h-48z" fill="none"/>
+</svg>
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..0f3c0e1
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2020, Tano Systems LLC. All Rights Reserved.
+ * Author: Anton Kikin <a.kikin@tano-systems.com>
+ */
+
+/*
+ * Filter select widget
+ */
+table.lldpd-filter td,
+table.lldpd-filter th {
+       border: 1px solid #ccc !important;
+       padding: 2px 10px 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: #eeeeee;
+}
+
+/*
+ * Parameters
+ */
+.lldpd-params {
+       column-count: 2;
+       -moz-column-count: 2;
+       -webkit-column-count: 2;
+       column-gap: 24px;
+       -moz-column-gap: 24px;
+       -webkit-column-gap: 24px;
+}
+
+@media only screen and (max-width: 850px) {
+       .lldpd-params {
+               column-count: 1;
+               -moz-column-count: 1;
+               -webkit-column-count: 1;
+       }
+}
+
+.lldpd-params > div {
+       display: grid;
+       grid-template-columns: 1fr auto;
+       border-bottom: 1px solid #e6e6e6;
+       padding: 0 8px;
+       -webkit-column-break-inside: avoid;
+       -moz-column-break-inside: avoid;
+       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;
+       -moz-column-count: 1;
+       -webkit-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;
+       width: -moz-fit-content !important;
+       width: -webkit-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..a4761f9
--- /dev/null
@@ -0,0 +1,484 @@
+/*
+ * Copyright (c) 2020 Tano Systems LLC. All Rights Reserved.
+ * Author: Anton Kikin <a.kikin@tano-systems.com>
+ */
+
+'use strict';
+'require rpc';
+'require form';
+'require lldpd';
+'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 }
+});
+
+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')
+               ]);
+       },
+
+       // -----------------------------------------------------------------------------------------
+       //
+       //   Basic Options
+       //
+       // -----------------------------------------------------------------------------------------
+
+       /** @private */
+       populateBasicOptions: function(s, tab, data) {
+               var o;
+               var serviceEnabled = data[0];
+
+               // 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('mstpd', section_id, 'enabled', value);
+
+                       if (value == '1') {
+                               // Enable and start
+                               return callInitAction('lldpd', 'enable').then(function() {
+                                       return callInitAction('lldpd', 'start');
+                               });
+                       }
+                       else {
+                               // Stop and disable
+                               return callInitAction('lldpd', 'stop').then(function() {
+                                       return callInitAction('lldpd', 'disable');
+                               });
+                       }
+               };
+
+               // System description
+               o = s.taboption(tab, form.Value, 'lldp_description',
+                       _('System description'),
+                       _('Override system description with the provided description.'));
+
+               o.placeholder = 'System description';
+
+               // System hostname
+               o = s.taboption(tab, form.Value, 'lldp_hostname',
+                       _('System hostname'),
+                       _('Override system hostname with the provided value.'));
+
+               o.placeholder = 'System hostname';
+
+               // Host location
+               o = s.taboption(tab, form.Value, 'lldp_location',
+                       _('Host location'),
+                       _('Override the location of the host announced by lldp.'));
+
+               o.placeholder = 'address country EU';
+
+               // Platform
+               o = s.taboption(tab, form.Value, 'lldp_platform',
+                       _('System platform description'),
+                       _('Override the platform description with the provided value. ' +
+                         'The default description is the kernel name (Linux).'));
+
+               o.placeholder = 'System platform description';
+
+               // Management addresses of this system
+               o = s.taboption(tab, form.Value, 'lldp_mgmt_ip',
+                       _('Management addresses of this system'),
+                       _('Specify the management addresses of this system. ' +
+                         'If not specified, the first IPv4 and the first ' +
+                         'IPv6 are used. If an exact IP address is provided, it is used ' +
+                         'as a management address without any check. If you want to ' +
+                         'blacklist IPv6 addresses, you can use <code>!*:*</code>. ' +
+                         'See more details about available patterns ' +
+                         '<a href=\"https://vincentbernat.github.io/lldpd/usage.html\">here</a>.'));
+
+               o.placeholder = 'Management addresses';
+
+               // LLDP tx interval
+               o = s.taboption(tab, form.Value, 'lldp_tx_interval',
+                       _('Transmit delay'),
+                       _('The transmit delay is the delay between two ' +
+                         'transmissions of LLDP PDU. The default value ' +
+                         'is 30 seconds.'));
+
+               o.datatype = 'uinteger';
+               o.default = 30;
+               o.placeholder = 30;
+               o.rmempty = false;
+
+               o.validate = function(section_id, value) {
+                       if (value != parseInt(value))
+                               return _('Must be a number');
+                       else if (value <= 0)
+                               return _('Transmit delay must be greater than 0');
+                       return true;
+               };
+
+               // LLDP tx hold
+               o = s.taboption(tab, form.Value, 'lldp_tx_hold',
+                       _('Transmit hold value'),
+                       _('This value is used to compute the TTL of transmitted ' +
+                         'packets which is the product of this value and of the ' +
+                         'transmit delay. The default value is 4 and 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',
+                       _('Enable receive-only mode'),
+                       _('With this option, LLDPd will not send any frames. ' +
+                         'It will only listen to neighbors.'));
+
+               o.rmempty = false;
+               o.optional = false;
+               o.default = '0';
+       },
+
+       // -----------------------------------------------------------------------------------------
+       //
+       //   Network Interfaces
+       //
+       // -----------------------------------------------------------------------------------------
+
+       /** @private */
+       populateIfacesOptions: function(s, tab, data) {
+               var o;
+
+               // Interfaces to listen on
+               o = s.taboption(tab, widgets.DeviceSelect, 'interface',
+                       _('Network interfaces'),
+                       _('Specify which interface to listen and send LLDPDU to. ' +
+                         'If no interfaces is specified, LLDPd will use all available physical interfaces.'));
+
+               o.nobridges = true;
+               o.rmempty   = true;
+               o.multiple  = true;
+               o.nocreate  = true;
+               o.noaliases = true;
+               o.networks  = null;
+
+               // ChassisID interfaces
+               o = s.taboption(tab, widgets.DeviceSelect, 'cid_interface',
+                       _('Network interfaces for chassis ID computing'),
+                       _('Specify which interfaces to use for computing chassis ID. ' +
+                         'If no interfaces is specified, all interfaces are considered. ' +
+                         'LLDPd will take the first MAC address from all the considered ' +
+                         'interfaces to compute the chassis ID.'));
+
+               o.nobridges = false;
+               o.rmempty   = true;
+               o.multiple  = true;
+               o.nocreate  = true;
+               o.noaliases = true;
+               o.networks  = null;
+       },
+
+       // -----------------------------------------------------------------------------------------
+       //
+       //   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 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 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 don\'t want to transmit sensible 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 \'Unknown\'.'));
+
+               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. For more details see \"FILTERING NEIGHBORS\" section ' +
+                         '<a href=\"https://vincentbernat.github.io/lldpd/usage.html\">here</a>.'));
+
+               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',
+                       _('The destination MAC address used to send LLDPDU'),
+                       _('The destination MAC address used to send LLDPDU allows an agent ' +
+                         'to control the propagation of LLDPDUs. By default, the ' +
+                         '<code>01:80:c2:00:00:0e</code> MAC address is used and limit the propagation ' +
+                         'of the LLDPDU to the nearest bridge.'));
+
+               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 to send LLDP packets'),
+                       _('Force to send LLDP packets even when there is no LLDP peer ' +
+                         'detected but there is a peer speaking another protocol detected. ' +
+                         '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 a 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..96847e1
--- /dev/null
@@ -0,0 +1,711 @@
+/*
+ * Copyright (c) 2020 Tano Systems. All Rights Reserved.
+ * Author: Anton Kikin <a.kikin@tano-systems.com>
+ */
+
+'use strict';
+'require rpc';
+'require form';
+'require lldpd';
+'require dom';
+'require poll';
+
+var callLLDPStatus = rpc.declare({
+       object: 'lldpd',
+       method: 'getStatus',
+       expect: {}
+});
+
+var dataMap = {
+       local: {
+               localChassis: null,
+       },
+       remote: {
+               neightbors: null,
+               statistics: null,
+       },
+};
+
+return L.view.extend({
+       __init__: function() {
+               this.super('__init__', arguments);
+
+               this.rowsUnfolded = {};
+
+               this.tableNeighbors = E('div', { 'class': 'table lldpd-table' }, [
+                       E('div', { 'class': 'tr table-titles' }, [
+                               E('div', { 'class': 'th left top' }, _('Local interface')),
+                               E('div', { 'class': 'th left top' }, _('Protocol')),
+                               E('div', { 'class': 'th left top' }, _('Discovered chassis')),
+                               E('div', { 'class': 'th left top' }, _('Discovered port')),
+                       ]),
+                       E('div', { 'class': 'tr center placeholder' }, [
+                               E('div', { 'class': 'td' }, E('em', { 'class': 'spinning' },
+                                       _('Collecting data...'))),
+                       ])
+               ]);
+
+               this.tableStatistics = E('div', { 'class': 'table lldpd-table' }, [
+                       E('div', { 'class': 'tr table-titles' }, [
+                               E('div', { 'class': 'th left top' }, _('Local interface')),
+                               E('div', { 'class': 'th left top' }, _('Protocol')),
+                               E('div', { 'class': 'th left top' }, _('Administrative Status')),
+                               E('div', { 'class': 'th right top' }, _('Tx')),
+                               E('div', { 'class': 'th right top' }, _('Rx')),
+                               E('div', { 'class': 'th right top' }, _('Tx discarded')),
+                               E('div', { 'class': 'th right top' }, _('Rx unrecognized')),
+                               E('div', { 'class': 'th right top' }, _('Ageout count')),
+                               E('div', { 'class': 'th right top' }, _('Insert count')),
+                               E('div', { 'class': 'th right top' }, _('Delete count')),
+                       ]),
+                       E('div', { 'class': 'tr center placeholder' }, [
+                               E('div', { 'class': 'td' }, E('em', { 'class': 'spinning' },
+                                       _('Collecting data...'))),
+                       ])
+               ]);
+
+               // Inject CSS
+               var head = document.getElementsByTagName('head')[0];
+               var css = E('link', { 'href':
+                       L.resource('lldpd/lldpd.css')
+                               + '?v=#PKG_VERSION', 'rel': 'stylesheet' });
+
+               head.appendChild(css);
+       },
+
+       load: function() {
+               return Promise.all([
+                       L.resolveDefault(callLLDPStatus(), {}),
+                       lldpd.init(),
+               ]);
+       },
+
+       /** @private */
+       renderParam: function(param, value) {
+               if (typeof value === 'undefined')
+                       return '';
+
+               return E('div', {}, [
+                       E('span', { 'class': 'lldpd-param' }, param),
+                       E('span', { 'class': 'lldpd-param-value' }, value)
+               ]);
+       },
+
+       /** @private */
+       renderAge: function(v) {
+               if (typeof v === 'undefined')
+                       return "&#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..be60566
--- /dev/null
@@ -0,0 +1,629 @@
+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"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:52
+msgid "Administrative Status"
+msgstr "Административное состояние"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:445
+msgid "Advanced Settings"
+msgstr "Дополнительные настройки"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:196
+#: htdocs/luci-static/resources/view/lldpd/status.js:207
+msgid "Age"
+msgstr "Возраст"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:57
+msgid "Ageout count"
+msgstr "Устарело"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:439
+msgid "Basic Settings"
+msgstr "Основные настройки"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:332
+msgid "CDP"
+msgstr "CDP"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:342
+msgid "CDP version"
+msgstr "Версия протокола CDP"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:344
+msgid "CDPv1 and CDPv2"
+msgstr "CDPv1 и CDPv2"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:317
+msgid "Capabilities"
+msgstr "Возможности"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:44
+#: htdocs/luci-static/resources/view/lldpd/status.js:63
+#: htdocs/luci-static/resources/view/lldpd/status.js:682
+msgid "Collecting data..."
+msgstr "Сбор данных..."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:243
+msgid "Communication Device Endpoints (Class III)"
+msgstr "Communication Device Endpoints (класс III)"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:59
+msgid "Delete count"
+msgstr "Удалено"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:271
+msgid "Description"
+msgstr "Описание"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:250
+msgid "Disable LLDP-MED inventory TLV transmission"
+msgstr "Отключить отправку инвентарной информации LLDP-MED TLV"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:150
+msgid "Disabled"
+msgstr "Отключено"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:688
+msgid "Discovered Neighbors"
+msgstr "Обнаруженные соседи"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:39
+msgid "Discovered chassis"
+msgstr "Обнаруженное шасси"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:40
+msgid "Discovered port"
+msgstr "Обнаруженный порт"
+
+#: htdocs/luci-static/resources/lldpd.js:117
+msgid "E &mdash; enable filter"
+msgstr "E &mdash; включить фильтр"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:392
+msgid "EDP"
+msgstr "EDP"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:334
+msgid "Enable CDP"
+msgstr "Включить CDP"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:394
+msgid "Enable EDP"
+msgstr "Включить EDP"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:374
+msgid "Enable FDP"
+msgstr "Включить FDP"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:312
+msgid "Enable LLDP"
+msgstr "Включить LLDP"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:420
+msgid "Enable SONMP"
+msgstr "Включить SONMP"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:168
+msgid "Enable receive-only mode"
+msgstr "Включить режим «только приём»"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:65
+msgid "Enable service"
+msgstr "Включить службу"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:335
+msgid ""
+"Enable the support of CDP protocol to deal with Cisco routers that do not "
+"speak LLDP"
+msgstr ""
+"Включить поддержку протокола CDP для работы с маршрутизаторами Cisco, "
+"которые не имеют поддержки LLDP"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:395
+msgid ""
+"Enable the support of EDP protocol to deal with Extreme routers and switches "
+"that do not speak LLDP."
+msgstr ""
+"Включить поддержку протокола EDP для работы с маршрутизаторами и "
+"коммутаторам Extreme, которые не имеют поддержки LLDP."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:375
+msgid ""
+"Enable the support of FDP protocol to deal with Foundry routers that do not "
+"speak LLDP"
+msgstr ""
+"Включить поддержку протокола FDP для работы с маршрутизаторами Foundry, "
+"которые не имеют поддержки LLDP"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:421
+msgid ""
+"Enable the support of SONMP protocol to deal with Nortel routers and "
+"switches that do not speak LLDP."
+msgstr ""
+"Включить поддержку протокола SONMP для работы с маршрутизаторами и "
+"коммутаторами Nortel, которые не имеют поддержки LLDP."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:372
+msgid "FDP"
+msgstr "FDP"
+
+#: htdocs/luci-static/resources/lldpd.js:170
+msgid "Filter"
+msgstr "Фильтр"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:267
+msgid "Force port ID subtype"
+msgstr "Использовать в качестве ID порта"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:358
+msgid "Force sending CDPv2 packets"
+msgstr "Форсировать отправку CDPv2 пакетов"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:318
+msgid "Force to send LLDP packets"
+msgstr "Форсировать отправку LLDP пакетов"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:319
+msgid ""
+"Force to send LLDP packets even when there is no LLDP peer detected but "
+"there is a peer speaking another protocol detected. 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 не "
+"обнаружено, но есть соседи, с поддержкой другого протокола. По умолчанию "
+"пакеты LLDP отправляются, когда обнаружен сосед с поддержкой LLDP или когда "
+"вообще нет обнаруженных соседей."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:241
+msgid "Generic Endpoint (Class I)"
+msgstr "Generic Endpoint (класс I)"
+
+#: root/usr/share/rpcd/acl.d/luci-app-lldpd.json:18
+msgid "Grant access for LLDP configuration"
+msgstr "Предоставить доступ к конфигурации LLDP"
+
+#: root/usr/share/rpcd/acl.d/luci-app-lldpd.json:3
+msgid "Grant access for LLDP status information"
+msgstr "Предоставить доступ к информации о состоянии LLDP"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:274
+msgid "ID"
+msgstr "ID"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:275
+msgid "ID type"
+msgstr "Тип ID"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:114
+msgid "IP address"
+msgstr "IP-адрес"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:229
+msgid ""
+"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."
+msgstr ""
+"Если указан путь к сокету, то LLDPd включит SNMP агент, используя протокол "
+"AgentX. Это позволяет получать информацию о локальной системе и удаленных "
+"системах через SNMP."
+
+#: htdocs/luci-static/resources/lldpd.js:171
+msgid "Incoming"
+msgstr "Входящие"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:58
+msgid "Insert count"
+msgstr "Обнаружено"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:271
+msgid "Interface MAC address"
+msgstr "MAC-адрес интерфейса"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:689
+msgid "Interface Statistics"
+msgstr "Статистика интерфейсов"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:272
+#: htdocs/luci-static/resources/view/lldpd/status.js:110
+msgid "Interface name"
+msgstr "Имя интерфейса"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:310
+#: root/usr/share/luci/menu.d/luci-app-lldpd.json:3
+msgid "LLDP"
+msgstr "LLDP"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:672
+msgid "LLDP Status"
+msgstr "Состояние LLDP"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:239
+msgid "LLDP-MED device class"
+msgstr "Класс устройства LLDP-MED"
+
+#: htdocs/luci-static/resources/lldpd.js:19
+msgid "LLDPd LuCI application (version %s)"
+msgstr "LuCI приложение управления LLDPd (версия %s)"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:455
+msgid "LLDPd Settings"
+msgstr "Настройки LLDPd"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:456
+msgid ""
+"LLDPd is a implementation of IEEE 802.1ab (<abbr title=\"Link Layer "
+"Discovery Protocol\">LLDP</abbr>)."
+msgstr ""
+"LLDPd это программная реализация стандарта IEEE 802.1ab (<abbr title=\"Link "
+"Layer Discovery Protocol\">LLDP</abbr>)."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:251
+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 don't want to transmit "
+"sensible information like serial numbers."
+msgstr ""
+"LLDPd будет по прежнему получать (и публиковать, используя SNMP, если он "
+"включен) информацию LLDP-MED TLV, но не будет отправлять. Используйте эту "
+"опцию, если вы не хотите передавать важную информацию, такую как серийные "
+"номера."
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:677
+msgid "Local Chassis"
+msgstr "Локальное шасси"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:112
+msgid "Local ID"
+msgstr "Локально заданный ID"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:37
+#: htdocs/luci-static/resources/view/lldpd/status.js:50
+msgid "Local interface"
+msgstr "Локальный интерфейс"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:108
+msgid "MAC address"
+msgstr "MAC-адрес"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:231
+msgid "MFS"
+msgstr "MFS"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:293
+msgid "Management IP(s)"
+msgstr "IP-адреса"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:114
+msgid "Management addresses of this system"
+msgstr "Адреса управления данной системы"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:242
+msgid "Media Endpoint (Class II)"
+msgstr "Media Endpoint (класс II)"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:139
+#: htdocs/luci-static/resources/view/lldpd/config.js:160
+msgid "Must be a number"
+msgstr "Должно быть числом"
+
+#: htdocs/luci-static/resources/lldpd.js:119
+msgid "N &mdash; keep only one neighbor"
+msgstr "N &mdash; хранить только одного соседа"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:195
+#: htdocs/luci-static/resources/view/lldpd/status.js:206
+#: htdocs/luci-static/resources/view/lldpd/status.js:268
+msgid "Name"
+msgstr "Имя"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:244
+msgid "Network Connectivity Device (Class IV)"
+msgstr "Network Connectivity Device (класс IV)"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:442
+msgid "Network Interfaces"
+msgstr "Сетевые интерфейсы"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:189
+msgid "Network interfaces"
+msgstr "Сетевые интерфейсы"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:202
+msgid "Network interfaces for chassis ID computing"
+msgstr "Сетевые интерфейсы, используемые для вычисления ID шасси (chassis ID)"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:604
+#: htdocs/luci-static/resources/view/lldpd/status.js:661
+#: htdocs/luci-static/resources/view/lldpd/status.js:665
+msgid "No data to display"
+msgstr "Нет данных для отображения"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:458
+msgid "On this page you may configure LLDPd parameters."
+msgstr "На данной странице можно настроить параметры LLDPd."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:345
+msgid "Only CDPv2"
+msgstr "Только CDPv2"
+
+#: htdocs/luci-static/resources/lldpd.js:172
+msgid "Outgoing"
+msgstr "Исходящие"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:93
+msgid "Override system description with the provided description."
+msgstr "Переопределить описание системы заданным значением."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:100
+msgid "Override system hostname with the provided value."
+msgstr "Переопределить имя системы заданным значением."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:107
+msgid ""
+"Override the platform description with the provided value. The default "
+"description is the kernel name (Linux)."
+msgstr ""
+"Переопределить описание платформы заданным значением. По умолчанию "
+"используется имя используемого ядра (Linux)."
+
+#: htdocs/luci-static/resources/lldpd.js:118
+msgid "P &mdash; keep only one protocol"
+msgstr "P &mdash; хранить только один протокол"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:214
+msgid "Port ID"
+msgstr "ID порта"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:217
+msgid "Port ID type"
+msgstr "Тип ID порта"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:222
+msgid "Port description"
+msgstr "Описание порта"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:38
+#: htdocs/luci-static/resources/view/lldpd/status.js:51
+msgid "Protocol"
+msgstr "Протокол"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:448
+msgid "Protocols Support"
+msgstr "Поддержка протоколов"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:54
+msgid "Rx"
+msgstr "Rx"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:144
+msgid "Rx and Tx"
+msgstr "Rx и Tx"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:146
+msgid "Rx only"
+msgstr "Только Rx"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:56
+msgid "Rx unrecognized"
+msgstr "Rx (не распознано)"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:228
+msgid "SNMP agentX socket path"
+msgstr "Путь к сокету SNMP agentX"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:418
+msgid "SONMP (NTDP, NDP, BNMP, BDP)"
+msgstr "SONMP (NTDP, NDP, BNMP, BDP)"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:351
+msgid "Send CDP packets even if no CDP peer detected"
+msgstr "Отправлять CDP пакеты даже если не обнаружено соседей с CDP протоколом"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:402
+msgid "Send EDP packets even if no EDP peer detected"
+msgstr "Отправлять EDP пакеты даже если не обнаружено соседей с EDP протоколом"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:382
+msgid "Send FDP packets even if no FDP peer detected"
+msgstr "Отправлять FDP пакеты даже если не обнаружено соседей с FDP протоколом"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:428
+msgid "Send SONMP packets even if no SONMP peer detected"
+msgstr ""
+"Отправлять SONMP пакеты даже если не обнаружено соседей с SONMP протоколом"
+
+#: root/usr/share/luci/menu.d/luci-app-lldpd.json:28
+msgid "Settings"
+msgstr "Настройки"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:259
+msgid "Specify the behaviour when detecting multiple neighbors"
+msgstr "Поведение при обнаружении нескольких соседей"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:115
+msgid ""
+"Specify the management addresses of this system. If not specified, the first "
+"IPv4 and the first IPv6 are used. If an exact IP address is provided, it is "
+"used as a management address without any check. If you want to blacklist "
+"IPv6 addresses, you can use <code>!*:*</code>. See more details about "
+"available patterns <a href=\"https://vincentbernat.github.io/lldpd/usage.html"
+"\">here</a>."
+msgstr ""
+"Адреса управления данной системой. Если не указано, используется первый IPv4 "
+"и первый IPv6 адреса. Если указан конкретный IP-адрес, он будет использован "
+"как адрес управления без какой-либо проверки. Если вы хотите занести в "
+"черный список IPv6-адреса, вы можете использовать шаблон <code>! *: *</"
+"code>. Подробнее о доступных шаблонах читайте <a href=\"https://"
+"vincentbernat.github.io/lldpd/usage.html\">здесь</a>."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:190
+msgid ""
+"Specify which interface to listen and send LLDPDU to. If no interfaces is "
+"specified, LLDPd will use all available physical interfaces."
+msgstr ""
+"Укажите какие интерфейсы должны прослушивать и отправлять LLDPDU. Если "
+"интерфейсы не указаны, LLDPd будет использовать все доступные физические "
+"интерфейсы."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:203
+msgid ""
+"Specify which interfaces to use for computing chassis ID. If no interfaces "
+"is specified, all interfaces are considered. LLDPd will take the first MAC "
+"address from all the considered interfaces to compute the chassis ID."
+msgstr ""
+"Укажите какие интерфейсы необходимо использовать для вычисления "
+"идентификатора шасси (chassis ID). Если интерфейсы не указаны, будут "
+"иcпользованы все интерфейсы. LLDPd получит первый MAC-адрес из всех "
+"доступных интерфейсов для вычисления ID шасси."
+
+#: root/usr/share/luci/menu.d/luci-app-lldpd.json:16
+msgid "Status"
+msgstr "Состояние"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:92
+msgid "System description"
+msgstr "Описание системы"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:99
+msgid "System hostname"
+msgstr "Имя системы"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:106
+msgid "System platform description"
+msgstr "Описание платформы"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:226
+#: htdocs/luci-static/resources/view/lldpd/status.js:228
+msgid "TTL"
+msgstr "TTL"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:260
+msgid ""
+"The default filter is 15. For more details see \"FILTERING NEIGHBORS\" "
+"section <a href=\"https://vincentbernat.github.io/lldpd/usage.html\">here</"
+"a>."
+msgstr ""
+"По умолчанию используется фильтр 15. Более подробную информацию о данной "
+"опции можно посмотреть в разделе «FILTERING NEIGHBORS» <a href=\"https://"
+"vincentbernat.github.io/lldpd/usage.html\">здесь</a>."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:278
+msgid "The destination MAC address used to send LLDPDU"
+msgstr "MAC-адрес назначения, используемый для отправки LLDPDU"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:279
+msgid ""
+"The destination MAC address used to send LLDPDU allows an agent to control "
+"the propagation of LLDPDUs. By default, the <code>01:80:c2:00:00:0e</code> "
+"MAC address is used and limit the propagation of the LLDPDU to the nearest "
+"bridge."
+msgstr ""
+"MAC-адрес назначения, используемый для отправки LLDPDU. Данный параметр "
+"позволяет агенту контролировать распространение LLDPDU. По умолчанию "
+"используется MAC-адрес <code>01:80:c2:00:00:0e</code> и ограничивает "
+"распространение LLDPDU до ближайшего моста."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:128
+msgid ""
+"The transmit delay is the delay between two transmissions of LLDP PDU. The "
+"default value is 30 seconds."
+msgstr ""
+"Задержка между двумя передачами LLDP PDU. Значение по умолчанию составляет "
+"30 секунд."
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:673
+msgid ""
+"This page allows you to see discovered LLDP neighbors, local interfaces "
+"statistics and local chassis information."
+msgstr ""
+"На данной странице вы можете посмотреть таблицу обнаруженных соседей LLDP, "
+"статистику локальных интерфейсов и информацию о локальном шасси."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:148
+msgid ""
+"This value is used to compute the TTL of transmitted packets which is the "
+"product of this value and of the transmit delay. The default value is 4 and "
+"therefore the default TTL is 120 seconds."
+msgstr ""
+"Это значение используется для вычисления TTL переданных пакетов, которое "
+"является произведением этого значения и задержки передачи. Значение по "
+"умолчанию равно 4. Соответственно, значение TTL по умолчанию составляет 120 "
+"секунд."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:127
+msgid "Transmit delay"
+msgstr "Задержка отправки"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:141
+msgid "Transmit delay must be greater than 0"
+msgstr "Значение задержки отправки должно быть больше 0"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:147
+msgid "Transmit hold value"
+msgstr "Значение «transmit hold»"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:162
+msgid "Transmit hold value must be greater than 0"
+msgstr "Значение «transmit hold» должно быть больше 0"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:53
+msgid "Tx"
+msgstr "Tx"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:55
+msgid "Tx discarded"
+msgstr "Tx (отброшено)"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:148
+msgid "Tx only"
+msgstr "Только Tx"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:152
+#: htdocs/luci-static/resources/view/lldpd/status.js:260
+msgid "Unknown"
+msgstr "Неизвестно"
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:169
+msgid ""
+"With this option, LLDPd will not send any frames. It will only listen to "
+"neighbors."
+msgstr ""
+"С этой опцией LLDPd не будет отправлять какие-либо пакеты. LLDPd будет "
+"только прослушивать сеть для обнаружения соседей."
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:268
+msgid ""
+"With this option, you can force the port identifier to be the interface name "
+"or the MAC address."
+msgstr ""
+"Данная настройка позволяет принудительно использовать в качестве "
+"идентификатор порта имя интерфейса или MAC-адрес."
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:305
+#: htdocs/luci-static/resources/view/lldpd/status.js:314
+msgid "disabled"
+msgstr "выключено"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:305
+#: htdocs/luci-static/resources/view/lldpd/status.js:314
+msgid "enabled"
+msgstr "включено"
+
+#: htdocs/luci-static/resources/lldpd.js:21
+msgid "© 2018–2021, Tano Systems LLC, Anton Kikin"
+msgstr "© 2018–2021, ООО «Тано Системс», Антон Кикин"
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..de98481
--- /dev/null
@@ -0,0 +1,561 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:52
+msgid "Administrative Status"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:445
+msgid "Advanced Settings"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:196
+#: htdocs/luci-static/resources/view/lldpd/status.js:207
+msgid "Age"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:57
+msgid "Ageout count"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:439
+msgid "Basic Settings"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:332
+msgid "CDP"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:342
+msgid "CDP version"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:344
+msgid "CDPv1 and CDPv2"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:317
+msgid "Capabilities"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:44
+#: htdocs/luci-static/resources/view/lldpd/status.js:63
+#: htdocs/luci-static/resources/view/lldpd/status.js:682
+msgid "Collecting data..."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:243
+msgid "Communication Device Endpoints (Class III)"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:59
+msgid "Delete count"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:271
+msgid "Description"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:250
+msgid "Disable LLDP-MED inventory TLV transmission"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:150
+msgid "Disabled"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:688
+msgid "Discovered Neighbors"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:39
+msgid "Discovered chassis"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:40
+msgid "Discovered port"
+msgstr ""
+
+#: htdocs/luci-static/resources/lldpd.js:117
+msgid "E &mdash; enable filter"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:392
+msgid "EDP"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:334
+msgid "Enable CDP"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:394
+msgid "Enable EDP"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:374
+msgid "Enable FDP"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:312
+msgid "Enable LLDP"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:420
+msgid "Enable SONMP"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:168
+msgid "Enable receive-only mode"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:65
+msgid "Enable service"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:335
+msgid ""
+"Enable the support of CDP protocol to deal with Cisco routers that do not "
+"speak LLDP"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:395
+msgid ""
+"Enable the support of EDP protocol to deal with Extreme routers and switches "
+"that do not speak LLDP."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:375
+msgid ""
+"Enable the support of FDP protocol to deal with Foundry routers that do not "
+"speak LLDP"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:421
+msgid ""
+"Enable the support of SONMP protocol to deal with Nortel routers and "
+"switches that do not speak LLDP."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:372
+msgid "FDP"
+msgstr ""
+
+#: htdocs/luci-static/resources/lldpd.js:170
+msgid "Filter"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:267
+msgid "Force port ID subtype"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:358
+msgid "Force sending CDPv2 packets"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:318
+msgid "Force to send LLDP packets"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:319
+msgid ""
+"Force to send LLDP packets even when there is no LLDP peer detected but "
+"there is a peer speaking another protocol detected. By default, LLDP packets "
+"are sent when there is a peer speaking LLDP detected or when there is no "
+"peer at all."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:241
+msgid "Generic Endpoint (Class I)"
+msgstr ""
+
+#: root/usr/share/rpcd/acl.d/luci-app-lldpd.json:18
+msgid "Grant access for LLDP configuration"
+msgstr ""
+
+#: root/usr/share/rpcd/acl.d/luci-app-lldpd.json:3
+msgid "Grant access for LLDP status information"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:274
+msgid "ID"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:275
+msgid "ID type"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:114
+msgid "IP address"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:229
+msgid ""
+"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."
+msgstr ""
+
+#: htdocs/luci-static/resources/lldpd.js:171
+msgid "Incoming"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:58
+msgid "Insert count"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:271
+msgid "Interface MAC address"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:689
+msgid "Interface Statistics"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:272
+#: htdocs/luci-static/resources/view/lldpd/status.js:110
+msgid "Interface name"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:310
+#: root/usr/share/luci/menu.d/luci-app-lldpd.json:3
+msgid "LLDP"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:672
+msgid "LLDP Status"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:239
+msgid "LLDP-MED device class"
+msgstr ""
+
+#: htdocs/luci-static/resources/lldpd.js:19
+msgid "LLDPd LuCI application (version %s)"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:455
+msgid "LLDPd Settings"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:456
+msgid ""
+"LLDPd is a implementation of IEEE 802.1ab (<abbr title=\"Link Layer "
+"Discovery Protocol\">LLDP</abbr>)."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:251
+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 don't want to transmit "
+"sensible information like serial numbers."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:677
+msgid "Local Chassis"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:112
+msgid "Local ID"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:37
+#: htdocs/luci-static/resources/view/lldpd/status.js:50
+msgid "Local interface"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:108
+msgid "MAC address"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:231
+msgid "MFS"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:293
+msgid "Management IP(s)"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:114
+msgid "Management addresses of this system"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:242
+msgid "Media Endpoint (Class II)"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:139
+#: htdocs/luci-static/resources/view/lldpd/config.js:160
+msgid "Must be a number"
+msgstr ""
+
+#: htdocs/luci-static/resources/lldpd.js:119
+msgid "N &mdash; keep only one neighbor"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:195
+#: htdocs/luci-static/resources/view/lldpd/status.js:206
+#: htdocs/luci-static/resources/view/lldpd/status.js:268
+msgid "Name"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:244
+msgid "Network Connectivity Device (Class IV)"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:442
+msgid "Network Interfaces"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:189
+msgid "Network interfaces"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:202
+msgid "Network interfaces for chassis ID computing"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:604
+#: htdocs/luci-static/resources/view/lldpd/status.js:661
+#: htdocs/luci-static/resources/view/lldpd/status.js:665
+msgid "No data to display"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:458
+msgid "On this page you may configure LLDPd parameters."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:345
+msgid "Only CDPv2"
+msgstr ""
+
+#: htdocs/luci-static/resources/lldpd.js:172
+msgid "Outgoing"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:93
+msgid "Override system description with the provided description."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:100
+msgid "Override system hostname with the provided value."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:107
+msgid ""
+"Override the platform description with the provided value. The default "
+"description is the kernel name (Linux)."
+msgstr ""
+
+#: htdocs/luci-static/resources/lldpd.js:118
+msgid "P &mdash; keep only one protocol"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:214
+msgid "Port ID"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:217
+msgid "Port ID type"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:222
+msgid "Port description"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:38
+#: htdocs/luci-static/resources/view/lldpd/status.js:51
+msgid "Protocol"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:448
+msgid "Protocols Support"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:54
+msgid "Rx"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:144
+msgid "Rx and Tx"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:146
+msgid "Rx only"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:56
+msgid "Rx unrecognized"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:228
+msgid "SNMP agentX socket path"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:418
+msgid "SONMP (NTDP, NDP, BNMP, BDP)"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:351
+msgid "Send CDP packets even if no CDP peer detected"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:402
+msgid "Send EDP packets even if no EDP peer detected"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:382
+msgid "Send FDP packets even if no FDP peer detected"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:428
+msgid "Send SONMP packets even if no SONMP peer detected"
+msgstr ""
+
+#: root/usr/share/luci/menu.d/luci-app-lldpd.json:28
+msgid "Settings"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:259
+msgid "Specify the behaviour when detecting multiple neighbors"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:115
+msgid ""
+"Specify the management addresses of this system. If not specified, the first "
+"IPv4 and the first IPv6 are used. If an exact IP address is provided, it is "
+"used as a management address without any check. If you want to blacklist "
+"IPv6 addresses, you can use <code>!*:*</code>. See more details about "
+"available patterns <a href=\"https://vincentbernat.github.io/lldpd/usage.html"
+"\">here</a>."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:190
+msgid ""
+"Specify which interface to listen and send LLDPDU to. If no interfaces is "
+"specified, LLDPd will use all available physical interfaces."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:203
+msgid ""
+"Specify which interfaces to use for computing chassis ID. If no interfaces "
+"is specified, all interfaces are considered. LLDPd will take the first MAC "
+"address from all the considered interfaces to compute the chassis ID."
+msgstr ""
+
+#: root/usr/share/luci/menu.d/luci-app-lldpd.json:16
+msgid "Status"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:92
+msgid "System description"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:99
+msgid "System hostname"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:106
+msgid "System platform description"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:226
+#: htdocs/luci-static/resources/view/lldpd/status.js:228
+msgid "TTL"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:260
+msgid ""
+"The default filter is 15. For more details see \"FILTERING NEIGHBORS\" "
+"section <a href=\"https://vincentbernat.github.io/lldpd/usage.html\">here</"
+"a>."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:278
+msgid "The destination MAC address used to send LLDPDU"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:279
+msgid ""
+"The destination MAC address used to send LLDPDU allows an agent to control "
+"the propagation of LLDPDUs. By default, the <code>01:80:c2:00:00:0e</code> "
+"MAC address is used and limit the propagation of the LLDPDU to the nearest "
+"bridge."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:128
+msgid ""
+"The transmit delay is the delay between two transmissions of LLDP PDU. The "
+"default value is 30 seconds."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:673
+msgid ""
+"This page allows you to see discovered LLDP neighbors, local interfaces "
+"statistics and local chassis information."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:148
+msgid ""
+"This value is used to compute the TTL of transmitted packets which is the "
+"product of this value and of the transmit delay. The default value is 4 and "
+"therefore the default TTL is 120 seconds."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:127
+msgid "Transmit delay"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:141
+msgid "Transmit delay must be greater than 0"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:147
+msgid "Transmit hold value"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:162
+msgid "Transmit hold value must be greater than 0"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:53
+msgid "Tx"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:55
+msgid "Tx discarded"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:148
+msgid "Tx only"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:152
+#: htdocs/luci-static/resources/view/lldpd/status.js:260
+msgid "Unknown"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:169
+msgid ""
+"With this option, LLDPd will not send any frames. It will only listen to "
+"neighbors."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/config.js:268
+msgid ""
+"With this option, you can force the port identifier to be the interface name "
+"or the MAC address."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:305
+#: htdocs/luci-static/resources/view/lldpd/status.js:314
+msgid "disabled"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/lldpd/status.js:305
+#: htdocs/luci-static/resources/view/lldpd/status.js:314
+msgid "enabled"
+msgstr ""
+
+#: htdocs/luci-static/resources/lldpd.js:21
+msgid "© 2018–2021, Tano Systems LLC, Anton Kikin"
+msgstr ""
diff --git a/applications/luci-app-lldpd/root/etc/uci-defaults/40_luci-lldpd b/applications/luci-app-lldpd/root/etc/uci-defaults/40_luci-lldpd
new file mode 100644 (file)
index 0000000..d7bfee2
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+rm -f /tmp/luci-indexcache
+exit 0
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..d79d7bd
--- /dev/null
@@ -0,0 +1,56 @@
+{
+       "luci-app-lldpd-status": {
+               "description": "Grant access for LLDP status information",
+               "read": {
+                       "ubus": {
+                               "lldpd": [
+                                       "getStatus"
+                               ]
+                       },
+                       "uci": [
+                               "lldpd",
+                               "luci"
+                       ]
+               }
+       },
+
+       "luci-app-lldpd-config": {
+               "description": "Grant access for LLDP configuration",
+               "read": {
+                       "uci": [
+                               "lldpd",
+                               "luci",
+                               "network",
+                               "wireless",
+                               "firewall"
+                       ],
+                       "ubus": {
+                               "luci": [
+                                       "getInitList"
+                               ],
+                               "luci-rpc": [
+                                       "getBoardJSON",
+                                       "getHostHints",
+                                       "getNetworkDevices",
+                                       "getWirelessDevices"
+                               ],
+                               "network": [
+                                       "get_proto_handlers"
+                               ],
+                               "network.interface": [
+                                       "dump"
+                               ]
+                       }
+               },
+               "write": {
+                       "uci": [
+                               "lldpd"
+                       ],
+                       "ubus": {
+                               "luci": [
+                                       "setInitAction"
+                               ]
+                       }
+               }
+       }
+}
diff --git a/applications/luci-app-lldpd/root/usr/share/rpcd/ucode/lldpd b/applications/luci-app-lldpd/root/usr/share/rpcd/ucode/lldpd
new file mode 100644 (file)
index 0000000..a35376f
--- /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 {
+       lldpd: {
+               getStatus: {
+                       call: function() {
+                               return {
+                                       statistics: lldpcli_json("statistics"),
+                                       neighbors:  lldpcli_json("neighbors details"),
+                                       interfaces: lldpcli_json("interfaces"),
+                                       chassis:    lldpcli_json("chassis")
+                               };
+                       }
+               }
+       }
+};