Selaa lähdekoodia

fix(firewall): fix DOM tbody element IDs and initialize default filter rules

Proxmox Router 1 viikko sitten
vanhempi
commit
c5708ad6c0
2 muutettua tiedostoa jossa 101 lisäystä ja 52 poistoa
  1. 48 31
      internal/firewall/manager.go
  2. 53 21
      internal/web/static/app.js

+ 48 - 31
internal/firewall/manager.go

@@ -36,7 +36,7 @@ func NewFirewallManager() *FirewallManager {
 	m.load()
 
 	// If clean installation and no rules exist, initialize default sensible router rules (like RouterOS default firewall)
-	if len(m.filterRules) == 0 && len(m.natRules) == 0 {
+	if len(m.filterRules) == 0 || len(m.natRules) == 0 {
 		m.initDefaultRules()
 	}
 
@@ -45,36 +45,40 @@ func NewFirewallManager() *FirewallManager {
 }
 
 func (m *FirewallManager) initDefaultRules() {
-	// Default Masquerade NAT
-	m.natRules = append(m.natRules, NATRule{
-		ID:       fmt.Sprintf("nat-%d", time.Now().UnixNano()),
-		Order:    1,
-		Chain:    "srcnat",
-		Action:   "masquerade",
-		Comment:  "defconf: masquerade outgoing traffic",
-		Disabled: false,
-	})
-
-	// Default Fasttrack / Established,Related Filter
-	m.filterRules = append(m.filterRules, FilterRule{
-		ID:              fmt.Sprintf("filter-%d-1", time.Now().UnixNano()),
-		Order:           1,
-		Chain:           "input",
-		Action:          "accept",
-		ConnectionState: []string{"established", "related"},
-		Comment:         "defconf: accept established,related",
-		Disabled:        false,
-	})
-
-	m.filterRules = append(m.filterRules, FilterRule{
-		ID:              fmt.Sprintf("filter-%d-2", time.Now().UnixNano()),
-		Order:           2,
-		Chain:           "forward",
-		Action:          "accept",
-		ConnectionState: []string{"established", "related"},
-		Comment:         "defconf: accept established,related forward",
-		Disabled:        false,
-	})
+	if len(m.natRules) == 0 {
+		// Default Masquerade NAT
+		m.natRules = append(m.natRules, NATRule{
+			ID:       fmt.Sprintf("nat-%d", time.Now().UnixNano()),
+			Order:    1,
+			Chain:    "srcnat",
+			Action:   "masquerade",
+			Comment:  "defconf: masquerade outgoing traffic",
+			Disabled: false,
+		})
+	}
+
+	if len(m.filterRules) == 0 {
+		// Default Fasttrack / Established,Related Filter
+		m.filterRules = append(m.filterRules, FilterRule{
+			ID:              fmt.Sprintf("filter-%d-1", time.Now().UnixNano()),
+			Order:           1,
+			Chain:           "input",
+			Action:          "accept",
+			ConnectionState: []string{"established", "related"},
+			Comment:         "defconf: accept established,related",
+			Disabled:        false,
+		})
+
+		m.filterRules = append(m.filterRules, FilterRule{
+			ID:              fmt.Sprintf("filter-%d-2", time.Now().UnixNano()),
+			Order:           2,
+			Chain:           "forward",
+			Action:          "accept",
+			ConnectionState: []string{"established", "related"},
+			Comment:         "defconf: accept established,related forward",
+			Disabled:        false,
+		})
+	}
 
 	_ = m.save()
 }
@@ -92,6 +96,19 @@ func (m *FirewallManager) load() {
 	if data, err := os.ReadFile(AddressListsStorePath); err == nil {
 		_ = json.Unmarshal(data, &m.addressLists)
 	}
+
+	if m.filterRules == nil {
+		m.filterRules = make([]FilterRule, 0)
+	}
+	if m.natRules == nil {
+		m.natRules = make([]NATRule, 0)
+	}
+	if m.mangleRules == nil {
+		m.mangleRules = make([]MangleRule, 0)
+	}
+	if m.addressLists == nil {
+		m.addressLists = make([]AddressListEntry, 0)
+	}
 }
 
 func (m *FirewallManager) save() error {

+ 53 - 21
internal/web/static/app.js

@@ -3630,8 +3630,12 @@ function switchFWSubTab(tab) {
     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);
 }
 
@@ -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 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)) {
       tbodyAddr.dataset.sig = sigAddr;
       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">${renderActionBadge(r.action)}${actionExtra}</td>
       <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 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">
@@ -4604,8 +4636,8 @@ function renderNATRules() {
       <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 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 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">
@@ -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 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">
-        <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 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">