7 var table_names
= [ 'Filter', 'NAT', 'Mangle', 'Raw' ];
11 return L
.resolveDefault(fs
.stat('/usr/sbin/ip6tables'));
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
);
20 tdiv
= E('div', { 'data-table': '%s-%s'.format(is_ipv6
? 'ipv6' : 'ipv4', table
) }, [
25 if (idiv
.firstElementChild
.nodeName
.toLowerCase() === 'p')
26 idiv
.removeChild(idiv
.firstElementChild
);
28 var added
= false, thisIdx
= table_names
.indexOf(table
);
30 idiv
.querySelectorAll('[data-table]').forEach(function(child
) {
31 var childIdx
= table_names
.indexOf(child
.getAttribute('data-table').split(/-/)[1]);
33 if (added
=== false && childIdx
> thisIdx
) {
34 idiv
.insertBefore(tdiv
, child
);
40 idiv
.appendChild(tdiv
);
43 return tdiv
.lastElementChild
;
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
)),
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'));
55 title
= '%s <em>%s</em> <span class="references">(%d %s)</span>'
56 .format(_('Chain'), chain
, references
, _('References'));
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'))
77 tdiv
.appendChild(cdiv
);
80 cdiv
.firstElementChild
.innerHTML
= title
;
83 return cdiv
.lastElementChild
;
86 updateChainSection: function(chaintable
, rows
) {
90 cbi_update_table(chaintable
, rows
, _('No rules in this chain.'));
92 if (rows
.length
=== 0 &&
93 document
.querySelector('[data-hide-empty="true"]'))
94 chaintable
.parentNode
.style
.display
= 'none';
96 chaintable
.parentNode
.style
.display
= '';
98 chaintable
.parentNode
.setAttribute('data-empty', rows
.length
=== 0);
101 parseIptablesDump: function(is_ipv6
, table
, s
) {
102 var current_chain
= null;
103 var current_rules
= [];
104 var seen_chains
= {};
106 var re
= /([^\n]*)\n/g;
109 while ((m
= re
.exec(s
)) != null) {
110 if (m
[1].match(/^Chain (.+) \(policy (\w+) (\d+) packets, (\d+) bytes\)$/)) {
111 var chain
= RegExp
.$1,
113 packets
= +RegExp
.$3,
116 this.updateChainSection(current_chain
, current_rules
);
118 seen_chains
[chain
] = true;
119 current_chain
= this.createChainSection(is_ipv6
, table
, chain
, policy
, packets
, bytes
);
122 else if (m
[1].match(/^Chain (.+) \((\d+) references\)$/)) {
123 var chain
= RegExp
.$1,
124 references
= +RegExp
.$2;
126 this.updateChainSection(current_chain
, current_rules
);
128 seen_chains
[chain
] = true;
129 current_chain
= this.createChainSection(is_ipv6
, table
, chain
, null, null, null, references
);
132 else if (m
[1].match(/^num /)) {
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) {
145 options
= m2
[11] || '-',
148 options
= options
.trim().replace(/(?:^| )\/\* (.+) \*\//,
150 comment
= m2
.replace(/^!fw3(: |$)/, '').trim() || '-';
155 '%.2m'.format(pkts
).nobr(),
156 '%.2mB'.format(bytes
).nobr(),
157 target
? '<span class="target">%s</span>'.format(target
) : '-',
159 (indev
!== '*') ? '<span class="ifacebadge">%s</span>'.format(indev
) : '*',
160 (outdev
!== '*') ? '<span class="ifacebadge">%s</span>'.format(outdev
) : '*',
168 chain_refs
[target
] = chain_refs
[target
] || [];
169 chain_refs
[target
].push([ current_chain
, num
]);
174 this.updateChainSection(current_chain
, current_rules
);
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
);
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
);
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') ]));
195 refs
.forEach(L
.bind(function(ref
) {
196 var chain
= ref
[0].parentNode
.getAttribute('data-chain'),
199 rspan
.lastElementChild
.lastElementChild
.appendChild(E('li', {}, [
204 'click': this.handleJumpTarget
206 ', %s #%d'.format(_('Rule'), num
)
214 pollFirewallLists: function(has_ip6tables
) {
215 var cmds
= [ '/usr/sbin/iptables' ];
218 cmds
.push('/usr/sbin/ip6tables');
220 poll
.add(L
.bind(function() {
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
]))));
231 return Promise
.all(tasks
);
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
));
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');
249 var rule
= elem
.nextElementSibling
.childNodes
[num
];
251 rule
.classList
.remove('flash');
252 void rule
.offsetWidth
;
253 rule
.classList
.add('flash');
259 handleHideEmpty: function(ev
) {
260 var btn
= ev
.currentTarget
,
261 hide
= (btn
.getAttribute('data-hide-empty') === 'false');
263 btn
.setAttribute('data-hide-empty', hide
);
264 btn
.firstChild
.data
= hide
? _('Show empty chains') : _('Hide empty chains');
267 document
.querySelectorAll('[data-chain][data-empty="true"]')
268 .forEach(function(chaintable
) {
269 chaintable
.style
.display
= hide
? 'none' : '';
273 handleCounterReset: function(has_ip6tables
, ev
) {
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
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
))) });
287 render: function(has_ip6tables
) {
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% }'
297 E('h2', {}, [ _('Firewall Status') ]),
298 E('div', { 'class': 'right', 'style': 'margin-bottom:-1.5em' }, [
300 'class': 'cbi-button',
301 'data-hide-empty': false,
302 'click': ui
.createHandlerFn(this, 'handleHideEmpty')
303 }, [ _('Hide empty chains') ]),
306 'class': 'cbi-button',
307 'click': ui
.createHandlerFn(this, 'handleCounterReset', has_ip6tables
)
308 }, [ _('Reset Counters') ]),
311 'class': 'cbi-button',
312 'click': ui
.createHandlerFn(this, 'handleRestart')
313 }, [ _('Restart Firewall') ])
316 E('div', { 'data-tab': 'iptables', 'data-tab-title': has_ip6tables
? _('IPv4 Firewall') : null }, [
317 E('p', {}, E('em', { 'class': 'spinning' }, [ _('Collecting data...') ]))
319 has_ip6tables
? E('div', { 'data-tab': 'ip6tables', 'data-tab-title': _('IPv6 Firewall') }, [
320 E('p', {}, E('em', { 'class': 'spinning' }, [ _('Collecting data...') ]))
326 ui
.tabs
.initTabGroup(view
.lastElementChild
.childNodes
);
328 this.pollFirewallLists(has_ip6tables
);
333 handleSaveApply
: null,