luci-app-transmission: port to client side
[project/luci.git] / applications / luci-app-firewall / htdocs / luci-static / resources / view / firewall / rules.js
1 'use strict';
2 'require ui';
3 'require rpc';
4 'require uci';
5 'require form';
6 'require tools.firewall as fwtool';
7 'require tools.widgets as widgets';
8
9 function fmt(fmt /*, ...*/) {
10 var repl = [], wrap = false;
11
12 for (var i = 1; i < arguments.length; i++) {
13 if (L.dom.elem(arguments[i])) {
14 switch (arguments[i].nodeType) {
15 case 1:
16 repl.push(arguments[i].outerHTML);
17 wrap = true;
18 break;
19
20 case 3:
21 repl.push(arguments[i].data);
22 break;
23
24 case 11:
25 var span = E('span');
26 span.appendChild(arguments[i]);
27 repl.push(span.innerHTML);
28 wrap = true;
29 break;
30
31 default:
32 repl.push('');
33 }
34 }
35 else {
36 repl.push(arguments[i]);
37 }
38 }
39
40 var rv = fmt.format.apply(fmt, repl);
41 return wrap ? E('span', rv) : rv;
42 }
43
44 function forward_proto_txt(s) {
45 return fmt('%s-%s',
46 fwtool.fmt_family(uci.get('firewall', s, 'family')),
47 fwtool.fmt_proto(uci.get('firewall', s, 'proto'),
48 uci.get('firewall', s, 'icmp_type')) || 'TCP+UDP');
49 }
50
51 function rule_src_txt(s) {
52 var z = fwtool.fmt_zone(uci.get('firewall', s, 'src')),
53 p = fwtool.fmt_port(uci.get('firewall', s, 'src_port')),
54 m = fwtool.fmt_mac(uci.get('firewall', s, 'src_mac'));
55
56 // Forward/Input
57 if (z) {
58 var a = fwtool.fmt_ip(uci.get('firewall', s, 'src_ip'), _('any host'));
59 if (p && m)
60 return fmt(_('From %s in %s with source %s and %s'), a, z, p, m);
61 else if (p || m)
62 return fmt(_('From %s in %s with source %s'), a, z, p || m);
63 else
64 return fmt(_('From %s in %s'), a, z);
65 }
66
67 // Output
68 else {
69 var a = fwtool.fmt_ip(uci.get('firewall', s, 'src_ip'), _('any router IP'));
70 if (p && m)
71 return fmt(_('From %s on <var>this device</var> with source %s and %s'), a, p, m);
72 else if (p || m)
73 return fmt(_('From %s on <var>this device</var> with source %s'), a, p || m);
74 else
75 return fmt(_('From %s on <var>this device</var>'), a);
76 }
77 }
78
79 function rule_dest_txt(s) {
80 var z = fwtool.fmt_zone(uci.get('firewall', s, 'dest')),
81 p = fwtool.fmt_port(uci.get('firewall', s, 'dest_port'));
82
83 // Forward
84 if (z) {
85 var a = fwtool.fmt_ip(uci.get('firewall', s, 'dest_ip'), _('any host'));
86 if (p)
87 return fmt(_('To %s, %s in %s'), a, p, z);
88 else
89 return fmt(_('To %s in %s'), a, z);
90 }
91
92 // Input
93 else {
94 var a = fwtool.fmt_ip(uci.get('firewall', s, 'dest_ip'), _('any router IP'));
95 if (p)
96 return fmt(_('To %s at %s on <var>this device</var>'), a, p);
97 else
98 return fmt(_('To %s on <var>this device</var>'), a);
99 }
100 }
101
102 function rule_target_txt(s) {
103 var t = fwtool.fmt_target(uci.get('firewall', s, 'target'), uci.get('firewall', s, 'src'), uci.get('firewall', s, 'dest')),
104 l = fwtool.fmt_limit(uci.get('firewall', s, 'limit'), uci.get('firewall', s, 'limit_burst'));
105
106 if (l)
107 return fmt(_('<var>%s</var> and limit to %s'), t, l);
108 else
109 return fmt('<var>%s</var>', t);
110 }
111
112 function update_ip_hints(map, section_id, family, hosts) {
113 var elem_src_ip = map.lookupOption('src_ip', section_id)[0].getUIElement(section_id),
114 elem_dst_ip = map.lookupOption('dest_ip', section_id)[0].getUIElement(section_id),
115 choice_values = [], choice_labels = {};
116
117 elem_src_ip.clearChoices();
118 elem_dst_ip.clearChoices();
119
120 if (!family || family == 'ipv4') {
121 L.sortedKeys(hosts, 'ipv4', 'addr').forEach(function(mac) {
122 var val = hosts[mac].ipv4,
123 txt = '%s (<strong>%s</strong>)'.format(val, hosts[mac].name || mac);
124
125 choice_values.push(val);
126 choice_labels[val] = txt;
127 });
128 }
129
130 if (!family || family == 'ipv6') {
131 L.sortedKeys(hosts, 'ipv6', 'addr').forEach(function(mac) {
132 var val = hosts[mac].ipv6,
133 txt = '%s (<strong>%s</strong>)'.format(val, hosts[mac].name || mac);
134
135 choice_values.push(val);
136 choice_labels[val] = txt;
137 });
138 }
139
140 elem_src_ip.addChoices(choice_values, choice_labels);
141 elem_dst_ip.addChoices(choice_values, choice_labels);
142 }
143
144 return L.view.extend({
145 callHostHints: rpc.declare({
146 object: 'luci-rpc',
147 method: 'getHostHints',
148 expect: { '': {} }
149 }),
150
151 callConntrackHelpers: rpc.declare({
152 object: 'luci',
153 method: 'getConntrackHelpers',
154 expect: { result: [] }
155 }),
156
157 load: function() {
158 return Promise.all([
159 this.callHostHints(),
160 this.callConntrackHelpers()
161 ]);
162 },
163
164 render: function(data) {
165 var hosts = data[0],
166 ctHelpers = data[1],
167 m, s, o;
168
169 m = new form.Map('firewall', _('Firewall - Traffic Rules'),
170 _('Traffic rules define policies for packets traveling between different zones, for example to reject traffic between certain hosts or to open WAN ports on the router.'));
171
172 s = m.section(form.GridSection, 'rule', _('Traffic Rules'));
173 s.addremove = true;
174 s.anonymous = true;
175 s.sortable = true;
176
177 s.tab('general', _('General Settings'));
178 s.tab('advanced', _('Advanced Settings'));
179 s.tab('timed', _('Time Restrictions'));
180
181 s.filter = function(section_id) {
182 return (uci.get('firewall', section_id, 'target') != 'SNAT');
183 };
184
185 s.sectiontitle = function(section_id) {
186 return uci.get('firewall', section_id, 'name') || _('Unnamed rule');
187 };
188
189 s.handleAdd = function(ev) {
190 var config_name = this.uciconfig || this.map.config,
191 section_id = uci.add(config_name, this.sectiontype),
192 opt1, opt2;
193
194 for (var i = 0; i < this.children.length; i++)
195 if (this.children[i].option == 'src')
196 opt1 = this.children[i];
197 else if (this.children[i].option == 'dest')
198 opt2 = this.children[i];
199
200 opt1.default = 'wan';
201 opt2.default = 'lan';
202
203 this.addedSection = section_id;
204 this.renderMoreOptionsModal(section_id);
205
206 delete opt1.default;
207 delete opt2.default;
208 };
209
210 o = s.taboption('general', form.Value, 'name', _('Name'));
211 o.placeholder = _('Unnamed rule');
212 o.modalonly = true;
213
214 o = s.option(form.DummyValue, '_match', _('Match'));
215 o.modalonly = false;
216 o.textvalue = function(s) {
217 return E('small', [
218 forward_proto_txt(s), E('br'),
219 rule_src_txt(s), E('br'),
220 rule_dest_txt(s)
221 ]);
222 };
223
224 o = s.option(form.ListValue, '_target', _('Action'));
225 o.modalonly = false;
226 o.textvalue = function(s) {
227 return rule_target_txt(s);
228 };
229
230 o = s.option(form.Flag, 'enabled', _('Enable'));
231 o.modalonly = false;
232 o.default = o.enabled;
233 o.editable = true;
234
235
236 o = s.taboption('advanced', form.ListValue, 'direction', _('Match device'));
237 o.modalonly = true;
238 o.value('', _('unspecified'));
239 o.value('in', _('Inbound device'));
240 o.value('out', _('Outbound device'));
241 o.cfgvalue = function(section_id) {
242 var val = uci.get('firewall', section_id, 'direction');
243 switch (val) {
244 case 'in':
245 case 'ingress':
246 return 'in';
247
248 case 'out':
249 case 'egress':
250 return 'out';
251 }
252
253 return null;
254 };
255
256 o = s.taboption('advanced', widgets.DeviceSelect, 'device', _('Device name'),
257 _('Specifies whether to tie this traffic rule to a specific inbound or outbound network device.'));
258 o.modalonly = true;
259 o.noaliases = true;
260 o.rmempty = false;
261 o.depends('direction', 'in');
262 o.depends('direction', 'out');
263
264 o = s.taboption('advanced', form.ListValue, 'family', _('Restrict to address family'));
265 o.modalonly = true;
266 o.rmempty = true;
267 o.value('', _('IPv4 and IPv6'));
268 o.value('ipv4', _('IPv4 only'));
269 o.value('ipv6', _('IPv6 only'));
270 o.validate = function(section_id, value) {
271 update_ip_hints(this.map, section_id, value, hosts);
272 return true;
273 };
274
275 o = s.taboption('general', form.Value, 'proto', _('Protocol'));
276 o.modalonly = true;
277 o.default = 'tcp udp';
278 o.value('all', _('Any'));
279 o.value('tcp udp', 'TCP+UDP');
280 o.value('tcp', 'TCP');
281 o.value('udp', 'UDP');
282 o.value('icmp', 'ICMP');
283 o.cfgvalue = function(/* ... */) {
284 var v = this.super('cfgvalue', arguments);
285 return (v == 'tcpudp') ? 'tcp udp' : v;
286 };
287
288 o = s.taboption('advanced', form.MultiValue, 'icmp_type', _('Match ICMP type'));
289 o.modalonly = true;
290 o.multiple = true;
291 o.custom = true;
292 o.cast = 'table';
293 o.placeholder = _('any');
294 o.value('', 'any');
295 o.value('address-mask-reply');
296 o.value('address-mask-request');
297 o.value('communication-prohibited');
298 o.value('destination-unreachable');
299 o.value('echo-reply');
300 o.value('echo-request');
301 o.value('fragmentation-needed');
302 o.value('host-precedence-violation');
303 o.value('host-prohibited');
304 o.value('host-redirect');
305 o.value('host-unknown');
306 o.value('host-unreachable');
307 o.value('ip-header-bad');
308 o.value('neighbour-advertisement');
309 o.value('neighbour-solicitation');
310 o.value('network-prohibited');
311 o.value('network-redirect');
312 o.value('network-unknown');
313 o.value('network-unreachable');
314 o.value('parameter-problem');
315 o.value('port-unreachable');
316 o.value('precedence-cutoff');
317 o.value('protocol-unreachable');
318 o.value('redirect');
319 o.value('required-option-missing');
320 o.value('router-advertisement');
321 o.value('router-solicitation');
322 o.value('source-quench');
323 o.value('source-route-failed');
324 o.value('time-exceeded');
325 o.value('timestamp-reply');
326 o.value('timestamp-request');
327 o.value('TOS-host-redirect');
328 o.value('TOS-host-unreachable');
329 o.value('TOS-network-redirect');
330 o.value('TOS-network-unreachable');
331 o.value('ttl-zero-during-reassembly');
332 o.value('ttl-zero-during-transit');
333 o.depends('proto', 'icmp');
334
335 o = s.taboption('general', widgets.ZoneSelect, 'src', _('Source zone'));
336 o.modalonly = true;
337 o.nocreate = true;
338 o.allowany = true;
339 o.allowlocal = 'src';
340
341 o = s.taboption('advanced', form.Value, 'src_mac', _('Source MAC address'));
342 o.modalonly = true;
343 o.datatype = 'list(macaddr)';
344 o.placeholder = _('any');
345 L.sortedKeys(hosts).forEach(function(mac) {
346 o.value(mac, '%s (%s)'.format(
347 mac,
348 hosts[mac].name || hosts[mac].ipv4 || hosts[mac].ipv6 || '?'
349 ));
350 });
351
352 o = s.taboption('general', form.Value, 'src_ip', _('Source address'));
353 o.modalonly = true;
354 o.datatype = 'list(neg(ipmask))';
355 o.placeholder = _('any');
356 o.transformChoices = function() { return {} }; /* force combobox rendering */
357
358 o = s.taboption('general', form.Value, 'src_port', _('Source port'));
359 o.modalonly = true;
360 o.datatype = 'list(neg(portrange))';
361 o.placeholder = _('any');
362 o.depends('proto', 'tcp');
363 o.depends('proto', 'udp');
364 o.depends('proto', 'tcp udp');
365 o.depends('proto', 'tcpudp');
366
367 o = s.taboption('general', widgets.ZoneSelect, 'dest', _('Destination zone'));
368 o.modalonly = true;
369 o.nocreate = true;
370 o.allowany = true;
371 o.allowlocal = true;
372
373 o = s.taboption('general', form.Value, 'dest_ip', _('Destination address'));
374 o.modalonly = true;
375 o.datatype = 'list(neg(ipmask))';
376 o.placeholder = _('any');
377 o.transformChoices = function() { return {} }; /* force combobox rendering */
378
379 o = s.taboption('general', form.Value, 'dest_port', _('Destination port'));
380 o.modalonly = true;
381 o.datatype = 'list(neg(portrange))';
382 o.placeholder = _('any');
383 o.depends('proto', 'tcp');
384 o.depends('proto', 'udp');
385 o.depends('proto', 'tcp udp');
386 o.depends('proto', 'tcpudp');
387
388 o = s.taboption('general', form.ListValue, 'target', _('Action'));
389 o.modalonly = true;
390 o.default = 'ACCEPT';
391 o.value('DROP', _('drop'));
392 o.value('ACCEPT', _('accept'));
393 o.value('REJECT', _('reject'));
394 o.value('NOTRACK', _("don't track"));
395 o.value('HELPER', _('assign conntrack helper'));
396 o.value('MARK_SET', _('apply firewall mark'));
397 o.value('MARK_XOR', _('XOR firewall mark'));
398 o.value('DSCP', _('DSCP classification'));
399 o.cfgvalue = function(section_id) {
400 var t = uci.get('firewall', section_id, 'target'),
401 m = uci.get('firewall', section_id, 'set_mark');
402
403 if (t == 'MARK')
404 return m ? 'MARK_SET' : 'MARK_XOR';
405
406 return t;
407 };
408 o.write = function(section_id, value) {
409 return this.super('write', [section_id, (value == 'MARK_SET' || value == 'MARK_XOR') ? 'MARK' : value]);
410 };
411
412 fwtool.addMarkOption(s, 1);
413 fwtool.addMarkOption(s, 2);
414 fwtool.addDSCPOption(s, true);
415
416 o = s.taboption('general', form.ListValue, 'set_helper', _('Tracking helper'), _('Assign the specified connection tracking helper to matched traffic.'));
417 o.modalonly = true;
418 o.placeholder = _('any');
419 o.depends('target', 'HELPER');
420 for (var i = 0; i < ctHelpers.length; i++)
421 o.value(ctHelpers[i].name, '%s (%s)'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase()));
422
423 o = s.taboption('advanced', form.Value, 'helper', _('Match helper'), _('Match traffic using the specified connection tracking helper.'));
424 o.modalonly = true;
425 o.placeholder = _('any');
426 for (var i = 0; i < ctHelpers.length; i++)
427 o.value(ctHelpers[i].name, '%s (%s)'.format(ctHelpers[i].description, ctHelpers[i].name.toUpperCase()));
428 o.validate = function(section_id, value) {
429 if (value == '' || value == null)
430 return true;
431
432 value = value.replace(/^!\s*/, '');
433
434 for (var i = 0; i < ctHelpers.length; i++)
435 if (value == ctHelpers[i].name)
436 return true;
437
438 return _('Unknown or not installed conntrack helper "%s"').format(value);
439 };
440
441 fwtool.addMarkOption(s, false);
442 fwtool.addDSCPOption(s, false);
443 fwtool.addLimitOption(s);
444 fwtool.addLimitBurstOption(s);
445
446 o = s.taboption('advanced', form.Value, 'extra', _('Extra arguments'),
447 _('Passes additional arguments to iptables. Use with care!'));
448 o.modalonly = true;
449
450 o = s.taboption('timed', form.MultiValue, 'weekdays', _('Week Days'));
451 o.modalonly = true;
452 o.multiple = true;
453 o.display = 5;
454 o.placeholder = _('Any day');
455 o.value('Sun', _('Sunday'));
456 o.value('Mon', _('Monday'));
457 o.value('Tue', _('Tuesday'));
458 o.value('Wed', _('Wednesday'));
459 o.value('Thu', _('Thursday'));
460 o.value('Fri', _('Friday'));
461 o.value('Sat', _('Saturday'));
462 o.write = function(section_id, value) {
463 return this.super('write', [ section_id, L.toArray(value).join(' ') ]);
464 };
465
466 o = s.taboption('timed', form.MultiValue, 'monthdays', _('Month Days'));
467 o.modalonly = true;
468 o.multiple = true;
469 o.display_size = 15;
470 o.placeholder = _('Any day');
471 o.write = function(section_id, value) {
472 return this.super('write', [ section_id, L.toArray(value).join(' ') ]);
473 };
474 for (var i = 1; i <= 31; i++)
475 o.value(i);
476
477 o = s.taboption('timed', form.Value, 'start_time', _('Start Time (hh.mm.ss)'));
478 o.modalonly = true;
479 o.datatype = 'timehhmmss';
480
481 o = s.taboption('timed', form.Value, 'stop_time', _('Stop Time (hh.mm.ss)'));
482 o.modalonly = true;
483 o.datatype = 'timehhmmss';
484
485 o = s.taboption('timed', form.Value, 'start_date', _('Start Date (yyyy-mm-dd)'));
486 o.modalonly = true;
487 o.datatype = 'dateyyyymmdd';
488
489 o = s.taboption('timed', form.Value, 'stop_date', _('Stop Date (yyyy-mm-dd)'));
490 o.modalonly = true;
491 o.datatype = 'dateyyyymmdd';
492
493 o = s.taboption('timed', form.Flag, 'utc_time', _('Time in UTC'));
494 o.modalonly = true;
495 o.default = o.disabled;
496
497 return m.render();
498 }
499 });