Translated using Weblate (Japanese)
[oweals/luci.git] / modules / luci-base / htdocs / luci-static / resources / tools / widgets.js
1 'use strict';
2 'require ui';
3 'require form';
4 'require network';
5 'require firewall';
6 'require fs';
7
8 function getUsers() {
9     return fs.lines('/etc/passwd').then(function(lines) {
10         return lines.map(function(line) { return line.split(/:/)[0] });
11     });
12 }
13
14 function getGroups() {
15     return fs.lines('/etc/group').then(function(lines) {
16         return lines.map(function(line) { return line.split(/:/)[0] });
17     });
18 }
19
20 var CBIZoneSelect = form.ListValue.extend({
21         __name__: 'CBI.ZoneSelect',
22
23         load: function(section_id) {
24                 return Promise.all([ firewall.getZones(), network.getNetworks() ]).then(L.bind(function(zn) {
25                         this.zones = zn[0];
26                         this.networks = zn[1];
27
28                         return this.super('load', section_id);
29                 }, this));
30         },
31
32         filter: function(section_id, value) {
33                 return true;
34         },
35
36         lookupZone: function(name) {
37                 return this.zones.filter(function(zone) { return zone.getName() == name })[0];
38         },
39
40         lookupNetwork: function(name) {
41                 return this.networks.filter(function(network) { return network.getName() == name })[0];
42         },
43
44         renderWidget: function(section_id, option_index, cfgvalue) {
45                 var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
46                     isOutputOnly = false,
47                     choices = {};
48
49                 if (this.option == 'dest') {
50                         for (var i = 0; i < this.section.children.length; i++) {
51                                 var opt = this.section.children[i];
52                                 if (opt.option == 'src') {
53                                         var val = opt.cfgvalue(section_id) || opt.default;
54                                         isOutputOnly = (val == null || val == '');
55                                         break;
56                                 }
57                         }
58
59                         this.title = isOutputOnly ? _('Output zone') :  _('Destination zone');
60                 }
61
62                 if (this.allowlocal) {
63                         choices[''] = E('span', {
64                                 'class': 'zonebadge',
65                                 'style': 'background-color:' + firewall.getColorForName(null)
66                         }, [
67                                 E('strong', _('Device')),
68                                 (this.allowany || this.allowlocal)
69                                         ? ' (%s)'.format(this.option != 'dest' ? _('output') : _('input')) : ''
70                         ]);
71                 }
72                 else if (!this.multiple && (this.rmempty || this.optional)) {
73                         choices[''] = E('span', {
74                                 'class': 'zonebadge',
75                                 'style': 'background-color:' + firewall.getColorForName(null)
76                         }, E('em', _('unspecified')));
77                 }
78
79                 if (this.allowany) {
80                         choices['*'] = E('span', {
81                                 'class': 'zonebadge',
82                                 'style': 'background-color:' + firewall.getColorForName(null)
83                         }, [
84                                 E('strong', _('Any zone')),
85                                 (this.allowany && this.allowlocal && !isOutputOnly) ? ' (%s)'.format(_('forward')) : ''
86                         ]);
87                 }
88
89                 for (var i = 0; i < this.zones.length; i++) {
90                         var zone = this.zones[i],
91                             name = zone.getName(),
92                             networks = zone.getNetworks(),
93                             ifaces = [];
94
95                         if (!this.filter(section_id, name))
96                                 continue;
97
98                         for (var j = 0; j < networks.length; j++) {
99                                 var network = this.lookupNetwork(networks[j]);
100
101                                 if (!network)
102                                         continue;
103
104                                 var span = E('span', {
105                                         'class': 'ifacebadge' + (network.getName() == this.network ? ' ifacebadge-active' : '')
106                                 }, network.getName() + ': ');
107
108                                 var devices = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
109
110                                 for (var k = 0; k < devices.length; k++) {
111                                         span.appendChild(E('img', {
112                                                 'title': devices[k].getI18n(),
113                                                 'src': L.resource('icons/%s%s.png'.format(devices[k].getType(), devices[k].isUp() ? '' : '_disabled'))
114                                         }));
115                                 }
116
117                                 if (!devices.length)
118                                         span.appendChild(E('em', _('(empty)')));
119
120                                 ifaces.push(span);
121                         }
122
123                         if (!ifaces.length)
124                                 ifaces.push(E('em', _('(empty)')));
125
126                         choices[name] = E('span', {
127                                 'class': 'zonebadge',
128                                 'style': 'background-color:' + zone.getColor()
129                         }, [ E('strong', name) ].concat(ifaces));
130                 }
131
132                 var widget = new ui.Dropdown(values, choices, {
133                         id: this.cbid(section_id),
134                         sort: true,
135                         multiple: this.multiple,
136                         optional: this.optional || this.rmempty,
137                         disabled: (this.readonly != null) ? this.readonly : this.map.readonly,
138                         select_placeholder: E('em', _('unspecified')),
139                         display_items: this.display_size || this.size || 3,
140                         dropdown_items: this.dropdown_size || this.size || 5,
141                         validate: L.bind(this.validate, this, section_id),
142                         create: !this.nocreate,
143                         create_markup: '' +
144                                 '<li data-value="{{value}}">' +
145                                         '<span class="zonebadge" style="background:repeating-linear-gradient(45deg,rgba(204,204,204,0.5),rgba(204,204,204,0.5) 5px,rgba(255,255,255,0.5) 5px,rgba(255,255,255,0.5) 10px)">' +
146                                                 '<strong>{{value}}:</strong> <em>('+_('create')+')</em>' +
147                                         '</span>' +
148                                 '</li>'
149                 });
150
151                 var elem = widget.render();
152
153                 if (this.option == 'src') {
154                         elem.addEventListener('cbi-dropdown-change', L.bind(function(ev) {
155                                 var opt = this.map.lookupOption('dest', section_id),
156                                     val = ev.detail.instance.getValue();
157
158                                 if (opt == null)
159                                         return;
160
161                                 var cbid = opt[0].cbid(section_id),
162                                     label = document.querySelector('label[for="widget.%s"]'.format(cbid)),
163                                     node = document.getElementById(cbid);
164
165                                 L.dom.content(label, val == '' ? _('Output zone') : _('Destination zone'));
166
167                                 if (val == '') {
168                                         if (L.dom.callClassMethod(node, 'getValue') == '')
169                                                 L.dom.callClassMethod(node, 'setValue', '*');
170
171                                         var emptyval = node.querySelector('[data-value=""]'),
172                                             anyval = node.querySelector('[data-value="*"]');
173
174                                         L.dom.content(anyval.querySelector('span'), E('strong', _('Any zone')));
175
176                                         if (emptyval != null)
177                                                 emptyval.parentNode.removeChild(emptyval);
178                                 }
179                                 else {
180                                         var anyval = node.querySelector('[data-value="*"]'),
181                                             emptyval = node.querySelector('[data-value=""]');
182
183                                         if (emptyval == null) {
184                                                 emptyval = anyval.cloneNode(true);
185                                                 emptyval.removeAttribute('display');
186                                                 emptyval.removeAttribute('selected');
187                                                 emptyval.setAttribute('data-value', '');
188                                         }
189
190                                         L.dom.content(emptyval.querySelector('span'), [
191                                                 E('strong', _('Device')), ' (%s)'.format(_('input'))
192                                         ]);
193
194                                         L.dom.content(anyval.querySelector('span'), [
195                                                 E('strong', _('Any zone')), ' (%s)'.format(_('forward'))
196                                         ]);
197
198                                         anyval.parentNode.insertBefore(emptyval, anyval);
199                                 }
200
201                         }, this));
202                 }
203                 else if (isOutputOnly) {
204                         var emptyval = elem.querySelector('[data-value=""]');
205                         emptyval.parentNode.removeChild(emptyval);
206                 }
207
208                 return elem;
209         },
210 });
211
212 var CBIZoneForwards = form.DummyValue.extend({
213         __name__: 'CBI.ZoneForwards',
214
215         load: function(section_id) {
216                 return Promise.all([
217                         firewall.getDefaults(),
218                         firewall.getZones(),
219                         network.getNetworks(),
220                         network.getDevices()
221                 ]).then(L.bind(function(dznd) {
222                         this.defaults = dznd[0];
223                         this.zones = dznd[1];
224                         this.networks = dznd[2];
225                         this.devices = dznd[3];
226
227                         return this.super('load', section_id);
228                 }, this));
229         },
230
231         renderZone: function(zone) {
232                 var name = zone.getName(),
233                     networks = zone.getNetworks(),
234                     devices = zone.getDevices(),
235                     subnets = zone.getSubnets(),
236                     ifaces = [];
237
238                 for (var j = 0; j < networks.length; j++) {
239                         var network = this.networks.filter(function(net) { return net.getName() == networks[j] })[0];
240
241                         if (!network)
242                                 continue;
243
244                         var span = E('span', {
245                                 'class': 'ifacebadge' + (network.getName() == this.network ? ' ifacebadge-active' : '')
246                         }, network.getName() + ': ');
247
248                         var subdevs = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
249
250                         for (var k = 0; k < subdevs.length && subdevs[k]; k++) {
251                                 span.appendChild(E('img', {
252                                         'title': subdevs[k].getI18n(),
253                                         'src': L.resource('icons/%s%s.png'.format(subdevs[k].getType(), subdevs[k].isUp() ? '' : '_disabled'))
254                                 }));
255                         }
256
257                         if (!subdevs.length)
258                                 span.appendChild(E('em', _('(empty)')));
259
260                         ifaces.push(span);
261                 }
262
263                 for (var i = 0; i < devices.length; i++) {
264                         var device = this.devices.filter(function(dev) { return dev.getName() == devices[i] })[0],
265                             title = device ? device.getI18n() : _('Absent Interface'),
266                             type = device ? device.getType() : 'ethernet',
267                             up = device ? device.isUp() : false;
268
269                         ifaces.push(E('span', { 'class': 'ifacebadge' }, [
270                                 E('img', {
271                                         'title': title,
272                                         'src': L.resource('icons/%s%s.png'.format(type, up ? '' : '_disabled'))
273                                 }),
274                                 device ? device.getName() : devices[i]
275                         ]));
276                 }
277
278                 if (subnets.length > 0)
279                         ifaces.push(E('span', { 'class': 'ifacebadge' }, [ '{ %s }'.format(subnets.join('; ')) ]));
280
281                 if (!ifaces.length)
282                         ifaces.push(E('span', { 'class': 'ifacebadge' }, E('em', _('(empty)'))));
283
284                 return E('label', {
285                         'class': 'zonebadge cbi-tooltip-container',
286                         'style': 'background-color:' + zone.getColor()
287                 }, [
288                         E('strong', name),
289                         E('div', { 'class': 'cbi-tooltip' }, ifaces)
290                 ]);
291         },
292
293         renderWidget: function(section_id, option_index, cfgvalue) {
294                 var value = (cfgvalue != null) ? cfgvalue : this.default,
295                     zone = this.zones.filter(function(z) { return z.getName() == value })[0];
296
297                 if (!zone)
298                         return E([]);
299
300                 var forwards = zone.getForwardingsBy('src'),
301                     dzones = [];
302
303                 for (var i = 0; i < forwards.length; i++) {
304                         var dzone = forwards[i].getDestinationZone();
305
306                         if (!dzone)
307                                 continue;
308
309                         dzones.push(this.renderZone(dzone));
310                 }
311
312                 if (!dzones.length)
313                         dzones.push(E('label', { 'class': 'zonebadge zonebadge-empty' },
314                                 E('strong', this.defaults.getForward())));
315
316                 return E('div', { 'class': 'zone-forwards' }, [
317                         E('div', { 'class': 'zone-src' }, this.renderZone(zone)),
318                         E('span', '⇒'),
319                         E('div', { 'class': 'zone-dest' }, dzones)
320                 ]);
321         },
322 });
323
324 var CBINetworkSelect = form.ListValue.extend({
325         __name__: 'CBI.NetworkSelect',
326
327         load: function(section_id) {
328                 return network.getNetworks().then(L.bind(function(networks) {
329                         this.networks = networks;
330
331                         return this.super('load', section_id);
332                 }, this));
333         },
334
335         filter: function(section_id, value) {
336                 return true;
337         },
338
339         renderIfaceBadge: function(network) {
340                 var span = E('span', { 'class': 'ifacebadge' }, network.getName() + ': '),
341                     devices = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
342
343                 for (var j = 0; j < devices.length && devices[j]; j++) {
344                         span.appendChild(E('img', {
345                                 'title': devices[j].getI18n(),
346                                 'src': L.resource('icons/%s%s.png'.format(devices[j].getType(), devices[j].isUp() ? '' : '_disabled'))
347                         }));
348                 }
349
350                 if (!devices.length) {
351                         span.appendChild(E('em', { 'class': 'hide-close' }, _('(no interfaces attached)')));
352                         span.appendChild(E('em', { 'class': 'hide-open' }, '-'));
353                 }
354
355                 return span;
356         },
357
358         renderWidget: function(section_id, option_index, cfgvalue) {
359                 var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
360                     choices = {},
361                     checked = {};
362
363                 for (var i = 0; i < values.length; i++)
364                         checked[values[i]] = true;
365
366                 values = [];
367
368                 if (!this.multiple && (this.rmempty || this.optional))
369                         choices[''] = E('em', _('unspecified'));
370
371                 for (var i = 0; i < this.networks.length; i++) {
372                         var network = this.networks[i],
373                             name = network.getName();
374
375                         if (name == 'loopback' || name == this.exclude || !this.filter(section_id, name))
376                                 continue;
377
378                         if (this.novirtual && network.isVirtual())
379                                 continue;
380
381                         if (checked[name])
382                                 values.push(name);
383
384                         choices[name] = this.renderIfaceBadge(network);
385                 }
386
387                 var widget = new ui.Dropdown(this.multiple ? values : values[0], choices, {
388                         id: this.cbid(section_id),
389                         sort: true,
390                         multiple: this.multiple,
391                         optional: this.optional || this.rmempty,
392                         disabled: (this.readonly != null) ? this.readonly : this.map.readonly,
393                         select_placeholder: E('em', _('unspecified')),
394                         display_items: this.display_size || this.size || 3,
395                         dropdown_items: this.dropdown_size || this.size || 5,
396                         validate: L.bind(this.validate, this, section_id),
397                         create: !this.nocreate,
398                         create_markup: '' +
399                                 '<li data-value="{{value}}">' +
400                                         '<span class="ifacebadge" style="background:repeating-linear-gradient(45deg,rgba(204,204,204,0.5),rgba(204,204,204,0.5) 5px,rgba(255,255,255,0.5) 5px,rgba(255,255,255,0.5) 10px)">' +
401                                                 '{{value}}: <em>('+_('create')+')</em>' +
402                                         '</span>' +
403                                 '</li>'
404                 });
405
406                 return widget.render();
407         },
408
409         textvalue: function(section_id) {
410                 var cfgvalue = this.cfgvalue(section_id),
411                     values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
412                     rv = E([]);
413
414                 for (var i = 0; i < (this.networks || []).length; i++) {
415                         var network = this.networks[i],
416                             name = network.getName();
417
418                         if (values.indexOf(name) == -1)
419                                 continue;
420
421                         if (rv.length)
422                                 L.dom.append(rv, ' ');
423
424                         L.dom.append(rv, this.renderIfaceBadge(network));
425                 }
426
427                 if (!rv.firstChild)
428                         rv.appendChild(E('em', _('unspecified')));
429
430                 return rv;
431         },
432 });
433
434 var CBIDeviceSelect = form.ListValue.extend({
435         __name__: 'CBI.DeviceSelect',
436
437         load: function(section_id) {
438                 return Promise.all([
439                         network.getDevices(),
440                         this.noaliases ? null : network.getNetworks()
441                 ]).then(L.bind(function(data) {
442                         this.devices = data[0];
443                         this.networks = data[1];
444
445                         return this.super('load', section_id);
446                 }, this));
447         },
448
449         filter: function(section_id, value) {
450                 return true;
451         },
452
453         renderWidget: function(section_id, option_index, cfgvalue) {
454                 var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
455                     choices = {},
456                     checked = {},
457                     order = [];
458
459                 for (var i = 0; i < values.length; i++)
460                         checked[values[i]] = true;
461
462                 values = [];
463
464                 if (!this.multiple && (this.rmempty || this.optional))
465                         choices[''] = E('em', _('unspecified'));
466
467                 for (var i = 0; i < this.devices.length; i++) {
468                         var device = this.devices[i],
469                             name = device.getName(),
470                             type = device.getType();
471
472                         if (name == 'lo' || name == this.exclude || !this.filter(section_id, name))
473                                 continue;
474
475                         if (this.noaliases && type == 'alias')
476                                 continue;
477
478                         if (this.nobridges && type == 'bridge')
479                                 continue;
480
481                         if (this.noinactive && device.isUp() == false)
482                                 continue;
483
484                         var item = E([
485                                 E('img', {
486                                         'title': device.getI18n(),
487                                         'src': L.resource('icons/%s%s.png'.format(type, device.isUp() ? '' : '_disabled'))
488                                 }),
489                                 E('span', { 'class': 'hide-open' }, [ name ]),
490                                 E('span', { 'class': 'hide-close'}, [ device.getI18n() ])
491                         ]);
492
493                         var networks = device.getNetworks();
494
495                         if (networks.length > 0)
496                                 L.dom.append(item.lastChild, [ ' (', networks.map(function(n) { return n.getName() }).join(', '), ')' ]);
497
498                         if (checked[name])
499                                 values.push(name);
500
501                         choices[name] = item;
502                         order.push(name);
503                 }
504
505                 if (this.networks != null) {
506                         for (var i = 0; i < this.networks.length; i++) {
507                                 var net = this.networks[i],
508                                     device = network.instantiateDevice('@%s'.format(net.getName()), net),
509                                     name = device.getName();
510
511                                 if (name == '@loopback' || name == this.exclude || !this.filter(section_id, name))
512                                         continue;
513
514                                 if (this.noinactive && net.isUp() == false)
515                                         continue;
516
517                                 var item = E([
518                                         E('img', {
519                                                 'title': device.getI18n(),
520                                                 'src': L.resource('icons/alias%s.png'.format(net.isUp() ? '' : '_disabled'))
521                                         }),
522                                         E('span', { 'class': 'hide-open' }, [ name ]),
523                                         E('span', { 'class': 'hide-close'}, [ device.getI18n() ])
524                                 ]);
525
526                                 if (checked[name])
527                                         values.push(name);
528
529                                 choices[name] = item;
530                                 order.push(name);
531                         }
532                 }
533
534                 if (!this.nocreate) {
535                         var keys = Object.keys(checked).sort();
536
537                         for (var i = 0; i < keys.length; i++) {
538                                 if (choices.hasOwnProperty(keys[i]))
539                                         continue;
540
541                                 choices[keys[i]] = E([
542                                         E('img', {
543                                                 'title': _('Absent Interface'),
544                                                 'src': L.resource('icons/ethernet_disabled.png')
545                                         }),
546                                         E('span', { 'class': 'hide-open' }, [ keys[i] ]),
547                                         E('span', { 'class': 'hide-close'}, [ '%s: "%h"'.format(_('Absent Interface'), keys[i]) ])
548                                 ]);
549
550                                 values.push(keys[i]);
551                                 order.push(keys[i]);
552                         }
553                 }
554
555                 var widget = new ui.Dropdown(this.multiple ? values : values[0], choices, {
556                         id: this.cbid(section_id),
557                         sort: order,
558                         multiple: this.multiple,
559                         optional: this.optional || this.rmempty,
560                         disabled: (this.readonly != null) ? this.readonly : this.map.readonly,
561                         select_placeholder: E('em', _('unspecified')),
562                         display_items: this.display_size || this.size || 3,
563                         dropdown_items: this.dropdown_size || this.size || 5,
564                         validate: L.bind(this.validate, this, section_id),
565                         create: !this.nocreate,
566                         create_markup: '' +
567                                 '<li data-value="{{value}}">' +
568                                         '<img title="'+_('Custom Interface')+': &quot;{{value}}&quot;" src="'+L.resource('icons/ethernet_disabled.png')+'" />' +
569                                         '<span class="hide-open">{{value}}</span>' +
570                                         '<span class="hide-close">'+_('Custom Interface')+': "{{value}}"</span>' +
571                                 '</li>'
572                 });
573
574                 return widget.render();
575         },
576 });
577
578 var CBIUserSelect = form.ListValue.extend({
579         __name__: 'CBI.UserSelect',
580
581         load: function(section_id) {
582                 return getUsers().then(L.bind(function(users) {
583                         for (var i = 0; i < users.length; i++) {
584                                 this.value(users[i]);
585                         }
586
587                         return this.super('load', section_id);
588                 }, this));
589         },
590
591         filter: function(section_id, value) {
592                 return true;
593         },
594 });
595
596 var CBIGroupSelect = form.ListValue.extend({
597         __name__: 'CBI.GroupSelect',
598
599         load: function(section_id) {
600                 return getGroups().then(L.bind(function(groups) {
601                         for (var i = 0; i < groups.length; i++) {
602                                 this.value(groups[i]);
603                         }
604
605                         return this.super('load', section_id);
606                 }, this));
607         },
608
609         filter: function(section_id, value) {
610                 return true;
611         },
612 });
613
614
615 return L.Class.extend({
616         ZoneSelect: CBIZoneSelect,
617         ZoneForwards: CBIZoneForwards,
618         NetworkSelect: CBINetworkSelect,
619         DeviceSelect: CBIDeviceSelect,
620         UserSelect: CBIUserSelect,
621         GroupSelect: CBIGroupSelect,
622 });