diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/FirewallController.php b/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/FirewallController.php
index 521d0b2b4..a6df48cd0 100644
--- a/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/FirewallController.php
+++ b/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/FirewallController.php
@@ -92,6 +92,11 @@ class FirewallController extends IndexController
"name" => "interfaces",
"caption" => gettext("interfaces"),
"endpoint" => "/api/diagnostics/firewall/pf_statistcs/interfaces"
+ ],
+ [
+ "name" => "rules",
+ "caption" => gettext("rules"),
+ "endpoint" => "/api/diagnostics/firewall/pf_statistcs/rules"
]
];
$this->view->default_tab = "info";
diff --git a/src/opnsense/mvc/app/views/OPNsense/Diagnostics/treeview.volt b/src/opnsense/mvc/app/views/OPNsense/Diagnostics/treeview.volt
index 744591346..7382f7eb2 100644
--- a/src/opnsense/mvc/app/views/OPNsense/Diagnostics/treeview.volt
+++ b/src/opnsense/mvc/app/views/OPNsense/Diagnostics/treeview.volt
@@ -83,6 +83,8 @@
closedIcon: $(''),
openedIcon: $(''),
onCreateLi: function(node, $li) {
+ let n_title = $li.find('.jqtree-title');
+ n_title.text(n_title.text().replace('>','\>').replace('<','\<'));
if (node.value !== undefined) {
$li.find('.jqtree-element').append(
' : ' + node.value
@@ -101,6 +103,10 @@
$tree.tree('openNode', $tree.tree('getNodeById', key));
}
}
+ //open node on label click
+ $tree.bind('tree.click', function(e) {
+ $tree.tree('toggle', e.node);
+ });
} else {
let curent_state = $tree.tree('getState');
$tree.tree('loadData', dict_to_tree(data));
diff --git a/src/opnsense/scripts/filter/pfstatistcs.py b/src/opnsense/scripts/filter/pfstatistcs.py
index 05feb4d63..00ea17acf 100755
--- a/src/opnsense/scripts/filter/pfstatistcs.py
+++ b/src/opnsense/scripts/filter/pfstatistcs.py
@@ -96,17 +96,42 @@ def pfctl_interfaces():
return result
-def main():
- result = {
- 'info': pfctl_info(),
- 'memory': pfctl_memory(),
- 'timeouts': pfctl_timeouts(),
- 'interfaces': pfctl_interfaces()
+def pfctl_rules():
+ result = dict()
+ headings = {
+ "rules": "filter rules",
+ "nat": "nat rules"
}
+ for key in headings:
+ result[headings[key]] = dict()
+ rule = None
+ for line in subprocess.run(['/sbin/pfctl', '-vvs' + key], capture_output=True, text=True).stdout.split("\n"):
+ sline = line.strip()
+ if len(line) > 0 and line[0] not in ["\t", " "]:
+ rule = sline
+ result[headings[key]][rule] = dict()
+ elif rule is not None and sline.startswith('[') and sline.endswith(']'):
+ items = sline[1:].strip().lower().split(':')
+ for idx, item in enumerate(items[1:],1):
+ opt = 'state_creations' if items[idx-1].find('creations') > -1 else items[idx-1].split()[-1]
+ val = " ".join(item.split()[:-1]).replace('state', '')
+ result[headings[key]][rule][opt] = int(val) if val.isdigit() else val
- if len(sys.argv) > 1 and sys.argv[1] in result:
- return result[sys.argv[1]]
- else:
- return result
+ return result
+
+def main():
+ sections = {
+ 'info': pfctl_info,
+ 'memory': pfctl_memory,
+ 'timeouts': pfctl_timeouts,
+ 'interfaces': pfctl_interfaces,
+ 'rules': pfctl_rules
+ }
+ result = dict()
+ for section in sections:
+ if (len(sys.argv) > 1 and sys.argv[1] == section) or (len(sys.argv) == 1):
+ result[section] = sections[section]()
+
+ return result
print(ujson.dumps(main()))