luci-base: widgets.js: CBIZoneSelect: fix availability of "device" choice
[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
7 var CBIZoneSelect = form.ListValue.extend({
8         __name__: 'CBI.ZoneSelect',
9
10         load: function(section_id) {
11                 return Promise.all([ firewall.getZones(), network.getNetworks() ]).then(L.bind(function(zn) {
12                         this.zones = zn[0];
13                         this.networks = zn[1];
14
15                         return this.super('load', section_id);
16                 }, this));
17         },
18
19         filter: function(section_id, value) {
20                 return true;
21         },
22
23         lookupZone: function(name) {
24                 return this.zones.filter(function(zone) { return zone.getName() == name })[0];
25         },
26
27         lookupNetwork: function(name) {
28                 return this.networks.filter(function(network) { return network.getName() == name })[0];
29         },
30
31         renderWidget: function(section_id, option_index, cfgvalue) {
32                 var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
33                     choices = {};
34
35                 if (this.allowlocal) {
36                         choices[''] = E('span', {
37                                 'class': 'zonebadge',
38                                 'style': 'background-color:' + firewall.getColorForName(null)
39                         }, [
40                                 E('strong', _('Device')),
41                                 (this.allowany || this.allowlocal)
42                                         ? ' (%s)'.format(this.option != 'dest' ? _('output') : _('input')) : ''
43                         ]);
44                 }
45                 else if (!this.multiple && (this.rmempty || this.optional)) {
46                         choices[''] = E('span', {
47                                 'class': 'zonebadge',
48                                 'style': 'background-color:' + firewall.getColorForName(null)
49                         }, E('em', _('unspecified')));
50                 }
51
52                 if (this.allowany) {
53                         choices['*'] = E('span', {
54                                 'class': 'zonebadge',
55                                 'style': 'background-color:' + firewall.getColorForName(null)
56                         }, [
57                                 E('strong', _('Any zone')),
58                                 (this.allowany && this.allowlocal && this.option != 'src') ? ' (%s)'.format(_('forward')) : ''
59                         ]);
60                 }
61
62                 for (var i = 0; i < this.zones.length; i++) {
63                         var zone = this.zones[i],
64                             name = zone.getName(),
65                             networks = zone.getNetworks(),
66                             ifaces = [];
67
68                         if (!this.filter(section_id, name))
69                                 continue;
70
71                         for (var j = 0; j < networks.length; j++) {
72                                 var network = this.lookupNetwork(networks[j]);
73
74                                 if (!network)
75                                         continue;
76
77                                 var span = E('span', {
78                                         'class': 'ifacebadge' + (network.getName() == this.network ? ' ifacebadge-active' : '')
79                                 }, network.getName() + ': ');
80
81                                 var devices = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
82
83                                 for (var k = 0; k < devices.length; k++) {
84                                         span.appendChild(E('img', {
85                                                 'title': devices[k].getI18n(),
86                                                 'src': L.resource('icons/%s%s.png'.format(devices[k].getType(), devices[k].isUp() ? '' : '_disabled'))
87                                         }));
88                                 }
89
90                                 if (!devices.length)
91                                         span.appendChild(E('em', _('(empty)')));
92
93                                 ifaces.push(span);
94                         }
95
96                         if (!ifaces.length)
97                                 ifaces.push(E('em', _('(empty)')));
98
99                         choices[name] = E('span', {
100                                 'class': 'zonebadge',
101                                 'style': 'background-color:' + zone.getColor()
102                         }, [ E('strong', name) ].concat(ifaces));
103                 }
104
105                 var widget = new ui.Dropdown(values, choices, {
106                         id: this.cbid(section_id),
107                         sort: true,
108                         multiple: this.multiple,
109                         optional: this.optional || this.rmempty,
110                         select_placeholder: E('em', _('unspecified')),
111                         display_items: this.display_size || this.size || 3,
112                         dropdown_items: this.dropdown_size || this.size || 5,
113                         validate: L.bind(this.validate, this, section_id),
114                         create: !this.nocreate,
115                         create_markup: '' +
116                                 '<li data-value="{{value}}">' +
117                                         '<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)">' +
118                                                 '<strong>{{value}}:</strong> <em>('+_('create')+')</em>' +
119                                         '</span>' +
120                                 '</li>'
121                 });
122
123                 var elem = widget.render();
124
125                 if (this.option == 'src') {
126                         elem.addEventListener('cbi-dropdown-change', L.bind(function(ev) {
127                                 var opt = this.map.lookupOption('dest', section_id),
128                                     val = ev.detail.instance.getValue();
129
130                                 if (opt == null)
131                                         return;
132
133                                 var cbid = opt[0].cbid(section_id),
134                                     label = document.querySelector('label[for="widget.%s"]'.format(cbid)),
135                                     node = document.getElementById(cbid);
136
137                                 L.dom.content(label, val == '' ? _('Output zone') : _('Destination zone'));
138
139                                 if (val == '') {
140                                         if (L.dom.callClassMethod(node, 'getValue') == '')
141                                                 L.dom.callClassMethod(node, 'setValue', '*');
142
143                                         var emptyval = node.querySelector('[data-value=""]'),
144                                             anyval = node.querySelector('[data-value="*"]');
145
146                                         L.dom.content(anyval.querySelector('span'), E('strong', _('Any zone')));
147
148                                         if (emptyval != null)
149                                                 emptyval.parentNode.removeChild(emptyval);
150                                 }
151                                 else {
152                                         var anyval = node.querySelector('[data-value="*"]'),
153                                             emptyval = node.querySelector('[data-value=""]');
154
155                                         if (emptyval == null) {
156                                                 emptyval = anyval.cloneNode(true);
157                                                 emptyval.removeAttribute('display');
158                                                 emptyval.removeAttribute('selected');
159                                                 emptyval.setAttribute('data-value', '');
160                                         }
161
162                                         L.dom.content(emptyval.querySelector('span'), [
163                                                 E('strong', _('Device')), ' (%s)'.format(_('input'))
164                                         ]);
165
166                                         L.dom.content(anyval.querySelector('span'), [
167                                                 E('strong', _('Any zone')), ' (%s)'.format(_('forward'))
168                                         ]);
169
170                                         anyval.parentNode.insertBefore(emptyval, anyval);
171                                 }
172
173                         }, this));
174                 }
175                 else if (this.option == 'dest') {
176                         for (var i = 0; i < this.section.children.length; i++) {
177                                 var opt = this.section.children[i];
178                                 if (opt.option == 'src') {
179                                         if (!opt.cfgvalue(section_id) && !opt.default) {
180                                                 var emptyval = elem.querySelector('[data-value=""]');
181
182                                                 if (emptyval != null)
183                                                         emptyval.parentNode.removeChild(emptyval);
184                                         }
185                                 }
186                         }
187                 }
188
189                 return elem;
190         },
191 });
192
193 var CBIZoneForwards = form.DummyValue.extend({
194         __name__: 'CBI.ZoneForwards',
195
196         load: function(section_id) {
197                 return Promise.all([ firewall.getDefaults(), firewall.getZones(), network.getNetworks() ]).then(L.bind(function(dzn) {
198                         this.defaults = dzn[0];
199                         this.zones = dzn[1];
200                         this.networks = dzn[2];
201
202                         return this.super('load', section_id);
203                 }, this));
204         },
205
206         renderZone: function(zone) {
207                 var name = zone.getName(),
208                     networks = zone.getNetworks(),
209                     ifaces = [];
210
211                 for (var j = 0; j < networks.length; j++) {
212                         var network = this.networks.filter(function(net) { return net.getName() == networks[j] })[0];
213
214                         if (!network)
215                                 continue;
216
217                         var span = E('span', {
218                                 'class': 'ifacebadge' + (network.getName() == this.network ? ' ifacebadge-active' : '')
219                         }, network.getName() + ': ');
220
221                         var devices = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
222
223                         for (var k = 0; k < devices.length && devices[k]; k++) {
224                                 span.appendChild(E('img', {
225                                         'title': devices[k].getI18n(),
226                                         'src': L.resource('icons/%s%s.png'.format(devices[k].getType(), devices[k].isUp() ? '' : '_disabled'))
227                                 }));
228                         }
229
230                         if (!devices.length)
231                                 span.appendChild(E('em', _('(empty)')));
232
233                         ifaces.push(span);
234                 }
235
236                 if (!ifaces.length)
237                         ifaces.push(E('span', { 'class': 'ifacebadge' }, E('em', _('(empty)'))));
238
239                 return E('label', {
240                         'class': 'zonebadge cbi-tooltip-container',
241                         'style': 'background-color:' + zone.getColor()
242                 }, [
243                         E('strong', name),
244                         E('div', { 'class': 'cbi-tooltip' }, ifaces)
245                 ]);
246         },
247
248         renderWidget: function(section_id, option_index, cfgvalue) {
249                 var value = (cfgvalue != null) ? cfgvalue : this.default,
250                     zone = this.zones.filter(function(z) { return z.getName() == value })[0];
251
252                 if (!zone)
253                         return E([]);
254
255                 var forwards = zone.getForwardingsBy('src'),
256                     dzones = [];
257
258                 for (var i = 0; i < forwards.length; i++) {
259                         var dzone = forwards[i].getDestinationZone();
260
261                         if (!dzone)
262                                 continue;
263
264                         dzones.push(this.renderZone(dzone));
265                 }
266
267                 if (!dzones.length)
268                         dzones.push(E('label', { 'class': 'zonebadge zonebadge-empty' },
269                                 E('strong', this.defaults.getForward())));
270
271                 return E('div', { 'class': 'zone-forwards' }, [
272                         E('div', { 'class': 'zone-src' }, this.renderZone(zone)),
273                         E('span', '⇒'),
274                         E('div', { 'class': 'zone-dest' }, dzones)
275                 ]);
276         },
277 });
278
279 var CBINetworkSelect = form.ListValue.extend({
280         __name__: 'CBI.NetworkSelect',
281
282         load: function(section_id) {
283                 return network.getNetworks().then(L.bind(function(networks) {
284                         this.networks = networks;
285
286                         return this.super('load', section_id);
287                 }, this));
288         },
289
290         filter: function(section_id, value) {
291                 return true;
292         },
293
294         renderIfaceBadge: function(network) {
295                 var span = E('span', { 'class': 'ifacebadge' }, network.getName() + ': '),
296                     devices = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
297
298                 for (var j = 0; j < devices.length && devices[j]; j++) {
299                         span.appendChild(E('img', {
300                                 'title': devices[j].getI18n(),
301                                 'src': L.resource('icons/%s%s.png'.format(devices[j].getType(), devices[j].isUp() ? '' : '_disabled'))
302                         }));
303                 }
304
305                 if (!devices.length) {
306                         span.appendChild(E('em', { 'class': 'hide-close' }, _('(no interfaces attached)')));
307                         span.appendChild(E('em', { 'class': 'hide-open' }, '-'));
308                 }
309
310                 return span;
311         },
312
313         renderWidget: function(section_id, option_index, cfgvalue) {
314                 var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
315                     choices = {},
316                     checked = {};
317
318                 for (var i = 0; i < values.length; i++)
319                         checked[values[i]] = true;
320
321                 values = [];
322
323                 if (!this.multiple && (this.rmempty || this.optional))
324                         choices[''] = E('em', _('unspecified'));
325
326                 for (var i = 0; i < this.networks.length; i++) {
327                         var network = this.networks[i],
328                             name = network.getName();
329
330                         if (name == 'loopback' || !this.filter(section_id, name))
331                                 continue;
332
333                         if (this.novirtual && network.isVirtual())
334                                 continue;
335
336                         if (checked[name])
337                                 values.push(name);
338
339                         choices[name] = this.renderIfaceBadge(network);
340                 }
341
342                 var widget = new ui.Dropdown(this.multiple ? values : values[0], choices, {
343                         id: this.cbid(section_id),
344                         sort: true,
345                         multiple: this.multiple,
346                         optional: this.optional || this.rmempty,
347                         select_placeholder: E('em', _('unspecified')),
348                         display_items: this.display_size || this.size || 3,
349                         dropdown_items: this.dropdown_size || this.size || 5,
350                         validate: L.bind(this.validate, this, section_id),
351                         create: !this.nocreate,
352                         create_markup: '' +
353                                 '<li data-value="{{value}}">' +
354                                         '<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)">' +
355                                                 '{{value}}: <em>('+_('create')+')</em>' +
356                                         '</span>' +
357                                 '</li>'
358                 });
359
360                 return widget.render();
361         },
362
363         textvalue: function(section_id) {
364                 var cfgvalue = this.cfgvalue(section_id),
365                     values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
366                     rv = E([]);
367
368                 for (var i = 0; i < (this.networks || []).length; i++) {
369                         var network = this.networks[i],
370                             name = network.getName();
371
372                         if (values.indexOf(name) == -1)
373                                 continue;
374
375                         if (rv.length)
376                                 L.dom.append(rv, ' ');
377
378                         L.dom.append(rv, this.renderIfaceBadge(network));
379                 }
380
381                 if (!rv.firstChild)
382                         rv.appendChild(E('em', _('unspecified')));
383
384                 return rv;
385         },
386 });
387
388
389 return L.Class.extend({
390         ZoneSelect: CBIZoneSelect,
391         ZoneForwards: CBIZoneForwards,
392         NetworkSelect: CBINetworkSelect
393 });