luci-app-lldpd: retain empty settings for advertisements flags
[project/luci.git] / applications / luci-app-lldpd / htdocs / luci-static / resources / view / lldpd / config.js
1 /*
2 * Copyright (c) 2020 Tano Systems LLC. All Rights Reserved.
3 * Author: Anton Kikin <a.kikin@tano-systems.com>
4 * Copyright (c) 2023-2024. All Rights Reserved.
5 * Paul Donald <newtwen+github@gmail.com>
6 */
7
8 'use strict';
9 'require rpc';
10 'require form';
11 'require lldpd';
12 'require network';
13 'require uci';
14 'require tools.widgets as widgets';
15
16 var callInitList = rpc.declare({
17 object: 'luci',
18 method: 'getInitList',
19 params: [ 'name' ],
20 expect: { '': {} },
21 filter: function(res) {
22 for (var k in res)
23 return +res[k].enabled;
24 return null;
25 }
26 });
27
28 var callInitAction = rpc.declare({
29 object: 'luci',
30 method: 'setInitAction',
31 params: [ 'name', 'action' ],
32 expect: { result: false }
33 });
34
35 var usage = _('See syntax <a %s>here</a>.').format('href=https://lldpd.github.io/usage.html target="_blank"');
36
37 const validateioentries = function(section_id, value) {
38 if (value) {
39 const emsg = _('Cannot have both interface %s and its exclusion %s');
40 const a = value.split(' ');
41 const noex = a.filter(el=> !el.startsWith('!'));
42 const ex = a.filter(el=> el.startsWith('!') && !el.startsWith('!!'));
43 for (var i of noex) {
44 for (var e of ex) {
45 if ('!'+i == e){
46 return emsg.format(i, e);
47 }
48 }
49 }
50 }
51 return true;
52 };
53
54 return L.view.extend({
55 __init__: function() {
56 this.super('__init__', arguments);
57
58 // Inject CSS
59 var head = document.getElementsByTagName('head')[0];
60 var css = E('link', { 'href':
61 L.resource('lldpd/lldpd.css')
62 + '?v=#PKG_VERSION', 'rel': 'stylesheet' });
63
64 head.appendChild(css);
65 },
66
67 load: function() {
68 return Promise.all([
69 callInitList('lldpd'),
70 lldpd.init(),
71 uci.load('lldpd'),
72 network.getDevices()
73 ]);
74 },
75
76 // -----------------------------------------------------------------------------------------
77 //
78 // Basic Options
79 //
80 // -----------------------------------------------------------------------------------------
81
82 /** @private */
83 populateBasicOptions: function(s, tab, data) {
84 var o;
85 var serviceEnabled = data[0];
86 var net_devices = data[3];
87
88 // Service enable/disable
89 o = s.taboption(tab, form.Flag, 'enabled', _('Enable service'));
90 o.optional = false;
91 o.rmempty = false;
92
93 o.cfgvalue = function() {
94 return serviceEnabled ? this.enabled : this.disabled;
95 };
96
97 o.write = function(section_id, value) {
98 uci.set('lldpd', section_id, 'enabled', value);
99
100 if (value == '1') {
101 // Enable and start
102 callInitAction('lldpd', 'enable').then(function() {
103 return callInitAction('lldpd', 'start');
104 });
105 }
106 else {
107 // Stop and disable
108 callInitAction('lldpd', 'stop').then(function() {
109 return callInitAction('lldpd', 'disable');
110 });
111 }
112 };
113
114 // System description
115 o = s.taboption(tab, form.Value, 'lldp_description',
116 _('System description'),
117 _('Override %s.').format('<code>system description</code>'));
118
119 o.placeholder = 'System description';
120
121 // System hostname
122 o = s.taboption(tab, form.Value, 'lldp_hostname',
123 _('System hostname'),
124 _('Override %s.').format('<code>system hostname</code>'));
125
126 o.placeholder = 'System hostname';
127
128 // Host location
129 o = s.taboption(tab, form.Value, 'lldp_location',
130 _('Host location'),
131 _('Override the announced location of the host.') + '<br />' +
132 usage);
133 // multiple syntaxes alert for location parameter
134 o.placeholder = 'address country EU';
135 o.rmempty = true;
136 o.validate = function(section_id, value) {
137 if (value) {
138 if (!value.match(/^coordinate |^address |^elin /))
139 return _("Must start: 'coordinate ...', 'address ...' or 'elin ...'");
140 }
141 return true;
142 };
143
144
145 // Platform
146 o = s.taboption(tab, form.Value, 'lldp_platform',
147 _('System platform description'),
148 _('Override %s.').format('<code>system platform</code>') + '<br />' +
149 _('The default description is the kernel name (Linux).'));
150
151 o.placeholder = 'System platform description';
152
153 o = s.taboption(tab, form.Flag, 'lldp_capability_advertisements', _('System capability advertisements'));
154 o.default = '1'; //lldpd internal default
155 o.rmempty = false;
156
157 // Capabilities override
158 o = s.taboption(tab, form.MultiValue, 'lldp_syscapabilities',
159 _('System capabilities'),
160 _('Override %s.').format('<code>system capabilities</code>') + '<br />' +
161 _('The default is derived from kernel information.'));
162 o.depends({lldp_capability_advertisements: '1'});
163 o.value('bridge');
164 o.value('docsis');
165 o.value('other');
166 o.value('repeater');
167 o.value('router');
168 o.value('station');
169 o.value('telephone');
170 o.value('wlan');
171 o.cfgvalue = function(section_id) {
172 return String(this.super('load', [section_id]) || this.default).split(',');
173 };
174 o.write = function(section_id, value) {
175 return this.super('write', [ section_id, L.toArray(value).join(',') ]);
176 };
177
178 o = s.taboption(tab, form.Flag, 'lldp_mgmt_addr_advertisements', _('System management IO advertisements'));
179 o.default = '1'; //lldpd internal default
180 o.rmempty = false;
181
182 // Management addresses of this system
183 // This value: lldpd.init handles as a single value, and needs a CSV for lldpd.conf: 'configure system ip management pattern'
184 o = s.taboption(tab, lldpd.CBIMultiIOSelect, 'lldp_mgmt_ip',
185 _('System management IO'),
186 _('Defaults to the first IPv4 and IPv6. ' +
187 'If an exact IP address is provided, it is used ' +
188 'as a management address without any check. To ' +
189 'blacklist IPv6 addresses, use <code>!*:*</code>.') + '<br />' +
190 usage);
191 o.placeholder = 'Addresses and interfaces';
192 o.depends({lldp_mgmt_addr_advertisements: '1'});
193 o.cfgvalue = function(section_id) {
194 const opt = uci.get(this.config, section_id, this.option);
195 return opt ? opt.split(',') : '';
196 };
197 net_devices.forEach(nd => {
198 o.value(nd.getName());
199 o.value('!'+nd.getName());
200 nd.getIPAddrs().forEach(addr => o.value(addr.split('/')[0], E([], [addr.split('/')[0], ' (', E('strong', {}, nd.getName()), ')'])));
201 nd.getIP6Addrs().forEach(addr => o.value(addr.split('/')[0], E([], [addr.split('/')[0], ' (', E('strong', {}, nd.getName()), ')'])));
202 });
203 o.value('!*:*');
204 o.validate = validateioentries;
205 o.write = function(section_id, value, sep) {
206 return this.super('write', [ section_id, value.join(',') ]);
207 }
208
209 // LLDP tx interval
210 o = s.taboption(tab, form.Value, 'lldp_tx_interval',
211 _('Transmit delay'),
212 _('The delay between ' +
213 'transmissions of LLDP PDU. The default value ' +
214 'is 30 seconds.') + '<br />' +
215 _('Suffix %s for millisecond values.').format('<code>ms</code>'));
216 o.default = 30;
217 o.placeholder = 30;
218 o.rmempty = false;
219
220 o.validate = function(section_id, value) {
221 const pattern = /^(\d+)(?:ms)?$/;
222 if (!value.match(pattern) || parseInt(value) <= 0)
223 return _('Must be a greater than zero number optionally suffixed with "ms"');
224 return true;
225 };
226
227 // LLDP tx hold
228 o = s.taboption(tab, form.Value, 'lldp_tx_hold',
229 _('Transmit hold value'),
230 _('Determines the transmitted ' +
231 'packet TTL (== this value * transmit delay). ' +
232 'The default value is 4 &therefore; ' +
233 'the default TTL is 120 seconds.'));
234
235 o.datatype = 'uinteger';
236 o.default = 4;
237 o.placeholder = 4;
238 o.rmempty = false;
239
240 o.validate = function(section_id, value) {
241 if (value != parseInt(value))
242 return _('Must be a number');
243 else if (value <= 0)
244 return _('Transmit hold value must be greater than 0');
245 return true;
246 };
247
248 // Received-only mode (-r)
249 o = s.taboption(tab, form.Flag, 'readonly_mode',
250 _('Receive-only mode'),
251 _("LLDPd won't send any frames; " +
252 'only listen to neighbors.'));
253
254 o.rmempty = false;
255 o.optional = false;
256 o.default = '0';
257 },
258
259 // -----------------------------------------------------------------------------------------
260 //
261 // Network Interfaces
262 //
263 // -----------------------------------------------------------------------------------------
264
265 /** @private */
266 populateIfacesOptions: function(s, tab, data) {
267 var o;
268 var net_devices = data[3];
269
270 // Interfaces to listen on
271 // This value: lldpd.init handles as a list value, and produces a CSV for lldpd.conf: 'configure system interface pattern'
272 o = s.taboption(tab, lldpd.CBIMultiIOSelect, 'interface',
273 _('Network IO'),
274 _('Specify which interface (not) to listen upon and send LLDPDU from. ' +
275 'Absent any value, LLDPd uses all available physical interfaces.'));
276
277 o.value('*');
278 net_devices.forEach(nd => {
279 o.value(nd.getName());
280 o.value('!'+nd.getName());
281 o.value('!!'+nd.getName());
282 });
283 o.value('!*:*');
284 o.validate = validateioentries;
285
286 // ChassisID interfaces
287 // This value: lldpd.init handles as a list value, and produces a CSV for the -C param
288 o = s.taboption(tab, lldpd.CBIMultiIOSelect, 'cid_interface',
289 _('Network IO for chassis ID'),
290 _('Specify which interfaces (not) to use for computing chassis ID. ' +
291 'Absent any value, all interfaces are considered. ' +
292 'LLDPd takes the first MAC address from all the considered ' +
293 'interfaces to compute the chassis ID.'));
294
295 o.value('*');
296 o.value('!*');
297 net_devices.forEach(nd => {
298 o.value(nd.getName());
299 o.value('!'+nd.getName());
300 o.value('!!'+nd.getName());
301 });
302 o.value('!*:*');
303 o.validate = validateioentries;
304
305 },
306
307 // -----------------------------------------------------------------------------------------
308 //
309 // Advanced Options
310 //
311 // -----------------------------------------------------------------------------------------
312
313 /** @private */
314 populateAdvancedOptions: function(s, tab, data) {
315 var o;
316
317 // SNMP agentX socket
318 // **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.
319 // o = s.taboption(tab, form.Value, 'agentxsocket',
320 // _('SNMP agentX socket path'),
321 // _('If the path to the socket is set, then LLDPd will enable an ' +
322 // 'SNMP subagent using AgentX protocol. This allows you to get ' +
323 // 'information about local system and remote systems through SNMP.'));
324
325 // o.rmempty = true;
326 // o.placeholder = '/var/run/agentx.sock';
327 // o.default = '';
328
329 // LLDP-MED class
330 o = s.taboption(tab, form.ListValue, 'lldp_class',
331 _('LLDP-MED device class'));
332
333 o.value('1', _('Generic Endpoint (Class I)'));
334 o.value('2', _('Media Endpoint (Class II)'));
335 o.value('3', _('Communication Device Endpoints (Class III)'));
336 o.value('4', _('Network Connectivity Device (Class IV)'));
337
338 o.default = '4';
339
340 // LLDP-MED policy
341 o = s.taboption(tab, form.Value, 'lldp_policy',
342 _('LLDP-MED policy'));
343 o.depends({lldp_class: '2'});
344 o.depends({lldp_class: '3'});
345
346 o.rmempty = true;
347 o.placeholder = 'application streaming-video';
348 o.value('application voice');
349 o.value('application voice unknown');
350 o.value('application voice-signaling');
351 o.value('application voice-signaling unknown');
352 o.value('application guest-voice');
353 o.value('application guest-voice unknown');
354 o.value('application guest-voice-signaling');
355 o.value('application guest-voice-signaling unknown');
356 o.value('application softphone-voice');
357 o.value('application softphone-voice unknown');
358 o.value('application video-conferencing');
359 o.value('application video-conferencing unknown');
360 o.value('application streaming-video');
361 o.value('application streaming-video unknown');
362 o.value('application video-signaling');
363 o.value('application video-signaling unknown');
364
365 o.validate = function(section_id, value) {
366 if (value && !value.startsWith('application '))
367 return _('Must start: application ...');
368 return true;
369 };
370
371 // LLDP-MED fast-start
372 o = s.taboption(tab, form.Flag, 'lldpmed_fast_start',
373 _('LLDP-MED fast-start'));
374
375 // LLDP-MED fast-start
376 o = s.taboption(tab, form.Value, 'lldpmed_fast_start_tx_interval',
377 _('LLDP-MED fast-start tx-interval'));
378 o.depends({lldpmed_fast_start: '1'});
379 o.datatype = 'uinteger';
380 o.placeholder = '10';
381 o.rmempty = true;
382
383 // LLDP-MED inventory TLV transmission (-i)
384 o = s.taboption(tab, form.Flag, 'lldpmed_no_inventory',
385 _('Disable LLDP-MED inventory TLV transmission'),
386 _('LLDPd will still receive (and publish using SNMP if enabled) ' +
387 'those LLDP-MED TLV but will not send them. Use this option ' +
388 'if you do not want to transmit sensitive information like serial numbers.'));
389
390 o.default = '0';
391
392 // Disable advertising of kernel release, version and machine. (-k)
393 o = s.taboption(tab, form.Flag, 'lldp_no_version',
394 _('Disable advertising of kernel release, version and machine'),
395 _('Kernel name (ie: Linux) will still be shared, and Inventory ' +
396 'software version will be set to %s.').format('<code>Unknown</code>'));
397
398 o.default = '0';
399
400 // Filter neighbors (-H)
401 o = s.taboption(tab, lldpd.cbiFilterSelect, 'filter',
402 _('Specify the behaviour when detecting multiple neighbors'),
403 _('The default filter is 15. Refer to &quot;FILTERING NEIGHBORS&quot;.') + '<br />' +
404 usage);
405
406 o.default = 15;
407
408 // Force port ID subtype
409 o = s.taboption(tab, form.ListValue, 'lldp_portidsubtype',
410 _('Force port ID subtype'),
411 _('With this option, you can force the port identifier ' +
412 'to be the interface name or the MAC address.'));
413
414 o.value('macaddress', _('Interface MAC address'));
415 o.value('ifname', _('Interface name'));
416
417 o.default = 'macaddress';
418
419 // The destination MAC address used to send LLDPDU
420 o = s.taboption(tab, form.ListValue, 'lldp_agenttype',
421 _('LLDPDU destination MAC'),
422 _('Allows an agent ' +
423 'to control the propagation of LLDPDUs. By default, the ' +
424 'MAC address %s is used and limits the propagation ' +
425 'of the LLDPDU to the nearest bridge.').format('<code>01:80:c2:00:00:0e</code>'));
426
427 o.value('nearest-bridge', '01:80:c2:00:00:0e (nearest-bridge)');
428 o.value('nearest-nontpmr-bridge', '01:80:c2:00:00:03 (nearest-nontpmr-bridge)');
429 o.value('nearest-customer-bridge', '01:80:c2:00:00:00 (nearest-customer-bridge)');
430
431 o.default = 'nearest-bridge';
432 },
433
434 // -----------------------------------------------------------------------------------------
435 //
436 // Protocols Support
437 //
438 // -----------------------------------------------------------------------------------------
439
440 /** @private */
441 populateProtocolsOptions: function(s, tab, data) {
442 var o;
443
444 o = s.taboption(tab, form.SectionValue, '_protocols', form.TypedSection, 'lldpd');
445 var ss = o.subsection;
446 ss.anonymous = true;
447 ss.addremove = false;
448
449 //
450 // LLDPD
451 // Link Layer Discovery Protocol
452 //
453 ss.tab('lldp', _('LLDP'));
454 o = ss.taboption('lldp', form.Flag, 'enable_lldp',
455 _('Enable LLDP'));
456
457 o.default = '1';
458 o.rmempty = true;
459
460 o = ss.taboption('lldp', form.Flag, 'force_lldp',
461 _('Force sending LLDP packets'),
462 _('Even when there is no LLDP peer ' +
463 'detected but there is a peer speaking another protocol detected.') + '<br />' +
464 _('By default, LLDP packets are sent when there is a peer speaking ' +
465 'LLDP detected or when there is no peer at all.'));
466
467 o.default = '0';
468 o.rmempty = true;
469 o.depends('enable_lldp', '1');
470
471 //
472 // CDP
473 // Cisco Discovery Protocol
474 //
475 ss.tab('cdp', _('CDP'));
476 o = ss.taboption('cdp', form.Flag, 'enable_cdp',
477 _('Enable CDP'),
478 _('Enable the support of CDP protocol to deal with Cisco routers ' +
479 'that do not speak LLDP'));
480
481 o.default = '1';
482 o.rmempty = false;
483
484 o = ss.taboption('cdp', form.ListValue, 'cdp_version',
485 _('CDP version'));
486
487 o.value('cdpv1v2', _('CDPv1 and CDPv2'));
488 o.value('cdpv2', _('Only CDPv2'));
489 o.depends('enable_cdp', '1');
490
491 o.default = 'cdpv1v2';
492
493 o = ss.taboption('cdp', form.Flag, 'force_cdp',
494 _('Send CDP packets even if no CDP peer detected'));
495
496 o.default = '0';
497 o.rmempty = true;
498 o.depends('enable_cdp', '1');
499
500 o = ss.taboption('cdp', form.Flag, 'force_cdpv2',
501 _('Force sending CDPv2 packets'));
502
503 o.default = '0';
504 o.rmempty = true;
505 o.depends({
506 force_cdp: '1',
507 enable_cdp: '1',
508 cdp_version: 'cdpv1v2'
509 });
510
511 //
512 // FDP
513 // Foundry Discovery Protocol
514 //
515 ss.tab('fdp', _('FDP'));
516 o = ss.taboption('fdp', form.Flag, 'enable_fdp',
517 _('Enable FDP'),
518 _('Enable the support of FDP protocol to deal with Foundry routers ' +
519 'that do not speak LLDP'));
520
521 o.default = '1';
522 o.rmempty = false;
523
524 o = ss.taboption('fdp', form.Flag, 'force_fdp',
525 _('Send FDP packets even if no FDP peer detected'));
526
527 o.default = '0';
528 o.rmempty = true;
529 o.depends('enable_fdp', '1');
530
531 //
532 // EDP
533 // Extreme Discovery Protocol
534 //
535 ss.tab('edp', _('EDP'));
536 o = ss.taboption('edp', form.Flag, 'enable_edp',
537 _('Enable EDP'),
538 _('Enable the support of EDP protocol to deal with Extreme routers ' +
539 'and switches that do not speak LLDP.'));
540
541 o.default = '1';
542 o.rmempty = false;
543
544 o = ss.taboption('edp', form.Flag, 'force_edp',
545 _('Send EDP packets even if no EDP peer detected'));
546
547 o.default = '0';
548 o.rmempty = true;
549 o.depends('enable_edp', '1');
550
551 //
552 // SONMP
553 // SynOptics Network Management Protocol
554 //
555 // a.k.a.
556 // Nortel Topology Discovery Protocol (NTDP)
557 // Nortel Discovery Protocol (NDP)
558 // Bay Network Management Protocol (BNMP)
559 // Bay Discovery Protocol (BDP)
560 //
561 ss.tab('sonmp', _('SONMP (NTDP, NDP, BNMP, BDP)'));
562 o = ss.taboption('sonmp', form.Flag, 'enable_sonmp',
563 _('Enable SONMP'),
564 _('Enable the support of SONMP protocol to deal with Nortel ' +
565 'routers and switches that do not speak LLDP.'));
566
567 o.default = '1';
568 o.rmempty = false;
569
570 o = ss.taboption('sonmp', form.Flag, 'force_sonmp',
571 _('Send SONMP packets even if no SONMP peer detected'));
572
573 o.default = '0';
574 o.rmempty = true;
575 o.depends('enable_sonmp', '1');
576 },
577
578 /** @private */
579 populateOptions: function(s, data) {
580 var o;
581
582 s.tab('basic', _('Basic Settings'));
583 this.populateBasicOptions(s, 'basic', data);
584
585 s.tab('ifaces', _('Network Interfaces'));
586 this.populateIfacesOptions(s, 'ifaces', data);
587
588 s.tab('advanced', _('Advanced Settings'));
589 this.populateAdvancedOptions(s, 'advanced', data);
590
591 s.tab('protocols', _('Protocols Support'));
592 this.populateProtocolsOptions(s, 'protocols', data);
593 },
594
595 render: function(data) {
596 var m, s;
597
598 m = new form.Map('lldpd', _('LLDPd Settings'),
599 _('LLDPd is an implementation of IEEE 802.1ab') + ' ' +
600 '(<abbr title="Link Layer Discovery Protocol">LLDP</abbr>).' + ' ' +
601 _('On this page you may configure LLDPd parameters.'));
602
603 s = m.section(form.TypedSection, 'lldpd');
604 s.addremove = false;
605 s.anonymous = true;
606
607 this.populateOptions(s, data);
608
609 return m.render();
610 },
611 });