Agent Police Department v1.0.0 MVP

This commit is contained in:
BunBun Builder
2026-06-14 13:18:56 +00:00
commit 672e9ef17c
30 changed files with 7619 additions and 0 deletions
+199
View File
@@ -0,0 +1,199 @@
// popup.js — Dashboard UI for Agent Police Department
document.addEventListener('DOMContentLoaded', async () => {
await loadStats();
await loadViolations();
setupTabs();
setupSearch();
setupPolicyEditor();
setupFooter();
});
async function loadStats() {
try {
const response = await chrome.runtime.sendMessage({ type: 'GET_STATS' });
if (!response?.success) return;
const stats = response.stats;
document.getElementById('risk-score').textContent = stats.riskScore ?? 0;
const statusEl = document.getElementById('compliance-status');
statusEl.textContent = stats.complianceStatus || 'unknown';
statusEl.className = 'stat-badge ' + (stats.complianceStatus || '');
document.getElementById('active-count').textContent = stats.activeCount ?? 0;
document.getElementById('violation-count').textContent = stats.totalViolations ?? 0;
document.getElementById('event-count').textContent = stats.recentEvents ?? 0;
} catch (err) {
console.error('Stats load error:', err);
}
}
async function loadViolations() {
try {
const { violations = [] } = await chrome.storage.local.get(['violations']);
renderList(violations.slice(0, 50).reverse(), 'violations-list', formatViolation);
} catch (err) {
console.error('Violations load error:', err);
}
}
async function loadActivity() {
try {
const response = await chrome.runtime.sendMessage({ type: 'GET_AUDIT_LOG', limit: 50 });
if (!response?.success) return;
renderList(response.log || [], 'activity-list', formatEvent);
} catch (err) {
console.error('Activity load error:', err);
}
}
function renderList(items, containerId, formatter) {
const container = document.getElementById(containerId);
if (!container) return;
if (items.length === 0) {
container.innerHTML = '<div class="empty">No items found</div>';
return;
}
container.innerHTML = items.map(formatter).join('');
}
function formatViolation(v) {
const time = new Date(v.timestamp).toLocaleTimeString();
return `
<div class="item-card severity-${v.severity}">
<div class="item-header">
<span class="item-title">${v.detector.replace(/_/g, ' ')}</span>
<span class="item-badge severity-${v.severity}">${v.severity}</span>
</div>
<div class="item-meta">${time}${v.rule_triggered || 'system'}</div>
<div class="item-desc">${v.message}</div>
</div>
`;
}
function formatEvent(e) {
const time = new Date(e.timestamp).toLocaleTimeString();
return `
<div class="item-card severity-${e.severity || 'info'}">
<div class="item-header">
<span class="item-title">${e.type} ${e.tool ? '(' + e.tool + ')' : ''}</span>
<span class="item-badge severity-${e.severity || 'info'}">${e.severity || 'info'}</span>
</div>
<div class="item-meta">${time}${e.agent || 'unknown'}${e.url || 'unknown'}</div>
<div class="item-desc">${e.target || e.context || 'No details'}</div>
</div>
`;
}
function setupTabs() {
const tabs = document.querySelectorAll('.tab-btn');
tabs.forEach(tab => {
tab.addEventListener('click', async () => {
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
const tabName = tab.dataset.tab;
document.querySelectorAll('.tab-content').forEach(c => c.classList.add('hidden'));
document.getElementById(tabName + '-tab').classList.remove('hidden');
if (tabName === 'activity') await loadActivity();
if (tabName === 'policy') await loadPolicyUI();
});
});
}
function setupSearch() {
const vSearch = document.getElementById('violation-search');
if (vSearch) {
vSearch.addEventListener('input', async (e) => {
const q = e.target.value.toLowerCase();
const { violations = [] } = await chrome.storage.local.get(['violations']);
const filtered = violations.filter(v =>
v.detector.includes(q) || v.message.toLowerCase().includes(q) || v.severity.includes(q)
);
renderList(filtered.slice(0, 50).reverse(), 'violations-list', formatViolation);
});
}
const aSearch = document.getElementById('activity-search');
if (aSearch) {
aSearch.addEventListener('input', async (e) => {
const q = e.target.value.toLowerCase();
const response = await chrome.runtime.sendMessage({ type: 'GET_AUDIT_LOG', limit: 100 });
if (!response?.success) return;
const filtered = (response.log || []).filter(item =>
(item.agent || '').toLowerCase().includes(q) ||
(item.type || '').toLowerCase().includes(q) ||
(item.tool || '').toLowerCase().includes(q) ||
(item.target || '').toLowerCase().includes(q)
);
renderList(filtered, 'activity-list', formatEvent);
});
}
}
async function loadPolicyUI() {
const { policy } = await chrome.storage.local.get(['policy']);
if (!policy) return;
const container = document.getElementById('policy-rules');
if (!container) return;
container.innerHTML = policy.rules.map(rule => `
<div class="rule-row">
<div>
<div class="rule-name">${rule.id}</div>
<div class="rule-type">${rule.type}</div>
</div>
<label class="toggle-switch">
<input type="checkbox" data-rule-id="${rule.id}" ${rule.enabled ? 'checked' : ''}>
<span class="toggle-slider"></span>
</label>
</div>
`).join('');
}
function setupPolicyEditor() {
document.getElementById('save-policy')?.addEventListener('click', async () => {
const { policy } = await chrome.storage.local.get(['policy']);
if (!policy) return;
const toggles = document.querySelectorAll('#policy-rules input[type="checkbox"]');
toggles.forEach(toggle => {
const rule = policy.rules.find(r => r.id === toggle.dataset.ruleId);
if (rule) rule.enabled = toggle.checked;
});
await chrome.storage.local.set({ policy });
showStatus('Policy saved');
});
document.getElementById('reset-policy')?.addEventListener('click', async () => {
await chrome.storage.local.remove(['policy']);
showStatus('Policy reset to default');
await loadPolicyUI();
});
}
function setupFooter() {
document.getElementById('export-btn')?.addEventListener('click', async () => {
const response = await chrome.runtime.sendMessage({ type: 'EXPORT_AUDIT' });
if (!response?.success) return;
const blob = new Blob([JSON.stringify(response.data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `agent-police-audit-${new Date().toISOString().slice(0, 10)}.json`;
a.click();
URL.revokeObjectURL(url);
showStatus('Audit log exported');
});
document.getElementById('verify-btn')?.addEventListener('click', async () => {
const { verifyIntegrity } = await import('./lib/audit.js');
const result = await verifyIntegrity();
showStatus(result.valid ? 'Integrity verified ✓' : 'Integrity check failed ✗');
});
}
function showStatus(msg) {
const el = document.createElement('div');
el.className = 'status-message';
el.textContent = msg;
document.body.appendChild(el);
setTimeout(() => el.remove(), 2000);
}