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