show running process in process viewer and add limiting to the notification system
This commit is contained in:
@@ -27,6 +27,9 @@ const keywordInput = document.getElementById("keyword-input");
|
||||
const clipboardEnabledInput = document.getElementById("clipboard-enabled");
|
||||
const saveKeywordsBtn = document.getElementById("save-keywords");
|
||||
const keywordHint = document.getElementById("keyword-hint");
|
||||
const antiSpamMaxHitsInput = document.getElementById("antispam-max-hits");
|
||||
const antiSpamWindowInput = document.getElementById("antispam-window");
|
||||
const antiSpamCooldownInput = document.getElementById("antispam-cooldown");
|
||||
|
||||
// Webhook section
|
||||
const webhookEnabledInput = document.getElementById("webhook-enabled");
|
||||
@@ -476,6 +479,15 @@ async function loadKeywords() {
|
||||
if (clipboardEnabledInput) {
|
||||
clipboardEnabledInput.checked = data?.notifications?.clipboardEnabled === true;
|
||||
}
|
||||
if (antiSpamMaxHitsInput) {
|
||||
antiSpamMaxHitsInput.value = data?.notifications?.antiSpamMaxHits ?? 15;
|
||||
}
|
||||
if (antiSpamWindowInput) {
|
||||
antiSpamWindowInput.value = Math.round((data?.notifications?.antiSpamWindowMs ?? 600000) / 60000);
|
||||
}
|
||||
if (antiSpamCooldownInput) {
|
||||
antiSpamCooldownInput.value = Math.round((data?.notifications?.antiSpamCooldownMs ?? 600000) / 60000);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -484,11 +496,21 @@ function wireKeywordSave() {
|
||||
saveKeywordsBtn.addEventListener("click", async () => {
|
||||
const keywords = parseKeywords(keywordInput.value);
|
||||
const clipboardEnabled = clipboardEnabledInput ? clipboardEnabledInput.checked : false;
|
||||
const payload = { keywords, clipboardEnabled };
|
||||
if (antiSpamMaxHitsInput) {
|
||||
payload.antiSpamMaxHits = Math.max(1, parseInt(antiSpamMaxHitsInput.value, 10) || 15);
|
||||
}
|
||||
if (antiSpamWindowInput) {
|
||||
payload.antiSpamWindowMs = Math.max(1, parseInt(antiSpamWindowInput.value, 10) || 10) * 60000;
|
||||
}
|
||||
if (antiSpamCooldownInput) {
|
||||
payload.antiSpamCooldownMs = Math.max(1, parseInt(antiSpamCooldownInput.value, 10) || 10) * 60000;
|
||||
}
|
||||
try {
|
||||
const res = await fetch("/api/notifications/config", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ keywords, clipboardEnabled }),
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!res.ok) {
|
||||
window.showToast?.("Failed to save keywords", "error", 4000);
|
||||
@@ -501,6 +523,15 @@ function wireKeywordSave() {
|
||||
if (clipboardEnabledInput && typeof data?.notifications?.clipboardEnabled === "boolean") {
|
||||
clipboardEnabledInput.checked = data.notifications.clipboardEnabled;
|
||||
}
|
||||
if (antiSpamMaxHitsInput && data?.notifications?.antiSpamMaxHits) {
|
||||
antiSpamMaxHitsInput.value = data.notifications.antiSpamMaxHits;
|
||||
}
|
||||
if (antiSpamWindowInput && data?.notifications?.antiSpamWindowMs) {
|
||||
antiSpamWindowInput.value = Math.round(data.notifications.antiSpamWindowMs / 60000);
|
||||
}
|
||||
if (antiSpamCooldownInput && data?.notifications?.antiSpamCooldownMs) {
|
||||
antiSpamCooldownInput.value = Math.round(data.notifications.antiSpamCooldownMs / 60000);
|
||||
}
|
||||
window.showToast?.("Keywords updated", "success", 3000);
|
||||
} catch {
|
||||
window.showToast?.("Failed to save keywords", "error", 4000);
|
||||
|
||||
@@ -255,6 +255,9 @@ function createProcessRow(proc, depth = 0) {
|
||||
if (selectedPid === proc.pid) {
|
||||
row.classList.add("selected");
|
||||
}
|
||||
if (proc.self) {
|
||||
row.classList.add("self-process");
|
||||
}
|
||||
|
||||
const cpuColor =
|
||||
proc.cpu > 50
|
||||
@@ -276,6 +279,7 @@ function createProcessRow(proc, depth = 0) {
|
||||
}
|
||||
|
||||
let nameColor = "text-slate-200";
|
||||
let iconClass = "fa-microchip";
|
||||
let iconColor = "text-blue-400";
|
||||
if (proc.type === "system") {
|
||||
nameColor = "text-purple-400";
|
||||
@@ -287,12 +291,21 @@ function createProcessRow(proc, depth = 0) {
|
||||
nameColor = "text-green-300";
|
||||
iconColor = "text-green-400";
|
||||
}
|
||||
if (proc.self) {
|
||||
nameColor = "text-yellow-300 font-semibold";
|
||||
iconClass = "fa-crosshairs";
|
||||
iconColor = "text-yellow-400";
|
||||
}
|
||||
|
||||
const selfBadge = proc.self
|
||||
? ' <span class="ml-1 text-[10px] uppercase tracking-wide px-1.5 py-0.5 rounded bg-yellow-500/20 text-yellow-300 border border-yellow-500/40">agent</span>'
|
||||
: "";
|
||||
|
||||
row.innerHTML = `
|
||||
<div class="col-span-1 text-sm font-mono text-slate-400">${proc.pid}</div>
|
||||
<div class="col-span-4 flex items-center gap-1 truncate">
|
||||
${indent}${treeIcon}<i class="fa-solid fa-microchip ${iconColor}"></i>
|
||||
<span class="truncate ${nameColor}">${escapeHtml(proc.name)}</span>
|
||||
${indent}${treeIcon}<i class="fa-solid ${iconClass} ${iconColor}"></i>
|
||||
<span class="truncate ${nameColor}">${escapeHtml(proc.name)}</span>${selfBadge}
|
||||
</div>
|
||||
<div class="col-span-2 text-sm ${cpuColor} font-semibold">${proc.cpu.toFixed(1)}%</div>
|
||||
<div class="col-span-2 text-sm text-slate-400">${memoryStr}</div>
|
||||
|
||||
@@ -72,6 +72,46 @@
|
||||
Monitor clipboard content for keyword matches
|
||||
</label>
|
||||
<div class="mt-2 text-xs text-slate-500" id="keyword-hint"></div>
|
||||
|
||||
<!-- Anti-Spam Settings -->
|
||||
<div class="mt-4 pt-4 border-t border-slate-800">
|
||||
<h3 class="text-sm font-semibold text-slate-300">Anti-Spam Limits</h3>
|
||||
<p class="text-xs text-slate-500 mt-1">
|
||||
Block a keyword for a client when it fires too many times in a short window.
|
||||
</p>
|
||||
<div class="mt-3 grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs text-slate-400 mb-1">Max hits before block</label>
|
||||
<input
|
||||
id="antispam-max-hits"
|
||||
type="number"
|
||||
min="1"
|
||||
max="10000"
|
||||
class="w-full rounded-lg bg-slate-950 border border-slate-800 text-slate-100 px-3 py-2 text-sm focus:outline-none focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-slate-400 mb-1">Detection window (minutes)</label>
|
||||
<input
|
||||
id="antispam-window"
|
||||
type="number"
|
||||
min="1"
|
||||
max="1440"
|
||||
class="w-full rounded-lg bg-slate-950 border border-slate-800 text-slate-100 px-3 py-2 text-sm focus:outline-none focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-slate-400 mb-1">Cooldown (minutes)</label>
|
||||
<input
|
||||
id="antispam-cooldown"
|
||||
type="number"
|
||||
min="1"
|
||||
max="1440"
|
||||
class="w-full rounded-lg bg-slate-950 border border-slate-800 text-slate-100 px-3 py-2 text-sm focus:outline-none focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Per-user Webhook Delivery -->
|
||||
|
||||
@@ -26,6 +26,14 @@
|
||||
background-color: rgba(59, 130, 246, 0.3);
|
||||
border-color: rgba(59, 130, 246, 0.6);
|
||||
}
|
||||
.process-row.self-process {
|
||||
background-color: rgba(234, 179, 8, 0.12);
|
||||
border-color: rgba(234, 179, 8, 0.5);
|
||||
box-shadow: inset 3px 0 0 0 #facc15;
|
||||
}
|
||||
.process-row.self-process.selected {
|
||||
background-color: rgba(234, 179, 8, 0.28);
|
||||
}
|
||||
/* Dark scrollbar styling */
|
||||
#process-list::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
@@ -189,6 +197,10 @@
|
||||
<i class="fa-solid fa-circle text-slate-200"></i>
|
||||
<span class="text-slate-400">Other</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<i class="fa-solid fa-crosshairs text-yellow-400"></i>
|
||||
<span class="text-slate-400">Overlord Agent</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Process List -->
|
||||
|
||||
Reference in New Issue
Block a user