From 7b71028db137dc438904c7a9299ea896371cb88c Mon Sep 17 00:00:00 2001 From: JaiPrakashVI Date: Mon, 26 Oct 2026 21:28:39 +0530 Subject: [PATCH] feat(playground): add real-time event filter and search bar (fixes #774) --- agentwatch-landing/app/playground/page.tsx | 233 +++++++++++++----- panopti/playground.html | 263 ++++++++++++++++++++- 2 files changed, 430 insertions(+), 66 deletions(-) diff --git a/agentwatch-landing/app/playground/page.tsx b/agentwatch-landing/app/playground/page.tsx index 7173334d..def7efe8 100644 --- a/agentwatch-landing/app/playground/page.tsx +++ b/agentwatch-landing/app/playground/page.tsx @@ -1,55 +1,78 @@ -"use client"; +'use client'; -import { useState } from "react"; -import Link from "next/link"; -import Navbar from "../components/Navbar"; -import Footer from "../components/Footer"; +import { useState, useMemo } from 'react'; +import Link from 'next/link'; +import Navbar from '../components/Navbar'; +import Footer from '../components/Footer'; export default function PlaygroundPage() { - const [toolName, setToolName] = useState("execute_sql"); + const [toolName, setToolName] = useState('execute_sql'); const [toolArgs, setToolArgs] = useState('{"query": "DROP TABLE users;"}'); - const [verdict, setVerdict] = useState<"VETO" | "ALLOW">("VETO"); - const [cbState, setCbState] = useState("TRIPPED (OPEN)"); - const [confidence, setConfidence] = useState(0.10); - const [merkleRoot, setMerkleRoot] = useState("0x9661f089d02af353..."); - const [logs, setLogs] = useState>([ + const [verdict, setVerdict] = useState<'VETO' | 'ALLOW'>('VETO'); + const [cbState, setCbState] = useState('TRIPPED (OPEN)'); + const [confidence, setConfidence] = useState(0.1); + const [merkleRoot, setMerkleRoot] = useState('0x9661f089d02af353...'); + const [logs, setLogs] = useState< + Array<{ time: string; tag: string; msg: string; isVeto: boolean }> + >([ { - time: "00:00:01", - tag: "INIT", - msg: "Panopti Sentinel Pre-Execution Runtime Hypervisor online.", + time: '00:00:01', + tag: 'INIT', + msg: 'Panopti Sentinel Pre-Execution Runtime Hypervisor online.', isVeto: false, }, { - time: "00:00:02", - tag: "VETO", + time: '00:00:02', + tag: 'VETO', msg: "Pre-execution interceptor blocked 'execute_sql' (DROP TABLE users;) - Invariant breach!", isVeto: true, }, ]); + const [filterMode, setFilterMode] = useState<'ALL' | 'VETO' | 'ALLOW'>('ALL'); + const [searchQuery, setSearchQuery] = useState(''); + + const filteredLogs = useMemo(() => { + return logs.filter((log) => { + const matchesFilter = + filterMode === 'ALL' || + (filterMode === 'VETO' && (log.isVeto || log.tag === 'VETO')) || + (filterMode === 'ALLOW' && (log.tag === 'ALLOW' || (!log.isVeto && log.tag !== 'INIT'))); + + const q = searchQuery.trim().toLowerCase(); + const matchesSearch = + !q || + log.msg.toLowerCase().includes(q) || + log.tag.toLowerCase().includes(q) || + log.time.toLowerCase().includes(q); + + return matchesFilter && matchesSearch; + }); + }, [logs, filterMode, searchQuery]); + const scenarios = [ { - name: "πŸ’₯ Adversarial SQL Injection (DROP TABLE)", - tag: "HIGH RISK", - tool: "execute_sql", + name: 'πŸ’₯ Adversarial SQL Injection (DROP TABLE)', + tag: 'HIGH RISK', + tool: 'execute_sql', args: '{"query": "DROP TABLE users;"}', }, { - name: "πŸ”„ Tool Oscillation & Infinite Hammering", - tag: "LOOP TRAP", - tool: "sync_customer_data", + name: 'πŸ”„ Tool Oscillation & Infinite Hammering', + tag: 'LOOP TRAP', + tool: 'sync_customer_data', args: '{"batch_id": 994, "retry_count": 8}', }, { - name: "⚠️ Unverified Financial State Mutation", - tag: "INVARIANT BREACH", - tool: "transfer_funds", + name: '⚠️ Unverified Financial State Mutation', + tag: 'INVARIANT BREACH', + tool: 'transfer_funds', args: '{"recipient": "0xAttacker", "amount_usd": 500000}', }, { - name: "βœ… Nominal Safe Read Query", - tag: "BENIGN", - tool: "fetch_catalog", + name: 'βœ… Nominal Safe Read Query', + tag: 'BENIGN', + tool: 'fetch_catalog', args: '{"category": "ai_tools", "limit": 10}', }, ]; @@ -57,34 +80,35 @@ export default function PlaygroundPage() { const handleIntercept = (overrideTool?: string, overrideArgs?: string) => { const t = overrideTool ?? toolName; const a = overrideArgs ?? toolArgs; - const isDestructive = /drop\s+table|truncate|delete\s+from|0xattacker|retry_count":\s*[5-9]/i.test(a); - const now = new Date().toTimeString().split(" ")[0]; - const newHash = "0x" + Math.random().toString(16).substring(2, 10) + "..."; + const isDestructive = + /drop\s+table|truncate|delete\s+from|0xattacker|retry_count":\s*[5-9]/i.test(a); + const now = new Date().toTimeString().split(' ')[0]; + const newHash = '0x' + Math.random().toString(16).substring(2, 10) + '...'; if (isDestructive) { - setVerdict("VETO"); - setCbState("TRIPPED (OPEN)"); - setConfidence(0.10); + setVerdict('VETO'); + setCbState('TRIPPED (OPEN)'); + setConfidence(0.1); setMerkleRoot(newHash); setLogs((prev) => [ ...prev, { time: now, - tag: "VETO", + tag: 'VETO', msg: `Blocked proposed tool '${t}' before execution. Invariant breach detected!`, isVeto: true, }, ]); } else { - setVerdict("ALLOW"); - setCbState("CLOSED"); + setVerdict('ALLOW'); + setCbState('CLOSED'); setConfidence(0.98); setMerkleRoot(newHash); setLogs((prev) => [ ...prev, { time: now, - tag: "ALLOW", + tag: 'ALLOW', msg: `Approved proposed tool '${t}'. All temporal contracts and confidence floors satisfied.`, isVeto: false, }, @@ -104,8 +128,8 @@ export default function PlaygroundPage() { Test Panopti’s Active Intercept

- Simulate malicious agent tool calls, adversarial prompt injections, and oscillation traps. - Watch Panopti veto mutations before they reach production. + Simulate malicious agent tool calls, adversarial prompt injections, and oscillation + traps. Watch Panopti veto mutations before they reach production.

@@ -133,9 +157,9 @@ export default function PlaygroundPage() { {sc.tag} @@ -192,50 +216,133 @@ export default function PlaygroundPage() { {/* Verdict Banner */}
- {verdict === "VETO" ? "πŸ›‘ PRE-EXECUTION VETO ENFORCED" : "βœ… PRE-EXECUTION APPROVED"} + {verdict === 'VETO' + ? 'πŸ›‘ PRE-EXECUTION VETO ENFORCED' + : 'βœ… PRE-EXECUTION APPROVED'}
{/* Stats Row */}
-
Circuit Breaker
-
+
+ Circuit Breaker +
+
{cbState}
-
Confidence Floor
-
+
+ Confidence Floor +
+
{confidence.toFixed(2)}
-
Merkle Witness
+
+ Merkle Witness +
{merkleRoot}
- {/* Audit Console */} -
- Cryptographic Witness Audit Stream + {/* Audit Console Header & Controls */} +
+ + Cryptographic Witness Audit Stream + + + Showing {filteredLogs.length} / {logs.length} events +
+ + {/* Real-time Filter & Search Bar */} +
+
+ setSearchQuery(e.target.value)} + placeholder="Filter by tool or keyword..." + className="w-full bg-[#060911] border border-[#1f293d] focus:border-[#00f0ff] rounded-lg px-3 py-1.5 pr-7 text-xs font-mono text-white placeholder-[#4e678e] outline-none transition-colors" + /> + {searchQuery && ( + + )} +
+
+ + + +
+
+ + {/* Console Stream */}
- {logs.map((log, idx) => ( -
- [{log.time}] - - [{log.tag}] - - {log.msg} + {filteredLogs.length > 0 ? ( + filteredLogs.map((log, idx) => ( +
+ [{log.time}] + + [{log.tag}] + + {log.msg} +
+ )) + ) : ( +
+ ⚑ No audit events matching current filter or search criteria.
- ))} + )}
diff --git a/panopti/playground.html b/panopti/playground.html index 26b861b8..203c5820 100644 --- a/panopti/playground.html +++ b/panopti/playground.html @@ -285,6 +285,150 @@ .log-tag-veto { color: var(--danger); font-weight: bold; } .log-tag-info { color: var(--accent); } + /* Filter & Search Bar */ + .log-controls-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 10px; + margin-bottom: 8px; + } + + .log-counter { + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-muted); + } + + .log-toolbar { + display: flex; + gap: 8px; + margin-bottom: 10px; + align-items: center; + flex-wrap: wrap; + } + + .search-box-wrapper { + position: relative; + flex: 1; + min-width: 150px; + } + + .search-box-wrapper input { + width: 100%; + height: 32px; + padding: 6px 26px 6px 10px; + font-size: 12px; + background: #060911; + border: 1px solid #1f293d; + border-radius: 6px; + color: #fff; + font-family: var(--font-mono); + outline: none; + transition: all 0.2s ease; + } + + .search-box-wrapper input:focus { + border-color: var(--accent); + box-shadow: 0 0 8px var(--accent-dim); + } + + .search-box-wrapper input::placeholder { + color: #4e678e; + } + + .search-clear-btn { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + font-size: 12px; + line-height: 1; + padding: 2px 4px; + border-radius: 3px; + } + + .search-clear-btn:hover { + color: #fff; + } + + .filter-btn-group { + display: flex; + gap: 6px; + } + + .filter-btn { + background: #090e18; + border: 1px solid #1f293d; + color: var(--text-muted); + padding: 6px 10px; + border-radius: 6px; + font-size: 11px; + font-family: var(--font-mono); + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .filter-btn:hover { + border-color: #3b4e72; + color: var(--text); + } + + .filter-btn.active { + background: var(--accent-dim); + border-color: var(--accent); + color: var(--accent); + box-shadow: 0 0 8px var(--accent-dim); + } + + .filter-btn.filter-btn-veto.active { + background: var(--danger-dim); + border-color: var(--danger); + color: var(--danger); + box-shadow: 0 0 8px var(--danger-dim); + } + + .filter-btn.filter-btn-allow.active { + background: var(--success-dim); + border-color: var(--success); + color: var(--success); + box-shadow: 0 0 8px var(--success-dim); + } + + .empty-log-msg { + text-align: center; + padding: 24px 10px; + color: var(--text-muted); + font-size: 12px; + font-family: var(--font-mono); + font-style: italic; + } + + @media (max-width: 600px) { + .log-toolbar { + flex-direction: column; + align-items: stretch; + } + .filter-btn-group { + width: 100%; + display: flex; + } + .filter-btn { + flex: 1; + text-align: center; + padding: 7px 4px; + font-size: 10px; + } + } + /* Visual Graph Canvas */ #graphCanvas { width: 100%; @@ -380,13 +524,43 @@ - +
+ + Showing 1 / 1 events +
+ +
+
+ + +
+
+ + + +
+
+
-
+
[00:00:01] [INIT] Panopti Pre-Execution Sentinel Hypervisor initialized.
+
@@ -420,14 +594,96 @@ runIntercept(); } + let activeFilter = 'ALL'; + let searchQuery = ''; + + function setLogFilter(filter) { + activeFilter = filter; + document.querySelectorAll('.filter-btn').forEach(btn => { + if (btn.dataset.filter === filter) { + btn.classList.add('active'); + } else { + btn.classList.remove('active'); + } + }); + applyLogFilters(); + } + + function handleSearchInput(value) { + searchQuery = (value || '').trim().toLowerCase(); + const clearBtn = document.getElementById('searchClearBtn'); + if (clearBtn) { + clearBtn.style.display = searchQuery.length > 0 ? 'block' : 'none'; + } + applyLogFilters(); + } + + function clearSearch() { + const input = document.getElementById('logSearchInput'); + if (input) { + input.value = ''; + handleSearchInput(''); + input.focus(); + } + } + + function applyLogFilters() { + const logContainer = document.getElementById('consoleLog'); + const lines = logContainer.querySelectorAll('.log-line'); + let visibleCount = 0; + const totalCount = lines.length; + + lines.forEach(line => { + const isVeto = line.dataset.veto === 'true'; + const tag = line.dataset.tag || ''; + const text = line.textContent.toLowerCase(); + + let matchesFilter = true; + if (activeFilter === 'VETO') { + matchesFilter = isVeto || tag === 'VETO'; + } else if (activeFilter === 'ALLOW') { + matchesFilter = tag === 'ALLOW' || (!isVeto && tag !== 'INIT'); + } + + const matchesSearch = !searchQuery || text.includes(searchQuery); + + if (matchesFilter && matchesSearch) { + line.style.display = 'flex'; + visibleCount++; + } else { + line.style.display = 'none'; + } + }); + + const emptyMsg = document.getElementById('emptyLogMsg'); + if (emptyMsg) { + emptyMsg.style.display = visibleCount === 0 ? 'block' : 'none'; + } + + const counter = document.getElementById('logCounter'); + if (counter) { + counter.textContent = `Showing ${visibleCount} / ${totalCount} events`; + } + } + function addLog(tag, msg, isVeto) { const log = document.getElementById('consoleLog'); + const emptyMsg = document.getElementById('emptyLogMsg'); const now = new Date().toTimeString().split(' ')[0]; const tagClass = isVeto ? 'log-tag-veto' : (tag === 'ALLOW' ? 'log-tag-allow' : 'log-tag-info'); const line = document.createElement('div'); line.className = 'log-line'; + line.dataset.tag = tag; + line.dataset.veto = isVeto ? 'true' : 'false'; line.innerHTML = `[${now}] [${tag}] ${msg}`; - log.appendChild(line); + + if (emptyMsg && emptyMsg.parentNode === log) { + log.insertBefore(line, emptyMsg); + } else { + log.appendChild(line); + } + + applyLogFilters(); log.scrollTop = log.scrollHeight; } @@ -527,6 +783,7 @@ window.onload = () => { runIntercept(); + applyLogFilters(); }; window.onresize = () => drawGraph(true);