Merge pull request #3352 from NeoRaider/move-luasrcdiet
[project/luci.git] / modules / luci-mod-status / htdocs / luci-static / resources / view / status / iptables.js
1 'use strict';
2 'require fs';
3 'require ui';
4
5 var table_names = [ 'Filter', 'NAT', 'Mangle', 'Raw' ];
6
7 return L.view.extend({
8 load: function() {
9 return L.resolveDefault(fs.stat('/usr/sbin/ip6tables'));
10 },
11
12 createTableSection: function(is_ipv6, table) {
13 var idiv = document.querySelector('div[data-tab="%s"]'.format(is_ipv6 ? 'ip6tables' : 'iptables')),
14 tdiv = idiv.querySelector('[data-table="%s-%s"]'.format(is_ipv6 ? 'ipv6' : 'ipv4', table)),
15 title = '%s: %s'.format(_('Table'), table);
16
17 if (!tdiv) {
18 tdiv = E('div', { 'data-table': '%s-%s'.format(is_ipv6 ? 'ipv6' : 'ipv4', table) }, [
19 E('h3', {}, title),
20 E('div')
21 ]);
22
23 if (idiv.firstElementChild.nodeName.toLowerCase() === 'p')
24 idiv.removeChild(idiv.firstElementChild);
25
26 var added = false, thisIdx = table_names.indexOf(table);
27
28 idiv.querySelectorAll('[data-table]').forEach(function(child) {
29 var childIdx = table_names.indexOf(child.getAttribute('data-table').split(/-/)[1]);
30
31 if (added === false && childIdx > thisIdx) {
32 idiv.insertBefore(tdiv, child);
33 added = true;
34 }
35 });
36
37 if (added === false)
38 idiv.appendChild(tdiv);
39 }
40
41 return tdiv.lastElementChild;
42 },
43
44 createChainSection: function(is_ipv6, table, chain, policy, packets, bytes, references) {
45 var tdiv = this.createTableSection(is_ipv6, table),
46 cdiv = tdiv.querySelector('[data-chain="%s"]'.format(chain)),
47 title;
48
49 if (policy)
50 title = '%s <em>%s</em> <span>(%s: <em>%s</em>, %d %s, %.2mB %s)</span>'
51 .format(_('Chain'), chain, _('Policy'), policy, packets, _('Packets'), bytes, _('Traffic'));
52 else
53 title = '%s <em>%s</em> <span class="references">(%d %s)</span>'
54 .format(_('Chain'), chain, references, _('References'));
55
56 if (!cdiv) {
57 cdiv = E('div', { 'data-chain': chain }, [
58 E('h4', { 'id': 'rule_%s-%s_%s'.format(is_ipv6 ? 'ipv6' : 'ipv4', table.toLowerCase(), chain) }, title),
59 E('div', { 'class': 'table' }, [
60 E('div', { 'class': 'tr table-titles' }, [
61 E('div', { 'class': 'th center' }, _('Pkts.')),
62 E('div', { 'class': 'th center' }, _('Traffic')),
63 E('div', { 'class': 'th' }, _('Target')),
64 E('div', { 'class': 'th' }, _('Prot.')),
65 E('div', { 'class': 'th' }, _('In')),
66 E('div', { 'class': 'th' }, _('Out')),
67 E('div', { 'class': 'th' }, _('Source')),
68 E('div', { 'class': 'th' }, _('Destination')),
69 E('div', { 'class': 'th' }, _('Options')),
70 E('div', { 'class': 'th' }, _('Comment'))
71 ])
72 ])
73 ]);
74
75 tdiv.appendChild(cdiv);
76 }
77 else {
78 cdiv.firstElementChild.innerHTML = title;
79 }
80
81 return cdiv.lastElementChild;
82 },
83
84 updateChainSection: function(chaintable, rows) {
85 if (!chaintable)
86 return;
87
88 cbi_update_table(chaintable, rows, _('No rules in this chain.'));
89
90 if (rows.length === 0 &&
91 document.querySelector('[data-hide-empty="true"]'))
92 chaintable.parentNode.style.display = 'none';
93 else
94 chaintable.parentNode.style.display = '';
95
96 chaintable.parentNode.setAttribute('data-empty', rows.length === 0);
97 },
98
99 parseIptablesDump: function(is_ipv6, table, s) {
100 var current_chain = null;
101 var current_rules = [];
102 var seen_chains = {};
103 var chain_refs = {};
104 var re = /([^\n]*)\n/g;
105 var m, m2;
106
107 while ((m = re.exec(s)) != null) {
108 if (m[1].match(/^Chain (.+) \(policy (\w+) (\d+) packets, (\d+) bytes\)$/)) {
109 var chain = RegExp.$1,
110 policy = RegExp.$2,
111 packets = +RegExp.$3,
112 bytes = +RegExp.$4;
113
114 this.updateChainSection(current_chain, current_rules);
115
116 seen_chains[chain] = true;
117 current_chain = this.createChainSection(is_ipv6, table, chain, policy, packets, bytes);
118 current_rules = [];
119 }
120 else if (m[1].match(/^Chain (.+) \((\d+) references\)$/)) {
121 var chain = RegExp.$1,
122 references = +RegExp.$2;
123
124 this.updateChainSection(current_chain, current_rules);
125
126 seen_chains[chain] = true;
127 current_chain = this.createChainSection(is_ipv6, table, chain, null, null, null, references);
128 current_rules = [];
129 }
130 else if (m[1].match(/^num /)) {
131 continue;
132 }
133 else if ((m2 = m[1].match(/^(\d+) +(\d+) +(\d+) +(.*?) +(\S+) +(\S*) +(\S+) +(\S+) +([a-f0-9:.]+(?:\/[a-f0-9:.]+)?) +([a-f0-9:.]+(?:\/[a-f0-9:.]+)?) +(.+)$/)) !== null) {
134 var num = +m2[1],
135 pkts = +m2[2],
136 bytes = +m2[3],
137 target = m2[4],
138 proto = m2[5],
139 indev = m2[7],
140 outdev = m2[8],
141 srcnet = m2[9],
142 dstnet = m2[10],
143 options = m2[11] || '-',
144 comment = '-';
145
146 options = options.trim().replace(/(?:^| )\/\* (.+) \*\//,
147 function(m1, m2) {
148 comment = m2.replace(/^!fw3(: |$)/, '').trim() || '-';
149 return '';
150 }) || '-';
151
152 current_rules.push([
153 '%.2m'.format(pkts).nobr(),
154 '%.2mB'.format(bytes).nobr(),
155 target ? '<span class="target">%s</span>'.format(target) : '-',
156 proto,
157 (indev !== '*') ? '<span class="ifacebadge">%s</span>'.format(indev) : '*',
158 (outdev !== '*') ? '<span class="ifacebadge">%s</span>'.format(outdev) : '*',
159 srcnet,
160 dstnet,
161 options,
162 [ comment ]
163 ]);
164
165 if (target) {
166 chain_refs[target] = chain_refs[target] || [];
167 chain_refs[target].push([ current_chain, num ]);
168 }
169 }
170 }
171
172 this.updateChainSection(current_chain, current_rules);
173
174 document.querySelectorAll('[data-table="%s-%s"] [data-chain]'.format(is_ipv6 ? 'ipv6' : 'ipv4', table)).forEach(L.bind(function(cdiv) {
175 if (!seen_chains[cdiv.getAttribute('data-chain')]) {
176 cdiv.parentNode.removeChild(cdiv);
177 return;
178 }
179
180 cdiv.querySelectorAll('.target').forEach(L.bind(function(tspan) {
181 if (seen_chains[tspan.textContent]) {
182 tspan.classList.add('jump');
183 tspan.addEventListener('click', this.handleJumpTarget);
184 }
185 }, this));
186
187 cdiv.querySelectorAll('.references').forEach(L.bind(function(rspan) {
188 var refs = chain_refs[cdiv.getAttribute('data-chain')];
189 if (refs && refs.length) {
190 rspan.classList.add('cbi-tooltip-container');
191 rspan.appendChild(E('small', { 'class': 'cbi-tooltip ifacebadge', 'style': 'top:1em; left:auto' }, [ E('ul') ]));
192
193 refs.forEach(L.bind(function(ref) {
194 var chain = ref[0].parentNode.getAttribute('data-chain'),
195 num = ref[1];
196
197 rspan.lastElementChild.lastElementChild.appendChild(E('li', {}, [
198 _('Chain'), ' ',
199 E('span', {
200 'class': 'jump',
201 'data-num': num,
202 'click': this.handleJumpTarget
203 }, chain),
204 ', %s #%d'.format(_('Rule'), num)
205 ]));
206 }, this));
207 }
208 }, this));
209 }, this));
210 },
211
212 pollFirewallLists: function(has_ip6tables) {
213 var cmds = [ '/usr/sbin/iptables' ];
214
215 if (has_ip6tables)
216 cmds.push('/usr/sbin/ip6tables');
217
218 L.Poll.add(L.bind(function() {
219 var tasks = [];
220
221 for (var i = 0; i < cmds.length; i++) {
222 for (var j = 0; j < table_names.length; j++) {
223 tasks.push(L.resolveDefault(
224 fs.exec_direct(cmds[i], [ '--line-numbers', '-w', '-nvxL', '-t', table_names[j].toLowerCase() ])
225 .then(this.parseIptablesDump.bind(this, i > 0, table_names[j]))));
226 }
227 }
228
229 return Promise.all(tasks);
230 }, this));
231 },
232
233 handleJumpTarget: function(ev) {
234 var link = ev.target,
235 table = findParent(link, '[data-table]').getAttribute('data-table'),
236 chain = link.textContent,
237 num = +link.getAttribute('data-num'),
238 elem = document.getElementById('rule_%s_%s'.format(table.toLowerCase(), chain));
239
240 if (elem) {
241 (document.documentElement || document.body.parentNode || document.body).scrollTop = elem.offsetTop - 40;
242 elem.classList.remove('flash');
243 void elem.offsetWidth;
244 elem.classList.add('flash');
245
246 if (num) {
247 var rule = elem.nextElementSibling.childNodes[num];
248 if (rule) {
249 rule.classList.remove('flash');
250 void rule.offsetWidth;
251 rule.classList.add('flash');
252 }
253 }
254 }
255 },
256
257 handleHideEmpty: function(ev) {
258 var btn = ev.currentTarget,
259 hide = (btn.getAttribute('data-hide-empty') === 'false');
260
261 btn.setAttribute('data-hide-empty', hide);
262 btn.firstChild.data = hide ? _('Show empty chains') : _('Hide empty chains');
263 btn.blur();
264
265 document.querySelectorAll('[data-chain][data-empty="true"]')
266 .forEach(function(chaintable) {
267 chaintable.style.display = hide ? 'none' : '';
268 });
269 },
270
271 handleCounterReset: function(has_ip6tables, ev) {
272 return Promise.all([
273 fs.exec('/usr/sbin/iptables', [ '-Z' ])
274 .catch(function(err) { ui.addNotification(null, E('p', {}, _('Unable to reset iptables counters: %s').format(err.message))) }),
275 has_ip6tables ? fs.exec('/usr/sbin/ip6tables', [ '-Z' ])
276 .catch(function(err) { ui.addNotification(null, E('p', {}, _('Unable to reset ip6tables counters: %s').format(err.message))) }) : null
277 ]);
278 },
279
280 handleRestart: function(ev) {
281 return fs.exec_direct('/etc/init.d/firewall', [ 'restart' ])
282 .catch(function(err) { ui.addNotification(null, E('p', {}, _('Unable to restart firewall: %s').format(err.message))) });
283 },
284
285 render: function(has_ip6tables) {
286 var view = E([], [
287 E('style', { 'type': 'text/css' }, [
288 '.cbi-tooltip-container, span.jump { border-bottom:1px dotted #00f;cursor:pointer }',
289 'ul { list-style:none }',
290 '.references { position:relative }',
291 '.references .cbi-tooltip { left:0!important;top:1.5em!important }',
292 'h4>span { font-size:90% }'
293 ]),
294
295 E('h2', {}, [ _('Firewall Status') ]),
296 E('div', { 'class': 'right', 'style': 'margin-bottom:-1.5em' }, [
297 E('button', {
298 'class': 'cbi-button',
299 'data-hide-empty': false,
300 'click': ui.createHandlerFn(this, 'handleHideEmpty')
301 }, [ _('Hide empty chains') ]),
302 ' ',
303 E('button', {
304 'class': 'cbi-button',
305 'click': ui.createHandlerFn(this, 'handleCounterReset', has_ip6tables)
306 }, [ _('Reset Counters') ]),
307 ' ',
308 E('button', {
309 'class': 'cbi-button',
310 'click': ui.createHandlerFn(this, 'handleRestart')
311 }, [ _('Restart Firewall') ])
312 ]),
313 E('div', {}, [
314 E('div', { 'data-tab': 'iptables', 'data-tab-title': has_ip6tables ? _('IPv4 Firewall') : null }, [
315 E('p', {}, E('em', { 'class': 'spinning' }, [ _('Collecting data...') ]))
316 ]),
317 has_ip6tables ? E('div', { 'data-tab': 'ip6tables', 'data-tab-title': _('IPv6 Firewall') }, [
318 E('p', {}, E('em', { 'class': 'spinning' }, [ _('Collecting data...') ]))
319 ]) : E([])
320 ])
321 ]);
322
323 if (has_ip6tables)
324 ui.tabs.initTabGroup(view.lastElementChild.childNodes);
325
326 this.pollFirewallLists(has_ip6tables);
327
328 return view;
329 },
330
331 handleSaveApply: null,
332 handleSave: null,
333 handleReset: null
334 });