|
1208 | 1208 | padding: 0; |
1209 | 1209 | display: flex; |
1210 | 1210 | } |
| 1211 | + |
| 1212 | + /* Update banner (macOS: new rules available) */ |
| 1213 | + .update-banner { |
| 1214 | + position: fixed; |
| 1215 | + top: 0; |
| 1216 | + left: 0; |
| 1217 | + right: 0; |
| 1218 | + background: linear-gradient(135deg, var(--warning) 0%, #f59e0b 100%); |
| 1219 | + color: #1a1a1a; |
| 1220 | + padding: 12px 24px; |
| 1221 | + z-index: 500; |
| 1222 | + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
| 1223 | + } |
| 1224 | + .update-banner-content { |
| 1225 | + display: flex; |
| 1226 | + align-items: center; |
| 1227 | + justify-content: center; |
| 1228 | + gap: 16px; |
| 1229 | + flex-wrap: wrap; |
| 1230 | + } |
| 1231 | + .update-banner-content .btn-primary { |
| 1232 | + background: #1a1a1a; |
| 1233 | + color: white; |
| 1234 | + border-color: #1a1a1a; |
| 1235 | + } |
| 1236 | + .update-banner-content .btn-primary:hover { |
| 1237 | + background: #333; |
| 1238 | + color: white; |
| 1239 | + } |
1211 | 1240 | </style> |
1212 | 1241 | </head> |
1213 | 1242 |
|
@@ -1251,6 +1280,15 @@ <h1>ClawEDR <span>Dashboard</span></h1> |
1251 | 1280 | </svg> |
1252 | 1281 | Policy Rules |
1253 | 1282 | </button> |
| 1283 | + <button class="tab sidebar-link" data-tab="settings" |
| 1284 | + style="text-align: left; display: flex; align-items: center; gap: 12px; padding: 10px 16px;"> |
| 1285 | + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" |
| 1286 | + stroke-linecap="round" stroke-linejoin="round"> |
| 1287 | + <circle cx="12" cy="12" r="3"></circle> |
| 1288 | + <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path> |
| 1289 | + </svg> |
| 1290 | + Settings |
| 1291 | + </button> |
1254 | 1292 | </nav> |
1255 | 1293 |
|
1256 | 1294 | <div class="sidebar-footer" style="margin-top: 24px; display: flex; flex-direction: column; gap: 12px;"> |
@@ -1419,6 +1457,40 @@ <h3 style="font-size: 15px; font-weight: 600;">Alert Details</h3> |
1419 | 1457 | </div> |
1420 | 1458 | </div> |
1421 | 1459 |
|
| 1460 | + <!-- Settings Tab --> |
| 1461 | + <div class="tab-panel" id="tab-settings"> |
| 1462 | + <div class="card"> |
| 1463 | + <div class="card-header"> |
| 1464 | + <span class="card-title">Rule Updates</span> |
| 1465 | + </div> |
| 1466 | + <p style="color:var(--text-muted);font-size:12px;margin-bottom:16px;"> |
| 1467 | + ClawEDR checks the GitHub registry every hour for updated threat signatures/rules. |
| 1468 | + When enabled, updates are applied automatically on Linux. On macOS, you'll see a banner |
| 1469 | + and must restart OpenClaw to enforce new rules. |
| 1470 | + </p> |
| 1471 | + <div class="form-group"> |
| 1472 | + <label class="form-label" style="display:flex;align-items:center;gap:12px;cursor:pointer;"> |
| 1473 | + <input type="checkbox" id="autoUpdateToggle" checked onchange="toggleAutoUpdate(this.checked)"> |
| 1474 | + <span>Auto-check for rule updates (hourly)</span> |
| 1475 | + </label> |
| 1476 | + </div> |
| 1477 | + <div style="display:flex;gap:12px;margin-top:16px;"> |
| 1478 | + <button class="btn btn-primary" onclick="checkForUpdates()" id="checkUpdatesBtn"> |
| 1479 | + Check for updates now |
| 1480 | + </button> |
| 1481 | + <span id="lastCheckLabel" style="font-size:12px;color:var(--text-muted);align-self:center;"></span> |
| 1482 | + </div> |
| 1483 | + </div> |
| 1484 | + </div> |
| 1485 | + |
| 1486 | + <!-- Update banner (macOS: when updates available, show count + restart message) --> |
| 1487 | + <div class="update-banner" id="updateBanner" style="display:none;"> |
| 1488 | + <div class="update-banner-content"> |
| 1489 | + <span id="updateBannerText"></span> |
| 1490 | + <button class="btn btn-primary btn-sm" onclick="applyUpdates()" id="applyUpdatesBtn">Download & apply</button> |
| 1491 | + </div> |
| 1492 | + </div> |
| 1493 | + |
1422 | 1494 | <div class="footer">ClawEDR v2.0 — Kernel-level endpoint detection for AI agents</div> |
1423 | 1495 | </main> |
1424 | 1496 | </div> |
@@ -2071,11 +2143,100 @@ <h2 id="modalTitle">Add Custom Rule</h2> |
2071 | 2143 | renderAlerts(activeDisplayedAlerts); |
2072 | 2144 | } |
2073 | 2145 |
|
| 2146 | + // ── Settings & Updates ── |
| 2147 | + async function loadSettings() { |
| 2148 | + try { |
| 2149 | + const s = await fetchJSON('/api/settings'); |
| 2150 | + document.getElementById('autoUpdateToggle').checked = s.auto_update_rules !== false; |
| 2151 | + const last = s.last_update_check; |
| 2152 | + document.getElementById('lastCheckLabel').textContent = last |
| 2153 | + ? 'Last check: ' + new Date(last).toLocaleString() |
| 2154 | + : ''; |
| 2155 | + } catch (_) {} |
| 2156 | + } |
| 2157 | + async function toggleAutoUpdate(on) { |
| 2158 | + try { |
| 2159 | + await fetch('/api/settings', { |
| 2160 | + method: 'POST', |
| 2161 | + headers: { 'Content-Type': 'application/json' }, |
| 2162 | + body: JSON.stringify({ auto_update_rules: on }) |
| 2163 | + }); |
| 2164 | + showToast(on ? 'Auto-update enabled' : 'Auto-update disabled', 'info'); |
| 2165 | + } catch (e) { showToast('Failed to save', 'danger'); } |
| 2166 | + } |
| 2167 | + async function checkForUpdates() { |
| 2168 | + const btn = document.getElementById('checkUpdatesBtn'); |
| 2169 | + btn.disabled = true; |
| 2170 | + btn.textContent = 'Checking...'; |
| 2171 | + try { |
| 2172 | + const r = await fetchJSON('/api/updates'); |
| 2173 | + if (r.error) { |
| 2174 | + showToast(r.error, 'danger'); |
| 2175 | + } else if (r.has_updates) { |
| 2176 | + const n = r.change_count || 0; |
| 2177 | + showToast(`${n} rule change(s) available`, 'warning'); |
| 2178 | + showUpdateBanner(r); |
| 2179 | + } else { |
| 2180 | + showToast('Rules are up to date', 'success'); |
| 2181 | + hideUpdateBanner(); |
| 2182 | + } |
| 2183 | + } catch (e) { |
| 2184 | + showToast('Check failed: ' + e.message, 'danger'); |
| 2185 | + } |
| 2186 | + btn.disabled = false; |
| 2187 | + btn.textContent = 'Check for updates now'; |
| 2188 | + loadSettings(); |
| 2189 | + } |
| 2190 | + function showUpdateBanner(r) { |
| 2191 | + const n = r.change_count || 0; |
| 2192 | + const isMac = detectedOS === 'Darwin'; |
| 2193 | + const el = document.getElementById('updateBanner'); |
| 2194 | + const text = document.getElementById('updateBannerText'); |
| 2195 | + text.textContent = isMac |
| 2196 | + ? `${n} new rule change(s) available. Restart OpenClaw to enforce.` |
| 2197 | + : `${n} new rule change(s) available.`; |
| 2198 | + el.style.display = 'block'; |
| 2199 | + } |
| 2200 | + function hideUpdateBanner() { |
| 2201 | + document.getElementById('updateBanner').style.display = 'none'; |
| 2202 | + } |
| 2203 | + async function applyUpdates() { |
| 2204 | + const btn = document.getElementById('applyUpdatesBtn'); |
| 2205 | + btn.disabled = true; |
| 2206 | + btn.textContent = 'Applying...'; |
| 2207 | + try { |
| 2208 | + const res = await fetch('/api/updates/apply', { method: 'POST' }); |
| 2209 | + const data = await res.json(); |
| 2210 | + if (data.error) { |
| 2211 | + showToast(data.error, 'danger'); |
| 2212 | + } else { |
| 2213 | + showToast(data.message || 'Updates applied', 'success'); |
| 2214 | + hideUpdateBanner(); |
| 2215 | + if (detectedOS === 'Linux') loadRules(); |
| 2216 | + } |
| 2217 | + } catch (e) { |
| 2218 | + showToast('Apply failed: ' + e.message, 'danger'); |
| 2219 | + } |
| 2220 | + btn.disabled = false; |
| 2221 | + btn.textContent = 'Download & apply'; |
| 2222 | + loadSettings(); |
| 2223 | + } |
| 2224 | + |
2074 | 2225 | // ── Init ── |
2075 | | - loadStatus().then(() => loadRules().then(() => loadUserRules())); |
| 2226 | + loadStatus().then(() => { |
| 2227 | + loadRules().then(() => loadUserRules()); |
| 2228 | + loadSettings(); |
| 2229 | + checkForUpdates().then(() => {}); // Initial check for banner |
| 2230 | + }); |
2076 | 2231 | loadSessions(); loadAlerts(); loadCustomRules(); |
2077 | 2232 | setInterval(loadAlerts, 30000); |
2078 | 2233 | setInterval(loadSessions, 15000); |
| 2234 | + setInterval(async () => { |
| 2235 | + try { |
| 2236 | + const r = await fetchJSON('/api/updates/cached'); |
| 2237 | + if (r.has_updates) showUpdateBanner(r); |
| 2238 | + } catch (_) {} |
| 2239 | + }, 120000); // Poll for cached updates every 2 min (from background hourly check) |
2079 | 2240 | </script> |
2080 | 2241 | </body> |
2081 | 2242 |
|
|
0 commit comments