mirror of
https://github.com/opnsense/core.git
synced 2026-05-28 04:34:51 -04:00
firewall: group filter rules by type and category
Always group rules by their priority/type in the filter grid and reuse the same rule type metadata for both bucket labels and icons. When tree view is enabled, categorized non-automatic rules are grouped one level deeper by category, while automatic and uncategorized rules remain directly below their rule type bucket. This keeps the default view structured without relying on a mixed flat/tree array and makes the tree toggle an additive category grouping layer.
This commit is contained in:
parent
1c643da59e
commit
07f90b72ba
1 changed files with 96 additions and 55 deletions
|
|
@ -67,61 +67,113 @@
|
|||
// read interface from URL hash once, for the first grid load
|
||||
let pendingUrlInterface = getUrlHash('interface') || null;
|
||||
|
||||
const ruleTypeMap = {
|
||||
'0': { label: "{{ lang._('Automatic rules') }}", icon: "fa-magic", tooltip: "{{ lang._('Automatic Rule') }}", color: "text-secondary" },
|
||||
'1': { label: "{{ lang._('Automatic rules') }}", icon: "fa-magic", tooltip: "{{ lang._('Automatic Rule') }}", color: "text-secondary" },
|
||||
'2': { label: "{{ lang._('Floating rules') }}", icon: "fa-layer-group", tooltip: "{{ lang._('Floating Rule') }}", color: "text-primary" },
|
||||
'3': { label: "{{ lang._('Group rules') }}", icon: "fa-sitemap", tooltip: "{{ lang._('Group Rule') }}", color: "text-warning" },
|
||||
'4': { label: "{{ lang._('Interface rules') }}", icon: "fa-ethernet", tooltip: "{{ lang._('Interface Rule') }}", color: "text-info" },
|
||||
'5': { label: "{{ lang._('Automatic rules') }}", icon: "fa-magic", tooltip: "{{ lang._('Automatic Rule') }}", color: "text-secondary" },
|
||||
};
|
||||
|
||||
const getRuleTypeDigit = function(row) {
|
||||
const sortOrder = row.sort_order ? row.sort_order.toString() : "";
|
||||
return sortOrder.charAt(0);
|
||||
};
|
||||
|
||||
const getRuleType = function(row) {
|
||||
return ruleTypeMap[getRuleTypeDigit(row)] || null;
|
||||
};
|
||||
|
||||
// Lives outside the grid, so the logic of the response handler can be changed after grid initialization
|
||||
function dynamicResponseHandler(response) {
|
||||
const makeBucket = function(row, label, uuid) {
|
||||
const bucket = {
|
||||
const getCategoryLabel = function(row) {
|
||||
return row["%categories"] || row.categories || "";
|
||||
};
|
||||
|
||||
const makeBucket = function(label, uuid, categoryColors) {
|
||||
return {
|
||||
// ensure uuid is as unique as possible for persistence handling
|
||||
uuid : uuid,
|
||||
isGroup : true,
|
||||
_label : label, // internal
|
||||
categories : label,
|
||||
/*
|
||||
* Bucket rows reuse the category formatter.
|
||||
* For category buckets, this copies the first child's category metadata
|
||||
* so the bucket can render the same category icon/color as its rules.
|
||||
* For rule type buckets, a synthetic categoryColors entry is supplied.
|
||||
*/
|
||||
category_colors: categoryColors,
|
||||
children : []
|
||||
};
|
||||
};
|
||||
|
||||
// copy the category info from the first child to use as parent
|
||||
bucket.categories = label;
|
||||
bucket.category_colors = row.category_colors || [];
|
||||
const createBucket = function(parent, label, uuid, categoryColors) {
|
||||
let bucket = parent.children.find(child => child.isGroup && child._label === label);
|
||||
|
||||
if (!bucket) {
|
||||
bucket = makeBucket(label, uuid, categoryColors);
|
||||
parent.children.push(bucket);
|
||||
}
|
||||
|
||||
return bucket;
|
||||
};
|
||||
|
||||
if (!treeViewEnabled) {
|
||||
// automatic rules are always collected in a single bucket
|
||||
// non-automatic rows are pushed without children, creating a mixed array of bucket and regular rows
|
||||
const rows = [];
|
||||
let automatic = null;
|
||||
response.rows.forEach(row => {
|
||||
if (row.is_automatic === true) {
|
||||
if (automatic === null) {
|
||||
// readable label used for grouping
|
||||
const label = (row["%categories"] || row.categories || "");
|
||||
automatic = makeBucket(row, label, "automaticrules");
|
||||
rows.push(automatic);
|
||||
}
|
||||
automatic.children.push(row);
|
||||
} else {
|
||||
rows.push(row);
|
||||
}
|
||||
});
|
||||
const root = { children: [] };
|
||||
|
||||
return Object.assign({}, response, { rows: rows });
|
||||
} else {
|
||||
// tree view groups all rows into category buckets instead of using mixed top-level rows
|
||||
const buckets = [];
|
||||
let current = null;
|
||||
response.rows.forEach(row => {
|
||||
// readable label used for grouping
|
||||
const label = (row["%categories"] || row.categories || "");
|
||||
// start a new bucket whenever the label changes
|
||||
if (!current || current._label !== label) {
|
||||
current = makeBucket(row, label, `${String(row.uuid).replace(/-/g, '')}`);
|
||||
buckets.push(current);
|
||||
}
|
||||
current.children.push(row);
|
||||
});
|
||||
response.rows.forEach(row => {
|
||||
const ruleType = getRuleType(row);
|
||||
const ruleTypeDigit = getRuleTypeDigit(row) || "other";
|
||||
const ruleTypeLabel = ruleType.label || "{{ lang._('Other rules') }}";
|
||||
const categoryLabel = getCategoryLabel(row);
|
||||
|
||||
return Object.assign({}, response, { rows: buckets });
|
||||
}
|
||||
/*
|
||||
* The first tree level is the default view, and always based on the rule priority/type.
|
||||
*
|
||||
* Automatic rules
|
||||
* rule
|
||||
* Interface rules
|
||||
* rule
|
||||
*/
|
||||
const ruleTypeBucket = createBucket(
|
||||
root,
|
||||
ruleTypeLabel,
|
||||
`ruletype${ruleTypeDigit}`,
|
||||
[{ name: ruleTypeLabel }]
|
||||
);
|
||||
|
||||
if (treeViewEnabled && row.is_automatic !== true && categoryLabel !== "") {
|
||||
/*
|
||||
* When tree view is enabled, categorized non-automatic rules are grouped
|
||||
* one level deeper by category below their rule priority/type bucket.
|
||||
*
|
||||
* Automatic rules and uncategorized rules stay directly below their rule
|
||||
* priority/type bucket to avoid redundant or low-value nesting.
|
||||
*
|
||||
* Automatic rules
|
||||
* rule
|
||||
* Interface rules
|
||||
* rule
|
||||
* Web (Category)
|
||||
* rule
|
||||
* Mail (Category)
|
||||
* rule
|
||||
*/
|
||||
const categoryBucket = createBucket(
|
||||
ruleTypeBucket,
|
||||
categoryLabel,
|
||||
`ruletype${ruleTypeDigit}category${String(categoryLabel).replace(/[^a-z0-9]/gi, '')}`,
|
||||
row.category_colors || []
|
||||
);
|
||||
|
||||
categoryBucket.children.push(row);
|
||||
} else {
|
||||
ruleTypeBucket.children.push(row);
|
||||
}
|
||||
});
|
||||
|
||||
return Object.assign({}, response, { rows: root.children });
|
||||
}
|
||||
|
||||
$('#download_rules').click(function(e){
|
||||
|
|
@ -436,22 +488,11 @@
|
|||
let result = "";
|
||||
|
||||
// Rule Type Icons (Determined by first digit of sort_order)
|
||||
const ruleTypeIcons = {
|
||||
'0': { icon: "fa-magic", tooltip: "{{ lang._('Automatic Rule') }}", color: "text-secondary" },
|
||||
'1': { icon: "fa-magic", tooltip: "{{ lang._('Automatic Rule') }}", color: "text-secondary" },
|
||||
'2': { icon: "fa-layer-group", tooltip: "{{ lang._('Floating Rule') }}", color: "text-primary" },
|
||||
'3': { icon: "fa-sitemap", tooltip: "{{ lang._('Group Rule') }}", color: "text-warning" },
|
||||
'4': { icon: "fa-ethernet", tooltip: "{{ lang._('Interface Rule') }}", color: "text-info" },
|
||||
'5': { icon: "fa-magic", tooltip: "{{ lang._('Automatic Rule') }}", color: "text-secondary" },
|
||||
};
|
||||
const ruleType = getRuleType(row);
|
||||
|
||||
const sortOrder = row.sort_order ? row.sort_order.toString() : "";
|
||||
if (sortOrder.length > 0) {
|
||||
const typeDigit = sortOrder.charAt(0);
|
||||
if (ruleTypeIcons[typeDigit]) {
|
||||
result += `<i class="fa ${ruleTypeIcons[typeDigit].icon} fa-fw ${ruleTypeIcons[typeDigit].color}"
|
||||
data-toggle="tooltip" title="${ruleTypeIcons[typeDigit].tooltip}"></i> `;
|
||||
}
|
||||
if (ruleType) {
|
||||
result += `<i class="fa ${ruleType.icon} fa-fw ${ruleType.color}"
|
||||
data-toggle="tooltip" title="${ruleType.tooltip}"></i> `;
|
||||
}
|
||||
|
||||
// Action
|
||||
|
|
|
|||
Loading…
Reference in a new issue