2 * Copyright (c) 2020 Tano Systems. All Rights Reserved.
3 * Author: Anton Kikin <a.kikin@tano-systems.com>
13 var callLLDPStatus
= rpc
.declare({
29 return L
.view
.extend({
30 __init__: function() {
31 this.super('__init__', arguments
);
33 this.rowsUnfolded
= {};
35 this.tableNeighbors
= E('div', { 'class': 'table lldpd-table' }, [
36 E('div', { 'class': 'tr table-titles' }, [
37 E('div', { 'class': 'th left top' }, _('Local interface')),
38 E('div', { 'class': 'th left top' }, _('Protocol')),
39 E('div', { 'class': 'th left top' }, _('Discovered chassis')),
40 E('div', { 'class': 'th left top' }, _('Discovered port')),
42 E('div', { 'class': 'tr center placeholder' }, [
43 E('div', { 'class': 'td' }, E('em', { 'class': 'spinning' },
44 _('Collecting data...'))),
48 this.tableStatistics
= E('div', { 'class': 'table lldpd-table' }, [
49 E('div', { 'class': 'tr table-titles' }, [
50 E('div', { 'class': 'th left top' }, _('Local interface')),
51 E('div', { 'class': 'th left top' }, _('Protocol')),
52 E('div', { 'class': 'th left top' }, _('Administrative Status')),
53 E('div', { 'class': 'th right top' }, _('Tx')),
54 E('div', { 'class': 'th right top' }, _('Rx')),
55 E('div', { 'class': 'th right top' }, _('Tx discarded')),
56 E('div', { 'class': 'th right top' }, _('Rx unrecognized')),
57 E('div', { 'class': 'th right top' }, _('Ageout count')),
58 E('div', { 'class': 'th right top' }, _('Insert count')),
59 E('div', { 'class': 'th right top' }, _('Delete count')),
61 E('div', { 'class': 'tr center placeholder' }, [
62 E('div', { 'class': 'td' }, E('em', { 'class': 'spinning' },
63 _('Collecting data...'))),
68 var head
= document
.getElementsByTagName('head')[0];
69 var css
= E('link', { 'href':
70 L
.resource('lldpd/lldpd.css')
71 + '?v=#PKG_VERSION', 'rel': 'stylesheet' });
73 head
.appendChild(css
);
78 L
.resolveDefault(callLLDPStatus(), {}),
84 renderParam: function(param
, value
) {
85 if (typeof value
=== 'undefined')
89 E('span', { 'class': 'lldpd-param' }, param
),
90 E('span', { 'class': 'lldpd-param-value' }, value
)
95 renderAge: function(v
) {
96 if (typeof v
=== 'undefined')
99 return E('nobr', {}, v
);
103 renderIdType: function(v
) {
104 if (typeof v
=== 'undefined')
108 return _('MAC address');
109 else if (v
== 'ifname')
110 return _('Interface name');
111 else if (v
== 'local')
112 return _('Local ID');
114 return _('IP address');
120 renderProtocol: function(v
) {
121 if (typeof v
=== 'undefined' || v
== 'unknown')
125 return E('span', { 'class': 'lldpd-protocol-badge lldpd-protocol-lldp' }, v
);
126 else if ((v
== 'CDPv1') || (v
== 'CDPv2'))
127 return E('span', { 'class': 'lldpd-protocol-badge lldpd-protocol-cdp' }, v
);
129 return E('span', { 'class': 'lldpd-protocol-badge lldpd-protocol-fdp' }, v
);
131 return E('span', { 'class': 'lldpd-protocol-badge lldpd-protocol-edp' }, v
);
132 else if (v
== 'SONMP')
133 return E('span', { 'class': 'lldpd-protocol-badge lldpd-protocol-sonmp' }, v
);
135 return E('span', { 'class': 'lldpd-protocol-badge' }, v
);
139 renderAdminStatus: function(status
) {
140 if ((typeof status
=== 'undefined') || !Array
.isArray(status
))
143 if (status
[0].value
=== 'RX and TX')
144 return _('Rx and Tx');
145 else if (status
[0].value
=== 'RX only')
147 else if (status
[0].value
=== 'TX only')
149 else if (status
[0].value
=== 'disabled')
150 return _('Disabled');
156 renderNumber: function(v
) {
164 renderPort: function(port
) {
165 if (typeof port
.port
!== 'undefined')
167 if (typeof port
.port
[0].descr
!== 'undefined' &&
168 typeof port
.port
[0].id
[0].value
!== 'undefined' &&
169 port
.port
[0].descr
[0].value
!== port
.port
[0].id
[0].value
)
172 E('strong', {}, port
.port
[0].descr
[0].value
),
174 port
.port
[0].id
[0].value
179 if (typeof port
.port
[0].descr
!== 'undefined')
180 return port
.port
[0].descr
[0].value
;
182 return port
.port
[0].id
[0].value
;
187 return '%s'.format(port
.name
);
192 renderPortParamTableShort: function(port
) {
195 items
.push(this.renderParam(_('Name'), port
.name
));
196 items
.push(this.renderParam(_('Age'), this.renderAge(port
.age
)));
198 return E('div', { 'class': 'lldpd-params' }, items
);
202 renderPortParamTable: function(port
, only_id_and_ttl
) {
205 if (!only_id_and_ttl
) {
206 items
.push(this.renderParam(_('Name'), port
.name
));
207 items
.push(this.renderParam(_('Age'), this.renderAge(port
.age
)));
210 if (typeof port
.port
!== 'undefined')
212 if (typeof port
.port
[0].id
!== 'undefined')
214 items
.push(this.renderParam(_('Port ID'),
215 port
.port
[0].id
[0].value
));
217 items
.push(this.renderParam(_('Port ID type'),
218 this.renderIdType(port
.port
[0].id
[0].type
)));
221 if (typeof port
.port
[0].descr
!== 'undefined')
222 items
.push(this.renderParam(_('Port description'),
223 port
.port
[0].descr
[0].value
));
225 if (typeof port
.ttl
!== 'undefined')
226 items
.push(this.renderParam(_('TTL'), port
.ttl
[0].ttl
));
227 else if (port
.port
[0].ttl
!== 'undefined')
228 items
.push(this.renderParam(_('TTL'), port
.port
[0].ttl
[0].value
));
230 if (typeof port
.port
[0].mfs
!== 'undefined')
231 items
.push(this.renderParam(_('MFS'), port
.port
[0].mfs
[0].value
));
234 return E('div', { 'class': 'lldpd-params' }, items
);
238 renderChassis: function(ch
) {
239 if (typeof ch
.name
!== 'undefined' &&
240 typeof ch
.descr
!== 'undefined' &&
241 typeof ch
.name
[0].value
!== 'undefined' &&
242 typeof ch
.descr
[0].value
!== 'undefined')
245 E('strong', {}, ch
.name
[0].value
),
250 else if (typeof ch
.name
!== 'undefined' &&
251 typeof ch
.name
[0].value
!== 'undefined')
252 return E('strong', {}, ch
.name
[0].value
);
253 else if (typeof ch
.descr
!== 'undefined' &&
254 typeof ch
.descr
[0].value
!== 'undefined')
255 return ch
.descr
[0].value
;
256 else if (typeof ch
.id
!== 'undefined' &&
257 typeof ch
.id
[0].value
!== 'undefined')
258 return ch
.id
[0].value
;
264 renderChassisParamTable: function(ch
) {
267 if (typeof ch
.name
!== 'undefined')
268 items
.push(this.renderParam(_('Name'), ch
.name
[0].value
));
270 if (typeof ch
.descr
!== 'undefined')
271 items
.push(this.renderParam(_('Description'), ch
.descr
[0].value
));
273 if (typeof ch
.id
!== 'undefined') {
274 items
.push(this.renderParam(_('ID'), ch
.id
[0].value
));
275 items
.push(this.renderParam(_('ID type'),
276 this.renderIdType(ch
.id
[0].type
)));
279 // Management addresses
280 if (typeof ch
['mgmt-ip'] !== 'undefined') {
283 if (ch
['mgmt-ip'].length
> 0) {
284 // Array of addresses
285 for (var ip
= 0; ip
< ch
["mgmt-ip"].length
; ip
++)
286 ips
+= ch
['mgmt-ip'][ip
].value
+ '<br />';
290 ips
+= ch
['mgmt-ip'][0].value
;
293 items
.push(this.renderParam(_('Management IP(s)'), ips
));
296 if (typeof ch
.capability
!== 'undefined') {
299 if (ch
.capability
.length
> 0)
301 // Array of capabilities
302 for (var cap
= 0; cap
< ch
.capability
.length
; cap
++) {
303 caps
+= ch
.capability
[cap
].type
;
304 caps
+= ' (' + (ch
.capability
[cap
].enabled
305 ? _('enabled') : _('disabled')) + ')';
312 caps
+= ch
.capability
[0].type
;
313 caps
+= ' (' + (ch
.capability
[0].enabled
314 ? _('enabled') : _('disabled')) + ')';
317 items
.push(this.renderParam(_('Capabilities'), caps
));
320 return E('div', { 'class': 'lldpd-params' }, items
);
324 getFoldingImage: function(unfolded
) {
325 return L
.resource('lldpd/details_' +
326 (unfolded
? 'hide' : 'show') + '.svg');
330 generateRowId: function(str
) {
331 return str
.replace(/[^a-z0-9]/gi, '-');
335 handleToggleFoldingRow: function(row
, row_id
) {
336 var e_img
= row
.querySelector('img');
337 var e_folded
= row
.querySelectorAll('.lldpd-folded');
338 var e_unfolded
= row
.querySelectorAll('.lldpd-unfolded');
340 if (e_folded
.length
!= e_unfolded
.length
)
343 var do_unfold
= (e_folded
[0].style
.display
!== 'none');
344 this.rowsUnfolded
[row_id
] = do_unfold
;
346 for (var i
= 0; i
< e_folded
.length
; i
++)
350 e_folded
[i
].style
.display
= 'none';
351 e_unfolded
[i
].style
.display
= 'block';
355 e_folded
[i
].style
.display
= 'block';
356 e_unfolded
[i
].style
.display
= 'none';
360 e_img
.src
= this.getFoldingImage(do_unfold
);
364 makeFoldingTableRow: function(row
, unfolded
) {
367 // row[1] - contents for first cell in row
368 // row[2] - contents for second cell in row
370 // row[N] - contents for N-th cell in row
375 for (let i
= 1; i
< row
.length
; i
++) {
377 // Fold/unfold image appears only in first column
378 var dImg
= E('div', { 'style': 'padding: 0 8px 0 0;' }, [
379 E('img', { 'width': '16px', 'src': this.getFoldingImage(unfolded
) }),
383 if (Array
.isArray(row
[i
])) {
384 // row[i][0] = folded contents
385 // row[i][1] = unfolded contents
388 let dFolded
= E('div', {
389 'class': 'lldpd-folded',
390 'style': unfolded
? 'display: none;' : 'display: block;'
393 // Unfolded cell data
394 let dUnfolded
= E('div', {
395 'class': 'lldpd-unfolded',
396 'style': unfolded
? 'display: block;' : 'display: none;'
401 'style': 'display: flex; flex-wrap: nowrap;'
402 }, [ dImg
, dFolded
, dUnfolded
]);
405 row
[i
] = E('div', {}, [ dFolded
, dUnfolded
]);
409 // row[i] = same content for folded and unfolded states
413 'style': 'display: flex; flex-wrap: nowrap;'
414 }, [ dImg
, E('div', row
[i
]) ]);
423 makeNeighborsTableRow: function(obj
) {
424 if (typeof obj
=== 'undefined')
425 obj
.name
= 'Unknown';
427 var new_id
= obj
.name
+ '-' + obj
.rid
;
429 if (typeof obj
.port
!== 'undefined') {
430 if (typeof obj
.port
[0].id
!== 'undefined')
431 new_id
+= "-" + obj
.port
[0].id
[0].value
;
433 if (typeof obj
.port
[0].descr
!== 'undefined')
434 new_id
+= "-" + obj
.port
[0].descr
[0].value
;
437 var row_id
= this.generateRowId(new_id
);
439 return this.makeFoldingTableRow([
442 '%s'.format(obj
.name
),
443 this.renderPortParamTableShort(obj
)
445 this.renderProtocol(obj
.via
),
447 this.renderChassis(obj
.chassis
[0]),
448 this.renderChassisParamTable(obj
.chassis
[0])
451 this.renderPort(obj
),
452 this.renderPortParamTable(obj
, true)
454 ], this.rowsUnfolded
[row_id
] || false);
458 renderInterfaceProtocols: function(iface
, neighbors
) {
459 if ((typeof iface
=== 'undefined') ||
460 (typeof neighbors
== 'undefined') ||
461 (typeof neighbors
.lldp
[0] === 'undefined') ||
462 (typeof neighbors
.lldp
[0].interface === 'undefined'))
465 var name
= iface
.name
;
468 /* Search protocols for interface <name> */
469 neighbors
.lldp
[0].interface.forEach(function(n
) {
473 protocols
.push(this.renderProtocol(n
.via
));
476 if (protocols
.length
> 0)
477 return E('span', {}, protocols
);
483 makeStatisticsTableRow: function(sobj
, iobj
, neighbors
) {
484 var row_id
= this.generateRowId(iobj
.name
);
486 return this.makeFoldingTableRow([
489 this.renderPort(iobj
), // folded
490 this.renderPortParamTable(iobj
, false) // unfolded
492 this.renderInterfaceProtocols(iobj
, neighbors
),
493 this.renderAdminStatus(iobj
.status
),
494 this.renderNumber(sobj
.tx
[0].tx
),
495 this.renderNumber(sobj
.rx
[0].rx
),
496 this.renderNumber(sobj
.rx_discarded_cnt
[0].rx_discarded_cnt
),
497 this.renderNumber(sobj
.rx_unrecognized_cnt
[0].rx_unrecognized_cnt
),
498 this.renderNumber(sobj
.ageout_cnt
[0].ageout_cnt
),
499 this.renderNumber(sobj
.insert_cnt
[0].insert_cnt
),
500 this.renderNumber(sobj
.delete_cnt
[0].delete_cnt
)
501 ], this.rowsUnfolded
[row_id
] || false);
505 updateTable: function(table
, data
, placeholder
) {
506 var target
= isElem(table
) ? table
: document
.querySelector(table
);
511 target
.querySelectorAll(
512 '.tr.table-titles, .cbi-section-table-titles').forEach(L
.bind(function(thead
) {
515 thead
.querySelectorAll('.th').forEach(function(th
) {
519 if (Array
.isArray(data
)) {
520 var n
= 0, rows
= target
.querySelectorAll('.tr');
522 data
.forEach(L
.bind(function(row
) {
524 var trow
= E('div', { 'class': 'tr', 'click': L
.bind(function(ev
) {
525 this.handleToggleFoldingRow(ev
.currentTarget
, id
);
526 // lldpd_folding_toggle(ev.currentTarget, id);
529 for (var i
= 0; i
< titles
.length
; i
++) {
530 var text
= (titles
[i
].innerText
|| '').trim();
531 var td
= trow
.appendChild(E('div', {
532 'class': titles
[i
].className
,
533 'data-title': (text
!== '') ? text
: null
534 }, row
[i
+ 1] || ''));
536 td
.classList
.remove('th');
537 td
.classList
.add('td');
540 trow
.classList
.add('cbi-rowstyle-%d'.format((n
++ % 2) ? 2 : 1));
543 target
.replaceChild(trow
, rows
[n
]);
545 target
.appendChild(trow
);
549 target
.removeChild(rows
[n
]);
551 if (placeholder
&& target
.firstElementChild
=== target
.lastElementChild
) {
552 var trow
= target
.appendChild(
553 E('div', { 'class': 'tr placeholder' }));
555 var td
= trow
.appendChild(
556 E('div', { 'class': 'center ' + titles
[0].className
}, placeholder
));
558 td
.classList
.remove('th');
559 td
.classList
.add('td');
562 thead
.parentNode
.style
.display
= 'none';
564 thead
.parentNode
.querySelectorAll('.tr, .cbi-section-table-row').forEach(function(trow
) {
565 if (trow
!== thead
) {
567 trow
.querySelectorAll('.th, .td').forEach(function(td
) {
568 if (n
< titles
.length
) {
569 var text
= (titles
[n
++].innerText
|| '').trim();
571 td
.setAttribute('data-title', text
);
577 thead
.parentNode
.style
.display
= '';
583 startPolling: function() {
584 poll
.add(L
.bind(function() {
585 return callLLDPStatus().then(L
.bind(function(data
) {
586 this.renderData(data
);
592 renderDataLocalChassis: function(data
) {
594 typeof data
!== 'undefined' &&
595 typeof data
['local-chassis'] !== 'undefined' &&
596 typeof data
['local-chassis'][0].chassis
[0].name
!== 'undefined') {
597 return this.renderChassisParamTable(data
['local-chassis'][0].chassis
[0]);
600 return E('div', { 'class': 'alert-message warning' },
601 _('No data to display'));
606 renderDataNeighbors: function(neighbors
) {
610 typeof neighbors
!== 'undefined' &&
611 typeof neighbors
.lldp
!== 'undefined')
613 var ifaces
= neighbors
.lldp
[0].interface;
616 if (typeof ifaces
!== 'undefined') {
617 for (i
= 0; i
< ifaces
.length
; i
++)
618 rows
.push(this.makeNeighborsTableRow(ifaces
[i
]));
626 renderDataStatistics: function(statistics
, interfaces
, neighbors
) {
631 typeof statistics
!== 'undefined' &&
632 typeof interfaces
!== 'undefined' &&
633 typeof statistics
.lldp
!== 'undefined' &&
634 typeof interfaces
.lldp
!== 'undefined')
636 var sifaces
= statistics
.lldp
[0].interface;
637 var ifaces
= interfaces
.lldp
[0].interface;
639 if ((typeof sifaces
!== 'undefined') &&
640 (typeof ifaces
!== 'undefined')) {
641 for (var i
= 0; i
< sifaces
.length
; i
++)
642 rows
.push(this.makeStatisticsTableRow(sifaces
[i
], ifaces
[i
], neighbors
));
650 renderData: function(data
) {
653 r
= this.renderDataLocalChassis(data
.chassis
);
654 dom
.content(document
.getElementById('lldpd-local-chassis'), r
);
656 r
= this.renderDataNeighbors(data
.neighbors
);
657 this.updateTable(this.tableNeighbors
, r
,
658 _('No data to display'));
660 r
= this.renderDataStatistics(data
.statistics
, data
.interfaces
, data
.neighbors
);
661 this.updateTable(this.tableStatistics
, r
,
662 _('No data to display'));
665 render: function(data
) {
668 m
= new form
.JSONMap(dataMap
,
670 _('This page allows you to see discovered LLDP neighbors, ' +
671 'local interfaces statistics and local chassis information.'));
673 s
= m
.section(form
.NamedSection
, 'local', 'local',
676 o
= s
.option(form
.DummyValue
, 'localChassis');
677 o
.render = function() {
678 return E('div', { 'id': 'lldpd-local-chassis' }, [
679 E('em', { 'class': 'spinning' }, _('Collecting data...'))
683 s
= m
.section(form
.NamedSection
, 'remote', 'remote');
685 s
.tab('neighbors', _('Discovered Neighbors'));
686 s
.tab('statistics', _('Interface Statistics'));
688 o
= s
.taboption('neighbors', form
.DummyValue
, 'neighbors');
689 o
.render
= L
.bind(function() {
690 return E('div', { 'class': 'table-wrapper' }, [
695 o
= s
.taboption('statistics', form
.DummyValue
, 'statistics');
696 o
.render
= L
.bind(function() {
697 return E('div', { 'class': 'table-wrapper' }, [
702 return m
.render().then(L
.bind(function(rendered
) {
708 handleSaveApply
: null,