dd58670694fd11d3a92a53acc520b3844a94b47f
7 var table_names
= [ 'Filter', 'NAT', 'Mangle', 'Raw' ],
8 raw_style
= 'font-family:monospace;font-size:smaller;text-align:right';
12 return L
.resolveDefault(fs
.stat('/usr/sbin/ip6tables'));
15 createTableSection: function(is_ipv6
, table
) {
16 var idiv
= document
.querySelector('div[data-tab="%s"]'.format(is_ipv6
? 'ip6tables' : 'iptables')),
17 tdiv
= idiv
.querySelector('[data-table="%s-%s"]'.format(is_ipv6
? 'ipv6' : 'ipv4', table
)),
18 title
= '%s: %s'.format(_('Table'), table
);
21 tdiv
= E('div', { 'data-table': '%s-%s'.format(is_ipv6
? 'ipv6' : 'ipv4', table
) }, [
26 if (idiv
.firstElementChild
.nodeName
.toLowerCase() === 'p')
27 idiv
.removeChild(idiv
.firstElementChild
);
29 var added
= false, thisIdx
= table_names
.indexOf(table
);
31 idiv
.querySelectorAll('[data-table]').forEach(function(child
) {
32 var childIdx
= table_names
.indexOf(child
.getAttribute('data-table').split(/-/)[1]);
34 if (added
=== false && childIdx
> thisIdx
) {
35 idiv
.insertBefore(tdiv
, child
);
41 idiv
.appendChild(tdiv
);
44 return tdiv
.lastElementChild
;
47 createChainSection: function(is_ipv6
, table
, chain
, policy
, packets
, bytes
, references
) {
48 var tdiv
= this.createTableSection(is_ipv6
, table
),
49 cdiv
= tdiv
.querySelector('[data-chain="%s"]'.format(chain
)),
53 title
= '%s <em>%s</em> <span>(%s: <em>%s</em>, %d %s, %.2mB %s)</span>'
54 .format(_('Chain'), chain
, _('Policy'), policy
, packets
, _('Packets'), bytes
, _('Traffic'));
56 title
= '%s <em>%s</em> <span class="references">(%d %s)</span>'
57 .format(_('Chain'), chain
, references
, _('References'));
60 cdiv
= E('div', { 'data-chain': chain
}, [
61 E('h4', { 'id': 'rule_%s-%s_%s'.format(is_ipv6
? 'ipv6' : 'ipv4', table
.toLowerCase(), chain
) }, title
),
62 E('table', { 'class': 'table' }, [
63 E('tr', { 'class': 'tr table-titles' }, [
64 E('th', { 'class': 'th' }, _('Pkts.')),
65 E('th', { 'class': 'th' }, _('Traffic')),
66 E('th', { 'class': 'th' }, _('Target')),
67 E('th', { 'class': 'th' }, _('Prot.')),
68 E('th', { 'class': 'th' }, _('In')),
69 E('th', { 'class': 'th' }, _('Out')),
70 E('th', { 'class': 'th' }, _('Source')),
71 E('th', { 'class': 'th' }, _('Destination')),
72 E('th', { 'class': 'th' }, _('Options')),
73 E('th', { 'class': 'th' }, _('Comment'))
78 tdiv
.appendChild(cdiv
);
81 cdiv
.firstElementChild
.innerHTML
= title
;
84 return cdiv
.lastElementChild
;
87 updateChainSection: function(chaintable
, rows
) {
91 cbi_update_table(chaintable
, rows
, _('No rules in this chain.'));
93 if (rows
.length
=== 0 &&
94 document
.querySelector('[data-hide-empty="true"]'))
95 chaintable
.parentNode
.style
.display
= 'none';
97 chaintable
.parentNode
.style
.display
= '';
99 chaintable
.parentNode
.setAttribute('data-empty', rows
.length
=== 0);
102 parseIptablesDump: function(is_ipv6
, table
, s
) {
103 var current_chain
= null;
104 var current_rules
= [];
105 var seen_chains
= {};
107 var re
= /([^\n]*)\n/g;
109 var raw
= document
.querySelector('[data-raw-counters="true"]');
111 while ((m
= re
.exec(s
)) != null) {
112 if (m
[1].match(/^Chain (.+) \(policy (\w+) (\d+) packets, (\d+) bytes\)$/)) {
113 var chain
= RegExp
.$1,
115 packets
= +RegExp
.$3,
118 this.updateChainSection(current_chain
, current_rules
);
120 seen_chains
[chain
] = true;
121 current_chain
= this.createChainSection(is_ipv6
, table
, chain
, policy
, packets
, bytes
);
124 else if (m
[1].match(/^Chain (.+) \((\d+) references\)$/)) {
125 var chain
= RegExp
.$1,
126 references
= +RegExp
.$2;
128 this.updateChainSection(current_chain
, current_rules
);
130 seen_chains
[chain
] = true;
131 current_chain
= this.createChainSection(is_ipv6
, table
, chain
, null, null, null, references
);
134 else if (m
[1].match(/^num /)) {
137 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) {
147 options
= m2
[11] || '-',
150 options
= options
.trim().replace(/(?:^| )\/\* (.+) \*\//,
152 comment
= m2
.replace(/^!fw3(: |$)/, '').trim() || '-';
159 'style': raw
? raw_style
: null,
160 'data-format': '%.2m',
162 }, (raw
? '%d' : '%.2m').format(pkts
)),
165 'style': raw
? raw_style
: null,
166 'data-format': '%.2mB',
168 }, (raw
? '%d' : '%.2mB').format(bytes
)),
169 target
? '<span class="target">%s</span>'.format(target
) : '-',
171 (indev
!== '*') ? '<span class="ifacebadge nowrap">%s</span>'.format(indev
) : '*',
172 (outdev
!== '*') ? '<span class="ifacebadge nowrap">%s</span>'.format(outdev
) : '*',
180 chain_refs
[target
] = chain_refs
[target
] || [];
181 chain_refs
[target
].push([ current_chain
, num
]);
186 this.updateChainSection(current_chain
, current_rules
);
188 document
.querySelectorAll('[data-table="%s-%s"] [data-chain]'.format(is_ipv6
? 'ipv6' : 'ipv4', table
)).forEach(L
.bind(function(cdiv
) {
189 if (!seen_chains
[cdiv
.getAttribute('data-chain')]) {
190 cdiv
.parentNode
.removeChild(cdiv
);
194 cdiv
.querySelectorAll('.target').forEach(L
.bind(function(tspan
) {
195 if (seen_chains
[tspan
.textContent
]) {
196 tspan
.classList
.add('jump');
197 tspan
.addEventListener('click', this.handleJumpTarget
);
201 cdiv
.querySelectorAll('.references').forEach(L
.bind(function(rspan
) {
202 var refs
= chain_refs
[cdiv
.getAttribute('data-chain')];
203 if (refs
&& refs
.length
) {
204 rspan
.classList
.add('cbi-tooltip-container');
205 rspan
.appendChild(E('small', { 'class': 'cbi-tooltip ifacebadge', 'style': 'top:1em; left:auto' }, [ E('ul') ]));
207 refs
.forEach(L
.bind(function(ref
) {
208 var chain
= ref
[0].parentNode
.getAttribute('data-chain'),
211 rspan
.lastElementChild
.lastElementChild
.appendChild(E('li', {}, [
216 'click': this.handleJumpTarget
218 ', %s #%d'.format(_('Rule'), num
)
226 pollFirewallLists: function(has_ip6tables
) {
227 var cmds
= [ '/usr/sbin/iptables' ];
230 cmds
.push('/usr/sbin/ip6tables');
232 poll
.add(L
.bind(function() {
235 for (var i
= 0; i
< cmds
.length
; i
++) {
236 for (var j
= 0; j
< table_names
.length
; j
++) {
237 tasks
.push(L
.resolveDefault(
238 fs
.exec_direct(cmds
[i
], [ '--line-numbers', '-w', '-nvxL', '-t', table_names
[j
].toLowerCase() ])
239 .then(this.parseIptablesDump
.bind(this, i
> 0, table_names
[j
]))));
243 return Promise
.all(tasks
);
247 handleJumpTarget: function(ev
) {
248 var link
= ev
.target
,
249 table
= findParent(link
, '[data-table]').getAttribute('data-table'),
250 chain
= link
.textContent
,
251 num
= +link
.getAttribute('data-num'),
252 elem
= document
.getElementById('rule_%s_%s'.format(table
.toLowerCase(), chain
));
255 (document
.documentElement
|| document
.body
.parentNode
|| document
.body
).scrollTop
= elem
.offsetTop
- 40;
256 elem
.classList
.remove('flash');
257 void elem
.offsetWidth
;
258 elem
.classList
.add('flash');
261 var rule
= elem
.nextElementSibling
.childNodes
[num
];
263 rule
.classList
.remove('flash');
264 void rule
.offsetWidth
;
265 rule
.classList
.add('flash');
271 handleRawCounters: function(ev
) {
272 var btn
= ev
.currentTarget
,
273 raw
= (btn
.getAttribute('data-raw-counters') === 'false');
275 btn
.setAttribute('data-raw-counters', raw
);
276 btn
.firstChild
.data
= raw
? _('Human-readable counters') : _('Show raw counters');
279 document
.querySelectorAll('[data-value]')
280 .forEach(function(div
) {
281 var fmt
= raw
? '%d' : div
.getAttribute('data-format');
283 div
.style
= raw
? raw_style
: '';
284 div
.innerText
= fmt
.format(div
.getAttribute('data-value'));
288 handleHideEmpty: function(ev
) {
289 var btn
= ev
.currentTarget
,
290 hide
= (btn
.getAttribute('data-hide-empty') === 'false');
292 btn
.setAttribute('data-hide-empty', hide
);
293 btn
.firstChild
.data
= hide
? _('Show empty chains') : _('Hide empty chains');
296 document
.querySelectorAll('[data-chain][data-empty="true"]')
297 .forEach(function(chaintable
) {
298 chaintable
.style
.display
= hide
? 'none' : '';
302 handleCounterReset: function(has_ip6tables
, ev
) {
304 fs
.exec('/usr/sbin/iptables', [ '-Z' ])
305 .catch(function(err
) { ui
.addNotification(null, E('p', {}, _('Unable to reset iptables counters: %s').format(err
.message
))) }),
306 has_ip6tables
? fs
.exec('/usr/sbin/ip6tables', [ '-Z' ])
307 .catch(function(err
) { ui
.addNotification(null, E('p', {}, _('Unable to reset ip6tables counters: %s').format(err
.message
))) }) : null
311 handleRestart: function(ev
) {
312 return fs
.exec_direct('/etc/init.d/firewall', [ 'restart' ])
313 .catch(function(err
) { ui
.addNotification(null, E('p', {}, _('Unable to restart firewall: %s').format(err
.message
))) });
316 render: function(has_ip6tables
) {
318 E('style', { 'type': 'text/css' }, [
319 '.cbi-tooltip-container, span.jump { border-bottom:1px dotted #00f;cursor:pointer }',
320 'ul { list-style:none }',
321 '.references { position:relative }',
322 '.references .cbi-tooltip { left:0!important;top:1.5em!important }',
323 'h4>span { font-size:90% }'
326 E('h2', {}, [ _('Firewall Status') ]),
327 E('div', { 'class': 'right', 'style': 'margin-bottom:-1.5em' }, [
329 'class': 'cbi-button',
330 'data-hide-empty': false,
331 'click': ui
.createHandlerFn(this, 'handleHideEmpty')
332 }, [ _('Hide empty chains') ]),
335 'data-raw-counters': false,
336 'click': ui
.createHandlerFn(this, 'handleRawCounters')
337 }, [ _('Show raw counters') ]),
340 'class': 'cbi-button',
341 'click': ui
.createHandlerFn(this, 'handleCounterReset', has_ip6tables
)
342 }, [ _('Reset Counters') ]),
345 'class': 'cbi-button',
346 'click': ui
.createHandlerFn(this, 'handleRestart')
347 }, [ _('Restart Firewall') ])
350 E('div', { 'data-tab': 'iptables', 'data-tab-title': has_ip6tables
? _('IPv4 Firewall') : null }, [
351 E('p', {}, E('em', { 'class': 'spinning' }, [ _('Collecting data...') ]))
353 has_ip6tables
? E('div', { 'data-tab': 'ip6tables', 'data-tab-title': _('IPv6 Firewall') }, [
354 E('p', {}, E('em', { 'class': 'spinning' }, [ _('Collecting data...') ]))
360 ui
.tabs
.initTabGroup(view
.lastElementChild
.childNodes
);
362 this.pollFirewallLists(has_ip6tables
);
367 handleSaveApply
: null,