e79cb2aeffa4a1e5ee1e77b0a620622d47e7e2f2
[project/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 });