200 lines
7.1 KiB
JavaScript
200 lines
7.1 KiB
JavaScript
// 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);
|
|
}
|