luci-base: add tools.widgets JS library
[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 function toArray(x) {
8         if (x == null)
9                 return [];
10         else if (Array.isArray(x))
11                 return x.map(String);
12         else if (typeof(x) == 'object')
13                 return [ x ];
14
15         var s = String(x).trim();
16
17         if (s == '')
18                 return [];
19
20         return s.split(/\s+/);
21 }
22
23 var CBIZoneSelect = form.ListValue.extend({
24         __name__: 'CBI.ZoneSelect',
25
26         load: function(section_id) {
27                 return Promise.all([ firewall.getZones(), network.getNetworks() ]).then(L.bind(function(zn) {
28                         this.zones = zn[0];
29                         this.networks = zn[1];
30
31                         return this.super('load', section_id);
32                 }, this));
33         },
34
35         filter: function(section_id, value) {
36                 return true;
37         },
38
39         lookupZone: function(name) {
40                 return this.zones.filter(function(zone) { return zone.getName() == name })[0];
41         },
42
43         lookupNetwork: function(name) {
44                 return this.networks.filter(function(network) { return network.getName() == name })[0];
45         },
46
47         renderWidget: function(section_id, option_index, cfgvalue) {
48                 var values = toArray((cfgvalue != null) ? cfgvalue : this.default),
49                     choices = {};
50
51                 if (this.allowlocal) {
52                         choices[''] = E('span', {
53                                 'class': 'zonebadge',
54                                 'style': 'background-color:' + firewall.getColorForName(null)
55                         }, [
56                                 E('strong', _('Device')),
57                                 (this.allowany || this.allowlocal)
58                                         ? ' (%s)'.format(this.alias != 'dest' ? _('output') : _('input')) : ''
59                         ]);
60                 }
61                 else if (!this.multiple && (this.rmempty || this.optional)) {
62                         choices[''] = E('span', {
63                                 'class': 'zonebadge',
64                                 'style': 'background-color:' + firewall.getColorForName(null)
65                         }, E('em', _('unspecified')));
66                 }
67
68                 if (this.allowany) {
69                         choices['*'] = E('span', {
70                                 'class': 'zonebadge',
71                                 'style': 'background-color:' + firewall.getColorForName(null)
72                         }, [
73                                 E('strong', _('Any zone')),
74                                 (this.allowany && this.allowlocal) ? ' (%s)'.format(_('forward')) : ''
75                         ]);
76                 }
77
78                 for (var i = 0; i < this.zones.length; i++) {
79                         var zone = this.zones[i],
80                             name = zone.getName(),
81                             networks = zone.getNetworks(),
82                             ifaces = [];
83
84                         if (!this.filter(section_id, name))
85                                 continue;
86
87                         for (var j = 0; j < networks.length; j++) {
88                                 var network = this.lookupNetwork(networks[j]);
89
90                                 if (!network)
91                                         continue;
92
93                                 var span = E('span', {
94                                         'class': 'ifacebadge' + (network.getName() == this.network ? ' ifacebadge-active' : '')
95                                 }, network.getName() + ': ');
96
97                                 var devices = network.isBridge() ? network.getDevices() : toArray(network.getDevice());
98
99                                 for (var k = 0; k < devices.length; k++) {
100                                         span.appendChild(E('img', {
101                                                 'title': devices[k].getI18n(),
102                                                 'src': L.resource('icons/%s%s.png'.format(devices[k].getType(), devices[k].isUp() ? '' : '_disabled'))
103                                         }));
104                                 }
105
106                                 if (!devices.length)
107                                         span.appendChild(E('em', _('(empty)')));
108
109                                 ifaces.push(span);
110                         }
111
112                         if (!ifaces.length)
113                                 ifaces.push(E('em', _('(empty)')));
114
115                         choices[name] = E('span', {
116                                 'class': 'zonebadge',
117                                 'style': 'background-color:' + zone.getColor()
118                         }, [ E('strong', name) ].concat(ifaces));
119                 }
120
121                 var widget = new ui.Dropdown(values, choices, {
122                         id: this.cbid(section_id),
123                         sort: true,
124                         multiple: this.multiple,
125                         optional: this.optional || this.rmempty,
126                         select_placeholder: E('em', _('unspecified')),
127                         display_items: this.display_size || this.size || 3,
128                         dropdown_items: this.dropdown_size || this.size || 5,
129                         validate: L.bind(this.validate, this, section_id),
130                         create: !this.nocreate,
131                         create_markup: '' +
132                                 '<li data-value="{{value}}">' +
133                                         '<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)">' +
134                                                 '<strong>{{value}}:</strong> <em>('+_('create')+')</em>' +
135                                         '</span>' +
136                                 '</li>'
137                 });
138
139                 return widget.render();
140         },
141 });
142
143 var CBIZoneForwards = form.DummyValue.extend({
144         __name__: 'CBI.ZoneForwards',
145
146         load: function(section_id) {
147                 return Promise.all([ firewall.getDefaults(), firewall.getZones(), network.getNetworks() ]).then(L.bind(function(dzn) {
148                         this.defaults = dzn[0];
149                         this.zones = dzn[1];
150                         this.networks = dzn[2];
151
152                         return this.super('load', section_id);
153                 }, this));
154         },
155
156         renderZone: function(zone) {
157                 var name = zone.getName(),
158                     networks = zone.getNetworks(),
159                     ifaces = [];
160
161                 for (var j = 0; j < networks.length; j++) {
162                         var network = this.networks.filter(function(net) { return net.getName() == networks[j] })[0];
163
164                         if (!network)
165                                 continue;
166
167                         var span = E('span', {
168                                 'class': 'ifacebadge' + (network.getName() == this.network ? ' ifacebadge-active' : '')
169                         }, network.getName() + ': ');
170
171                         var devices = network.isBridge() ? network.getDevices() : toArray(network.getDevice());
172
173                         for (var k = 0; k < devices.length && devices[k]; k++) {
174                                 span.appendChild(E('img', {
175                                         'title': devices[k].getI18n(),
176                                         'src': L.resource('icons/%s%s.png'.format(devices[k].getType(), devices[k].isUp() ? '' : '_disabled'))
177                                 }));
178                         }
179
180                         if (!devices.length)
181                                 span.appendChild(E('em', _('(empty)')));
182
183                         ifaces.push(span);
184                 }
185
186                 if (!ifaces.length)
187                         ifaces.push(E('span', { 'class': 'ifacebadge' }, E('em', _('(empty)'))));
188
189                 return E('label', {
190                         'class': 'zonebadge cbi-tooltip-container',
191                         'style': 'background-color:' + zone.getColor()
192                 }, [
193                         E('strong', name),
194                         E('div', { 'class': 'cbi-tooltip' }, ifaces)
195                 ]);
196         },
197
198         renderWidget: function(section_id, option_index, cfgvalue) {
199                 var value = (cfgvalue != null) ? cfgvalue : this.default,
200                     zone = this.zones.filter(function(z) { return z.getName() == value })[0];
201
202                 if (!zone)
203                         return E([]);
204
205                 var forwards = zone.getForwardingsBy('src'),
206                     dzones = [];
207
208                 for (var i = 0; i < forwards.length; i++) {
209                         var dzone = forwards[i].getDestinationZone();
210
211                         if (!dzone)
212                                 continue;
213
214                         dzones.push(this.renderZone(dzone));
215                 }
216
217                 if (!dzones.length)
218                         dzones.push(E('label', { 'class': 'zonebadge zonebadge-empty' },
219                                 E('strong', this.defaults.getForward())));
220
221                 return E('div', { 'class': 'zone-forwards' }, [
222                         E('div', { 'class': 'zone-src' }, this.renderZone(zone)),
223                         E('span', '⇒'),
224                         E('div', { 'class': 'zone-dest' }, dzones)
225                 ]);
226         },
227 });
228
229 var CBINetworkSelect = form.ListValue.extend({
230         __name__: 'CBI.NetworkSelect',
231
232         load: function(section_id) {
233                 return network.getNetworks().then(L.bind(function(networks) {
234                         this.networks = networks;
235
236                         return this.super('load', section_id);
237                 }, this));
238         },
239
240         filter: function(section_id, value) {
241                 return true;
242         },
243
244         renderWidget: function(section_id, option_index, cfgvalue) {
245                 var values = toArray((cfgvalue != null) ? cfgvalue : this.default),
246                     choices = {},
247                     checked = {};
248
249                 for (var i = 0; i < values.length; i++)
250                         checked[values[i]] = true;
251
252                 values = [];
253
254                 if (!this.multiple && (this.rmempty || this.optional))
255                         choices[''] = E('em', _('unspecified'));
256
257                 for (var i = 0; i < this.networks.length; i++) {
258                         var network = this.networks[i],
259                             name = network.getName();
260
261                         if (name == 'loopback' || !this.filter(section_id, name))
262                                 continue;
263
264                         if (this.novirtual && network.isVirtual())
265                                 continue;
266
267                         var span = E('span', { 'class': 'ifacebadge' }, network.getName() + ': '),
268                             devices = network.isBridge() ? network.getDevices() : toArray(network.getDevice());
269
270                         for (var j = 0; j < devices.length && devices[j]; j++) {
271                                 span.appendChild(E('img', {
272                                         'title': devices[j].getI18n(),
273                                         'src': L.resource('icons/%s%s.png'.format(devices[j].getType(), devices[j].isUp() ? '' : '_disabled'))
274                                 }));
275                         }
276
277                         if (!devices.length) {
278                                 span.appendChild(E('em', { 'class': 'hide-close' }, _('(no interfaces attached)')));
279                                 span.appendChild(E('em', { 'class': 'hide-open' }, '-'));
280                         }
281
282                         if (checked[name])
283                                 values.push(name);
284
285                         choices[name] = span;
286                 }
287
288                 var widget = new ui.Dropdown(this.multiple ? values : values[0], choices, {
289                         id: this.cbid(section_id),
290                         sort: true,
291                         multiple: this.multiple,
292                         optional: this.optional || this.rmempty,
293                         select_placeholder: E('em', _('unspecified')),
294                         display_items: this.display_size || this.size || 3,
295                         dropdown_items: this.dropdown_size || this.size || 5,
296                         validate: L.bind(this.validate, this, section_id),
297                         create: !this.nocreate,
298                         create_markup: '' +
299                                 '<li data-value="{{value}}">' +
300                                         '<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)">' +
301                                                 '{{value}}: <em>('+_('create')+')</em>' +
302                                         '</span>' +
303                                 '</li>'
304                 });
305
306                 return widget.render();
307         },
308 });
309
310
311 return L.Class.extend({
312         ZoneSelect: CBIZoneSelect,
313         ZoneForwards: CBIZoneForwards,
314         NetworkSelect: CBINetworkSelect
315 });