Add plugin manager scan summary
This commit is contained in:
parent
67468e92f7
commit
d3dfa2d652
|
|
@ -73,6 +73,21 @@ test.describe('E: Plugin Manager layout', () => {
|
||||||
expect(buttonStyle.borderRadius).toBe('6px');
|
expect(buttonStyle.borderRadius).toBe('6px');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('plugin manager summarizes plugin health and elevated permissions before the list', async ({ page }) => {
|
||||||
|
await openPluginManager(page);
|
||||||
|
|
||||||
|
const health = page.locator('[data-plugin-manager-summary="health"]');
|
||||||
|
await expect(health).toBeVisible();
|
||||||
|
await expect(health.locator('[data-plugin-status-summary="loaded"]')).toContainText('7');
|
||||||
|
await expect(health.locator('[data-plugin-status-summary="failed"]')).toContainText('0');
|
||||||
|
await expect(health.locator('[data-plugin-status-summary="disabled"]')).toContainText('0');
|
||||||
|
|
||||||
|
const risk = page.locator('[data-plugin-manager-summary="risk"]');
|
||||||
|
await expect(risk).toBeVisible();
|
||||||
|
await expect(risk.locator('[data-plugin-risk-summary="elevated-permissions"]')).toContainText('4');
|
||||||
|
await expect(risk).toContainText('elevated permissions');
|
||||||
|
});
|
||||||
|
|
||||||
test('workspace selection keeps exactly one active node', async ({ page }) => {
|
test('workspace selection keeps exactly one active node', async ({ page }) => {
|
||||||
const selected = page.locator('.wt-node.selected .wt-label');
|
const selected = page.locator('.wt-node.selected .wt-label');
|
||||||
await expect(selected).toHaveCount(1);
|
await expect(selected).toHaveCount(1);
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,25 @@
|
||||||
$: totalPlugins = plugins.length;
|
$: totalPlugins = plugins.length;
|
||||||
$: totalCaps = capabilities.length;
|
$: totalCaps = capabilities.length;
|
||||||
$: totalPerms = permissions.length;
|
$: totalPerms = permissions.length;
|
||||||
|
$: statusSummary = computeStatusSummary(plugins);
|
||||||
|
$: elevatedPermissionPluginCount = computeElevatedPermissionPluginCount(plugins, permissions);
|
||||||
|
|
||||||
|
function computeStatusSummary(pluginRows) {
|
||||||
|
return pluginRows.reduce((summary, plugin) => {
|
||||||
|
const status = plugin?.status || 'unknown';
|
||||||
|
if (status === 'loaded') summary.loaded += 1;
|
||||||
|
else if (status === 'degraded') summary.degraded += 1;
|
||||||
|
else if (status === 'failed' || status === 'incompatible' || status === 'missing-required-capability') summary.failed += 1;
|
||||||
|
else if (status === 'disabled' || plugin?.enabled === false) summary.disabled += 1;
|
||||||
|
else summary.other += 1;
|
||||||
|
return summary;
|
||||||
|
}, { loaded: 0, degraded: 0, failed: 0, disabled: 0, other: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeElevatedPermissionPluginCount(pluginRows, permissionRows) {
|
||||||
|
const dangerous = new Set((permissionRows || []).filter(permission => permission.dangerous).map(permission => permission.name));
|
||||||
|
return (pluginRows || []).filter(plugin => (plugin?.manifest?.permissions || []).some(permission => dangerous.has(permission))).length;
|
||||||
|
}
|
||||||
|
|
||||||
function closeSettings() {
|
function closeSettings() {
|
||||||
settingsPanel = null;
|
settingsPanel = null;
|
||||||
|
|
@ -280,6 +299,25 @@
|
||||||
<span class="badge">{totalPerms} permissions known</span>
|
<span class="badge">{totalPerms} permissions known</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="scan-summary" aria-label="Plugin scan summary">
|
||||||
|
<section class="scan-card" data-plugin-manager-summary="health">
|
||||||
|
<div class="scan-card-title">Plugin Health</div>
|
||||||
|
<div class="scan-metrics">
|
||||||
|
<span data-plugin-status-summary="loaded"><strong>{statusSummary.loaded}</strong> loaded</span>
|
||||||
|
<span data-plugin-status-summary="degraded"><strong>{statusSummary.degraded}</strong> degraded</span>
|
||||||
|
<span data-plugin-status-summary="failed"><strong>{statusSummary.failed}</strong> failed</span>
|
||||||
|
<span data-plugin-status-summary="disabled"><strong>{statusSummary.disabled}</strong> disabled</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="scan-card" data-plugin-manager-summary="risk">
|
||||||
|
<div class="scan-card-title">Permission Risk</div>
|
||||||
|
<div class="scan-metrics">
|
||||||
|
<span data-plugin-risk-summary="elevated-permissions"><strong>{elevatedPermissionPluginCount}</strong> plugins request elevated permissions</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if plugins.length === 0 && missingInstalled.length === 0}
|
{#if plugins.length === 0 && missingInstalled.length === 0}
|
||||||
<div class="empty">
|
<div class="empty">
|
||||||
<div class="empty-icon">
|
<div class="empty-icon">
|
||||||
|
|
@ -471,6 +509,47 @@
|
||||||
background: #16213e; padding: 0.25rem 0.75rem; border-radius: 12px;
|
background: #16213e; padding: 0.25rem 0.75rem; border-radius: 12px;
|
||||||
font-size: 0.8rem; color: #a0a0b8; border: 1px solid #0f3460;
|
font-size: 0.8rem; color: #a0a0b8; border: 1px solid #0f3460;
|
||||||
}
|
}
|
||||||
|
.scan-summary {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.scan-card {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #0f3460;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #121a2c;
|
||||||
|
}
|
||||||
|
.scan-card-title {
|
||||||
|
color: #e0e0f0;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.scan-metrics {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.45rem;
|
||||||
|
}
|
||||||
|
.scan-metrics span {
|
||||||
|
min-height: 1.55rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
padding: 0.18rem 0.5rem;
|
||||||
|
border: 1px solid rgba(78, 204, 163, 0.16);
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #a0a0b8;
|
||||||
|
background: #101626;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
}
|
||||||
|
.scan-metrics strong {
|
||||||
|
color: #f4f7fb;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
.empty {
|
.empty {
|
||||||
padding: 2rem; text-align: center; color: #a0a0b8;
|
padding: 2rem; text-align: center; color: #a0a0b8;
|
||||||
background: #16213e; border-radius: 8px; border: 1px dashed #0f3460;
|
background: #16213e; border-radius: 8px; border: 1px dashed #0f3460;
|
||||||
|
|
@ -542,6 +621,10 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scan-summary {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
width: min(880px, calc(100vw - 2rem));
|
width: min(880px, calc(100vw - 2rem));
|
||||||
height: min(680px, calc(100vh - 2rem));
|
height: min(680px, calc(100vh - 2rem));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue