Просмотр исходного кода

fix(geoip): remove redundant disable/enable buttons from geo-location country table and show rule usage instead

Proxmox Router 1 неделя назад
Родитель
Сommit
3657dc1794
2 измененных файлов с 96 добавлено и 23 удалено
  1. 81 13
      internal/web/static/app.js
  2. 15 10
      internal/web/static/index.html

+ 81 - 13
internal/web/static/app.js

@@ -3235,6 +3235,25 @@ async function loadGeoIP() {
   }
 }
 
+function getGeoUsageMap() {
+  const usage = {};
+  const checkRule = (r, type) => {
+    ['src_geoip', 'dst_geoip'].forEach(field => {
+      let val = (r && r[field]) || '';
+      let code = val.replace(/^!/, '').replace(/^geo:/, '').replace(/^geo_/, '').toUpperCase().trim();
+      if (code) {
+        if (!usage[code]) usage[code] = [];
+        const label = `${type} #${r.priority !== undefined ? r.priority : (r.id || '')}`;
+        if (!usage[code].includes(label)) usage[code].push(label);
+      }
+    });
+  };
+  (state.filterRules || []).forEach(r => checkRule(r, 'Filter'));
+  (state.natRules || []).forEach(r => checkRule(r, 'NAT'));
+  (state.mangleRules || []).forEach(r => checkRule(r, 'Mangle'));
+  return usage;
+}
+
 function updateGeoHeader() {
   const s = state.geoStatus || {};
   const statusBadge = document.getElementById('geo-status-badge');
@@ -3250,8 +3269,11 @@ function updateGeoHeader() {
     intervalLabel.innerText = `Every ${s.interval_hours || 24}h`;
   }
 
+  const usageMap = getGeoUsageMap();
+  const inUseCount = Object.keys(usageMap).length;
+
   const activeCountEl = document.getElementById('geo-stat-active-countries');
-  if (activeCountEl) activeCountEl.innerText = (s.enabled_count || 0).toLocaleString();
+  if (activeCountEl) activeCountEl.innerText = inUseCount.toLocaleString();
 
   const totalSubnetsEl = document.getElementById('geo-stat-total-subnets');
   if (totalSubnetsEl) totalSubnetsEl.innerText = (s.total_subnets || 0).toLocaleString();
@@ -3277,7 +3299,7 @@ function updateGeoHeader() {
   }
 
   const badge = document.getElementById('fw-badge-geo');
-  if (badge) badge.innerText = s.enabled_count || 0;
+  if (badge) badge.innerText = inUseCount || (state.geoCountries || []).filter(c => c.subnet_count > 0).length || 0;
 }
 
 function renderGeoCountries() {
@@ -3288,10 +3310,14 @@ function renderGeoCountries() {
 
   const q = (document.getElementById('geo-search-input')?.value || '').trim().toLowerCase();
   const continent = document.getElementById('geo-continent-filter')?.value || '';
-  const enabledOnly = document.getElementById('geo-filter-enabled-only')?.checked;
+  const inUseOnly = document.getElementById('geo-filter-inuse-only')?.checked;
+  const cachedOnly = document.getElementById('geo-filter-cached-only')?.checked;
+  const usageMap = getGeoUsageMap();
 
   const countries = (state.geoCountries || []).filter(c => {
-    if (enabledOnly && !c.enabled) return false;
+    const isUsed = !!usageMap[c.code];
+    if (inUseOnly && !isUsed) return false;
+    if (cachedOnly && (!c.subnet_count || c.subnet_count === 0)) return false;
     if (continent && c.continent !== continent) return false;
     if (q) {
       const matchCode = c.code.toLowerCase().includes(q);
@@ -3309,11 +3335,22 @@ function renderGeoCountries() {
 
   countries.forEach(c => {
     const tr = document.createElement('tr');
-    tr.className = `hover:bg-slate-800/40 transition text-xs ${c.enabled ? 'bg-emerald-950/10' : ''}`;
+    const isUsed = !!usageMap[c.code];
+    const rulesUsing = usageMap[c.code] || [];
+    tr.className = `hover:bg-slate-800/40 transition text-xs ${isUsed ? 'bg-emerald-950/20' : ''}`;
+
+    let statusHtml = '';
+    if (isUsed) {
+      statusHtml = `<span class="px-2 py-0.5 rounded text-[10px] font-mono font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/40" title="Assigned in: ${rulesUsing.join(', ')}">IN USE (${rulesUsing.length})</span>`;
+    } else if (c.subnet_count > 0) {
+      statusHtml = '<span class="px-2 py-0.5 rounded text-[10px] font-mono text-cyan-300 bg-cyan-950/40 border border-cyan-800/40">CACHED</span>';
+    } else {
+      statusHtml = '<span class="px-2 py-0.5 rounded text-[10px] font-mono text-slate-500 bg-slate-800/60">READY</span>';
+    }
 
     tr.innerHTML = `
       <td class="py-2.5 px-3 text-center">
-        <span class="px-2 py-0.5 rounded font-mono font-bold ${c.enabled ? 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/40' : 'bg-slate-800 text-cyan-300 border border-slate-700'}">
+        <span class="px-2 py-0.5 rounded font-mono font-bold ${isUsed ? 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/40' : 'bg-slate-800 text-cyan-300 border border-slate-700'}">
           ${c.code}
         </span>
       </td>
@@ -3324,15 +3361,10 @@ function renderGeoCountries() {
       </td>
       <td class="py-2.5 px-3 font-mono text-purple-300">${c.ipset_name}</td>
       <td class="py-2.5 px-3">
-        ${c.enabled
-          ? '<span class="px-2 py-0.5 rounded text-[10px] font-mono font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/40">ENABLED</span>'
-          : '<span class="px-2 py-0.5 rounded text-[10px] font-mono font-bold bg-slate-800 text-slate-500">DISABLED</span>'}
+        ${statusHtml}
       </td>
       <td class="py-2.5 px-3 text-right space-x-2 font-sans whitespace-nowrap">
-        ${c.enabled
-          ? `<button onclick="toggleGeoCountry('${c.code}', false)" class="text-xs text-rose-400 hover:underline font-semibold">Disable</button>`
-          : `<button onclick="toggleGeoCountry('${c.code}', true)" class="text-xs text-emerald-400 hover:underline font-semibold">Enable</button>`}
-        <button onclick="updateGeoCountry('${c.code}')" class="text-xs text-cyan-400 hover:text-cyan-300" title="Re-download latest country IP blocks">Sync</button>
+        <button onclick="updateGeoCountry('${c.code}')" class="text-xs text-cyan-400 hover:text-cyan-300 font-medium" title="Re-download latest country IP blocks">Sync</button>
         <button onclick="viewGeoSubnets('${c.code}', '${c.name.replace(/'/g, "\\'")}')" class="text-xs text-slate-400 hover:text-white" title="Preview Subnet Ranges">View</button>
       </td>
     `;
@@ -3340,6 +3372,27 @@ function renderGeoCountries() {
   });
 }
 
+function quickFilterGeo(preset) {
+  const searchInput = document.getElementById('geo-search-input');
+  const continentSel = document.getElementById('geo-continent-filter');
+  const inUseCheck = document.getElementById('geo-filter-inuse-only');
+  const cachedCheck = document.getElementById('geo-filter-cached-only');
+
+  if (searchInput) searchInput.value = '';
+  if (continentSel) continentSel.value = '';
+  if (inUseCheck) inUseCheck.checked = false;
+  if (cachedCheck) cachedCheck.checked = false;
+
+  if (preset === 'GR') {
+    if (searchInput) searchInput.value = 'Greece';
+  } else if (preset === 'Europe') {
+    if (continentSel) continentSel.value = 'Europe';
+  } else if (preset === 'HIGHRISK') {
+    if (searchInput) searchInput.value = 'China';
+  }
+  renderGeoCountries();
+}
+
 async function toggleGeoCountry(code, enabled) {
   try {
     const res = await fetch(`${API_BASE}/firewall/geoip/countries/${encodeURIComponent(code)}/toggle`, {
@@ -3358,6 +3411,21 @@ async function toggleGeoCountry(code, enabled) {
     showToast('Error', err.message, 'error');
   }
 }
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ enabled })
+    });
+    if (res.ok) {
+      showToast(enabled ? 'Country Enabled' : 'Country Disabled', `${code} ${enabled ? 'active in firewall' : 'removed from firewall'}`, 'success');
+      loadGeoIP();
+    } else {
+      const err = await res.json();
+      showToast('Error', err.message || 'Failed to toggle country', 'error');
+    }
+  } catch (err) {
+    showToast('Error', err.message, 'error');
+  }
+}
 
 async function updateGeoCountry(code) {
   try {

+ 15 - 10
internal/web/static/index.html

@@ -1676,7 +1676,7 @@
             <!-- Stats Bar -->
             <div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
               <div class="bg-slate-900/60 border border-slate-800/80 rounded-xl p-3">
-                <span class="text-[10px] uppercase font-bold text-slate-500 block">Active Countries</span>
+                <span class="text-[10px] uppercase font-bold text-slate-500 block">Countries in Rules</span>
                 <span id="geo-stat-active-countries" class="text-base font-bold font-mono text-emerald-400">0</span>
               </div>
               <div class="bg-slate-900/60 border border-slate-800/80 rounded-xl p-3">
@@ -1709,17 +1709,22 @@
                   </select>
 
                   <label class="flex items-center space-x-1.5 text-xs text-slate-300 cursor-pointer ml-2">
-                    <input type="checkbox" id="geo-filter-enabled-only" onchange="renderGeoCountries()" class="rounded bg-slate-800 border-slate-700 text-emerald-500">
-                    <span>Enabled in Firewall only</span>
+                    <input type="checkbox" id="geo-filter-inuse-only" onchange="renderGeoCountries()" class="rounded bg-slate-800 border-slate-700 text-emerald-500">
+                    <span>In Use by Rules only</span>
+                  </label>
+
+                  <label class="flex items-center space-x-1.5 text-xs text-slate-300 cursor-pointer ml-2">
+                    <input type="checkbox" id="geo-filter-cached-only" onchange="renderGeoCountries()" class="rounded bg-slate-800 border-slate-700 text-cyan-500">
+                    <span>Cached Only</span>
                   </label>
                 </div>
 
                 <div class="flex items-center space-x-2">
-                  <span class="text-xs text-slate-400 font-sans">Presets:</span>
-                  <button onclick="applyGeoPreset('GR')" class="px-2.5 py-1 text-xs rounded bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-700 font-mono">🇬🇷 GR</button>
-                  <button onclick="applyGeoPreset('EU')" class="px-2.5 py-1 text-xs rounded bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-700 font-mono">🇪🇺 EU</button>
-                  <button onclick="applyGeoPreset('BLOCKLIST')" class="px-2.5 py-1 text-xs rounded bg-rose-950/40 hover:bg-rose-900/50 text-rose-300 border border-rose-800/50 font-mono">🚫 High-Risk</button>
-                  <button onclick="applyGeoPreset('NONE')" class="px-2.5 py-1 text-xs rounded bg-slate-800 hover:bg-slate-700 text-slate-400 border border-slate-700 font-mono">Clear All</button>
+                  <span class="text-xs text-slate-400 font-sans">Quick Filter:</span>
+                  <button onclick="quickFilterGeo('GR')" class="px-2.5 py-1 text-xs rounded bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-700 font-mono">🇬🇷 GR</button>
+                  <button onclick="quickFilterGeo('Europe')" class="px-2.5 py-1 text-xs rounded bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-700 font-mono">🇪🇺 Europe</button>
+                  <button onclick="quickFilterGeo('HIGHRISK')" class="px-2.5 py-1 text-xs rounded bg-rose-950/40 hover:bg-rose-900/50 text-rose-300 border border-rose-800/50 font-mono">🚫 High-Risk</button>
+                  <button onclick="quickFilterGeo('')" class="px-2.5 py-1 text-xs rounded bg-slate-800 hover:bg-slate-700 text-slate-400 border border-slate-700 font-mono">Reset</button>
                 </div>
               </div>
 
@@ -1732,7 +1737,7 @@
                       <th class="py-2.5 px-3">Continent</th>
                       <th class="py-2.5 px-3">Subnets</th>
                       <th class="py-2.5 px-3">Kernel IPSet</th>
-                      <th class="py-2.5 px-3">Firewall Status</th>
+                      <th class="py-2.5 px-3">Rule Usage</th>
                       <th class="py-2.5 px-3 text-right font-sans">Actions</th>
                     </tr>
                   </thead>
@@ -4247,6 +4252,6 @@
     </div>
   </div>
 
-  <script src="app.js?v=2.9"></script>
+  <script src="app.js?v=3.0"></script>
 </body>
 </html>