Agent Police Department v1.0.0 MVP
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user