5 var table_names
= [ 'Filter', 'NAT', 'Mangle', 'Raw' ];
9 return L
.resolveDefault(fs
.stat('/usr/sbin/ip6tables'));
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
);
18 tdiv
= E('div', { 'data-table': '%s-%s'.format(is_ipv6
? 'ipv6' : 'ipv4', table
) }, [
23 if (idiv
.firstElementChild
.nodeName
.toLowerCase() === 'p')
24 idiv
.removeChild(idiv
.firstElementChild
);
26 var added
= false, thisIdx
= table_names
.indexOf(table
);
28 idiv
.querySelectorAll('[data-table]').forEach(function(child
) {
29 var childIdx
= table_names
.indexOf(child
.getAttribute('data-table').split(/-/)[1]);
31 if (added
=== false && childIdx
> thisIdx
) {
32 idiv
.insertBefore(tdiv
, child
);
38 idiv
.appendChild(tdiv
);
41 return tdiv
.lastElementChild
;
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
)),
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'));
53 title
= '%s <em>%s</em> <span class="references">(%d %s)</span>'
54 .format(_('Chain'), chain
, references
, _('References'));
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'))
75 tdiv
.appendChild(cdiv
);
78 cdiv
.firstElementChild
.innerHTML
= title
;
81 return cdiv
.lastElementChild
;
84 updateChainSection: function(chaintable
, rows
) {
88 cbi_update_table(chaintable
, rows
, _('No rules in this chain.'));
90 if (rows
.length
=== 0 &&
91 document
.querySelector('[data-hide-empty="true"]'))
92 chaintable
.parentNode
.style
.display
= 'none';
94 chaintable
.parentNode
.style
.display
= '';
96 chaintable
.parentNode
.setAttribute('data-empty', rows
.length
=== 0);
99 parseIptablesDump: function(is_ipv6
, table
, s
) {
100 var current_chain
= null;
101 var current_rules
= [];
102 var seen_chains
= {};
104 var re
= /([^\n]*)\n/g;
107 while ((m
= re
.exec(s
)) != null) {
108 if (m
[1].match(/^Chain (.+) \(policy (\w+) (\d+) packets, (\d+) bytes\)$/)) {
109 var chain
= RegExp
.$1,
111 packets
= +RegExp
.$3,
114 this.updateChainSection(current_chain
, current_rules
);
116 seen_chains
[chain
] = true;
117 current_chain
= this.createChainSection(is_ipv6
, table
, chain
, policy
, packets
, bytes
);
120 else if (m
[1].match(/^Chain (.+) \((\d+) references\)$/)) {
121 var chain
= RegExp
.$1,
122 references
= +RegExp
.$2;
124 this.updateChainSection(current_chain
, current_rules
);
126 seen_chains
[chain
] = true;
127 current_chain
= this.createChainSection(is_ipv6
, table
, chain
, null, null, null, references
);
130 else if (m
[1].match(/^num /)) {
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) {
143 options
= m2
[11] || '-',
146 options
= options
.trim().replace(/(?:^| )\/\* (.+) \*\//,
148 comment
= m2
.replace(/^!fw3(: |$)/, '').trim() || '-';
153 '%.2m'.format(pkts
).nobr(),
154 '%.2mB'.format(bytes
).nobr(),
155 target
? '<span class="target">%s</span>'.format(target
) : '-',
157 (indev
!== '*') ? '<span class="ifacebadge">%s</span>'.format(indev
) : '*',
158 (outdev
!== '*') ? '<span class="ifacebadge">%s</span>'.format(outdev
) : '*',
166 chain_refs
[target
] = chain_refs
[target
] || [];
167 chain_refs
[target
].push([ current_chain
, num
]);
172 this.updateChainSection(current_chain
, current_rules
);
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
);
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
);
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') ]));
193 refs
.forEach(L
.bind(function(ref
) {
194 var chain
= ref
[0].parentNode
.getAttribute('data-chain'),
197 rspan
.lastElementChild
.lastElementChild
.appendChild(E('li', {}, [
202 'click': this.handleJumpTarget
204 ', %s #%d'.format(_('Rule'), num
)
212 pollFirewallLists: function(has_ip6tables
) {
213 var cmds
= [ '/usr/sbin/iptables' ];
216 cmds
.push('/usr/sbin/ip6tables');
218 L
.Poll
.add(L
.bind(function() {
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
]))));
229 return Promise
.all(tasks
);
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
));
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');
247 var rule
= elem
.nextElementSibling
.childNodes
[num
];
249 rule
.classList
.remove('flash');
250 void rule
.offsetWidth
;
251 rule
.classList
.add('flash');
257 handleHideEmpty: function(ev
) {
258 var btn
= ev
.currentTarget
,
259 hide
= (btn
.getAttribute('data-hide-empty') === 'false');
261 btn
.setAttribute('data-hide-empty', hide
);
262 btn
.firstChild
.data
= hide
? _('Show empty chains') : _('Hide empty chains');
265 document
.querySelectorAll('[data-chain][data-empty="true"]')
266 .forEach(function(chaintable
) {
267 chaintable
.style
.display
= hide
? 'none' : '';
271 handleCounterReset: function(has_ip6tables
, ev
) {
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
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
))) });
285 render: function(has_ip6tables
) {
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% }'
295 E('h2', {}, [ _('Firewall Status') ]),
296 E('div', { 'class': 'right', 'style': 'margin-bottom:-1.5em' }, [
298 'class': 'cbi-button',
299 'data-hide-empty': false,
300 'click': ui
.createHandlerFn(this, 'handleHideEmpty')
301 }, [ _('Hide empty chains') ]),
304 'class': 'cbi-button',
305 'click': ui
.createHandlerFn(this, 'handleCounterReset', has_ip6tables
)
306 }, [ _('Reset Counters') ]),
309 'class': 'cbi-button',
310 'click': ui
.createHandlerFn(this, 'handleRestart')
311 }, [ _('Restart Firewall') ])
314 E('div', { 'data-tab': 'iptables', 'data-tab-title': has_ip6tables
? _('IPv4 Firewall') : null }, [
315 E('p', {}, E('em', { 'class': 'spinning' }, [ _('Collecting data...') ]))
317 has_ip6tables
? E('div', { 'data-tab': 'ip6tables', 'data-tab-title': _('IPv6 Firewall') }, [
318 E('p', {}, E('em', { 'class': 'spinning' }, [ _('Collecting data...') ]))
324 ui
.tabs
.initTabGroup(view
.lastElementChild
.childNodes
);
326 this.pollFirewallLists(has_ip6tables
);
331 handleSaveApply
: null,