|
@@ -3630,8 +3630,12 @@ function switchFWSubTab(tab) {
|
|
|
else if (tab === 'connections') addBtn.innerHTML = '<span>Refresh Flows</span>';
|
|
else if (tab === 'connections') addBtn.innerHTML = '<span>Refresh Flows</span>';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (tab === 'geo') loadGeoIP();
|
|
|
|
|
- if (tab === 'connections') loadConnections();
|
|
|
|
|
|
|
+ if (tab === 'filter') renderFilterRules();
|
|
|
|
|
+ else if (tab === 'nat') renderNATRules();
|
|
|
|
|
+ else if (tab === 'mangle') renderMangleRules();
|
|
|
|
|
+ else if (tab === 'addrlist') renderAddressLists();
|
|
|
|
|
+ else if (tab === 'geo') loadGeoIP();
|
|
|
|
|
+ else if (tab === 'connections') loadConnections();
|
|
|
setTimeout(initTableSorting, 50);
|
|
setTimeout(initTableSorting, 50);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -3681,22 +3685,50 @@ async function loadFirewall(populateDropdowns = true) {
|
|
|
const sigMangle = (state.mangleRules || []).map(r => `${r.id}:${r.chain}:${r.action}:${r.disabled}`).join(';');
|
|
const sigMangle = (state.mangleRules || []).map(r => `${r.id}:${r.chain}:${r.action}:${r.disabled}`).join(';');
|
|
|
const sigAddr = (state.addressLists || []).map(a => `${a.id}:${a.name}:${a.address}:${a.disabled}`).join(';');
|
|
const sigAddr = (state.addressLists || []).map(a => `${a.id}:${a.name}:${a.address}:${a.disabled}`).join(';');
|
|
|
|
|
|
|
|
- const tbodyFilter = document.getElementById('filter-rules-table-body');
|
|
|
|
|
- if (tbodyFilter && (populateDropdowns || tbodyFilter.dataset.sig !== sigFilter)) {
|
|
|
|
|
- tbodyFilter.dataset.sig = sigFilter;
|
|
|
|
|
- renderFilterRules();
|
|
|
|
|
|
|
+ const tbodyFilter = document.getElementById('filter-table-body');
|
|
|
|
|
+ if (tbodyFilter) {
|
|
|
|
|
+ if (populateDropdowns || tbodyFilter.dataset.sig !== sigFilter) {
|
|
|
|
|
+ tbodyFilter.dataset.sig = sigFilter;
|
|
|
|
|
+ renderFilterRules();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // In-place live stats update (ZERO flicker)
|
|
|
|
|
+ (state.filterRules || []).forEach(r => {
|
|
|
|
|
+ const p = document.getElementById(`filter-pkts-${r.id}`);
|
|
|
|
|
+ if (p) p.innerText = `${(r.packets || 0).toLocaleString()}p`;
|
|
|
|
|
+ const b = document.getElementById(`filter-bytes-${r.id}`);
|
|
|
|
|
+ if (b) b.innerText = formatTraffic(r.bytes || 0).replace('/s', '');
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- const tbodyNat = document.getElementById('nat-rules-table-body');
|
|
|
|
|
- if (tbodyNat && (populateDropdowns || tbodyNat.dataset.sig !== sigNat)) {
|
|
|
|
|
- tbodyNat.dataset.sig = sigNat;
|
|
|
|
|
- renderNATRules();
|
|
|
|
|
|
|
+ const tbodyNat = document.getElementById('nat-table-body');
|
|
|
|
|
+ if (tbodyNat) {
|
|
|
|
|
+ if (populateDropdowns || tbodyNat.dataset.sig !== sigNat) {
|
|
|
|
|
+ tbodyNat.dataset.sig = sigNat;
|
|
|
|
|
+ renderNATRules();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ (state.natRules || []).forEach(r => {
|
|
|
|
|
+ const p = document.getElementById(`nat-pkts-${r.id}`);
|
|
|
|
|
+ if (p) p.innerText = `${(r.packets || 0).toLocaleString()}p`;
|
|
|
|
|
+ const b = document.getElementById(`nat-bytes-${r.id}`);
|
|
|
|
|
+ if (b) b.innerText = formatTraffic(r.bytes || 0).replace('/s', '');
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- const tbodyMangle = document.getElementById('mangle-rules-table-body');
|
|
|
|
|
- if (tbodyMangle && (populateDropdowns || tbodyMangle.dataset.sig !== sigMangle)) {
|
|
|
|
|
- tbodyMangle.dataset.sig = sigMangle;
|
|
|
|
|
- renderMangleRules();
|
|
|
|
|
|
|
+ const tbodyMangle = document.getElementById('mangle-table-body');
|
|
|
|
|
+ if (tbodyMangle) {
|
|
|
|
|
+ if (populateDropdowns || tbodyMangle.dataset.sig !== sigMangle) {
|
|
|
|
|
+ tbodyMangle.dataset.sig = sigMangle;
|
|
|
|
|
+ renderMangleRules();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ (state.mangleRules || []).forEach(r => {
|
|
|
|
|
+ const p = document.getElementById(`mangle-pkts-${r.id}`);
|
|
|
|
|
+ if (p) p.innerText = `${(r.packets || 0).toLocaleString()}p`;
|
|
|
|
|
+ const b = document.getElementById(`mangle-bytes-${r.id}`);
|
|
|
|
|
+ if (b) b.innerText = formatTraffic(r.bytes || 0).replace('/s', '');
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- const tbodyAddr = document.getElementById('addr-list-table-body');
|
|
|
|
|
|
|
+ const tbodyAddr = document.getElementById('addrlist-table-body');
|
|
|
if (tbodyAddr && (populateDropdowns || tbodyAddr.dataset.sig !== sigAddr)) {
|
|
if (tbodyAddr && (populateDropdowns || tbodyAddr.dataset.sig !== sigAddr)) {
|
|
|
tbodyAddr.dataset.sig = sigAddr;
|
|
tbodyAddr.dataset.sig = sigAddr;
|
|
|
renderAddressLists();
|
|
renderAddressLists();
|
|
@@ -3756,8 +3788,8 @@ function renderFilterRules() {
|
|
|
<td class="py-2.5 px-3 text-emerald-400">${ifaces || '-'}</td>
|
|
<td class="py-2.5 px-3 text-emerald-400">${ifaces || '-'}</td>
|
|
|
<td class="py-2.5 px-3">${renderActionBadge(r.action)}${actionExtra}</td>
|
|
<td class="py-2.5 px-3">${renderActionBadge(r.action)}${actionExtra}</td>
|
|
|
<td class="py-2.5 px-3 text-[11px] text-slate-400">
|
|
<td class="py-2.5 px-3 text-[11px] text-slate-400">
|
|
|
- <span class="text-slate-300">${(r.packets || 0).toLocaleString()}p</span> /
|
|
|
|
|
- <span class="text-slate-500">${formatTraffic(r.bytes || 0).replace('/s', '')}</span>
|
|
|
|
|
|
|
+ <span class="text-slate-300" id="filter-pkts-${r.id}">${(r.packets || 0).toLocaleString()}p</span> /
|
|
|
|
|
+ <span class="text-slate-500" id="filter-bytes-${r.id}">${formatTraffic(r.bytes || 0).replace('/s', '')}</span>
|
|
|
</td>
|
|
</td>
|
|
|
<td class="py-2.5 px-3 text-slate-400 italic truncate max-w-[120px]" title="${r.comment || ''}">${r.comment || '-'}</td>
|
|
<td class="py-2.5 px-3 text-slate-400 italic truncate max-w-[120px]" title="${r.comment || ''}">${r.comment || '-'}</td>
|
|
|
<td class="py-2.5 px-3 text-right space-x-1.5 font-sans whitespace-nowrap">
|
|
<td class="py-2.5 px-3 text-right space-x-1.5 font-sans whitespace-nowrap">
|
|
@@ -4604,8 +4636,8 @@ function renderNATRules() {
|
|
|
<td class="py-2.5 px-3">${renderActionBadge(r.action)}</td>
|
|
<td class="py-2.5 px-3">${renderActionBadge(r.action)}</td>
|
|
|
<td class="py-2.5 px-3 font-mono text-xs">${toTarget}</td>
|
|
<td class="py-2.5 px-3 font-mono text-xs">${toTarget}</td>
|
|
|
<td class="py-2.5 px-3 text-[11px] text-slate-400">
|
|
<td class="py-2.5 px-3 text-[11px] text-slate-400">
|
|
|
- <span class="text-slate-300">${(r.packets || 0).toLocaleString()}p</span> /
|
|
|
|
|
- <span class="text-slate-500">${formatTraffic(r.bytes || 0).replace('/s', '')}</span>
|
|
|
|
|
|
|
+ <span class="text-slate-300" id="nat-pkts-${r.id}">${(r.packets || 0).toLocaleString()}p</span> /
|
|
|
|
|
+ <span class="text-slate-500" id="nat-bytes-${r.id}">${formatTraffic(r.bytes || 0).replace('/s', '')}</span>
|
|
|
</td>
|
|
</td>
|
|
|
<td class="py-2.5 px-3 text-slate-400 italic truncate max-w-[120px]" title="${r.comment || ''}">${r.comment || '-'}</td>
|
|
<td class="py-2.5 px-3 text-slate-400 italic truncate max-w-[120px]" title="${r.comment || ''}">${r.comment || '-'}</td>
|
|
|
<td class="py-2.5 px-3 text-right space-x-1.5 font-sans whitespace-nowrap">
|
|
<td class="py-2.5 px-3 text-right space-x-1.5 font-sans whitespace-nowrap">
|
|
@@ -5238,8 +5270,8 @@ function renderMangleRules() {
|
|
|
<td class="py-2.5 px-3 font-mono text-xs">${markVal}</td>
|
|
<td class="py-2.5 px-3 font-mono text-xs">${markVal}</td>
|
|
|
<td class="py-2.5 px-3 text-center">${r.passthrough ? '<span class="text-emerald-400">yes</span>' : '<span class="text-slate-500">no</span>'}</td>
|
|
<td class="py-2.5 px-3 text-center">${r.passthrough ? '<span class="text-emerald-400">yes</span>' : '<span class="text-slate-500">no</span>'}</td>
|
|
|
<td class="py-2.5 px-3 text-[11px] text-slate-400">
|
|
<td class="py-2.5 px-3 text-[11px] text-slate-400">
|
|
|
- <span class="text-slate-300">${(r.packets || 0).toLocaleString()}p</span> /
|
|
|
|
|
- <span class="text-slate-500">${formatTraffic(r.bytes || 0).replace('/s', '')}</span>
|
|
|
|
|
|
|
+ <span class="text-slate-300" id="mangle-pkts-${r.id}">${(r.packets || 0).toLocaleString()}p</span> /
|
|
|
|
|
+ <span class="text-slate-500" id="mangle-bytes-${r.id}">${formatTraffic(r.bytes || 0).replace('/s', '')}</span>
|
|
|
</td>
|
|
</td>
|
|
|
<td class="py-2.5 px-3 text-slate-400 italic truncate max-w-[120px]" title="${r.comment || ''}">${r.comment || '-'}</td>
|
|
<td class="py-2.5 px-3 text-slate-400 italic truncate max-w-[120px]" title="${r.comment || ''}">${r.comment || '-'}</td>
|
|
|
<td class="py-2.5 px-3 text-right space-x-1.5 font-sans whitespace-nowrap">
|
|
<td class="py-2.5 px-3 text-right space-x-1.5 font-sans whitespace-nowrap">
|