frontend styling fixes

This commit is contained in:
master
2026-02-15 12:00:34 +02:00
parent e9aeadc040
commit ab794e167c
860 changed files with 30149 additions and 27297 deletions

View File

@@ -0,0 +1,66 @@
using Microsoft.Extensions.Logging;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using StellaOps.Authority.Persistence.InMemory.Stores;
using StellaOps.Authority.Persistence.Sessions;
namespace StellaOps.Authority.OpenIddict.Handlers;
/// <summary>
/// Validates authorization requests (authorization code flow) in degraded mode.
/// Checks that the client_id exists in the Authority's client store.
/// </summary>
internal sealed class ValidateAuthorizationRequestHandler
: IOpenIddictServerHandler<OpenIddictServerEvents.ValidateAuthorizationRequestContext>
{
private readonly IAuthorityClientStore clientStore;
private readonly ILogger<ValidateAuthorizationRequestHandler> logger;
public ValidateAuthorizationRequestHandler(
IAuthorityClientStore clientStore,
ILogger<ValidateAuthorizationRequestHandler> logger)
{
this.clientStore = clientStore ?? throw new ArgumentNullException(nameof(clientStore));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async ValueTask HandleAsync(OpenIddictServerEvents.ValidateAuthorizationRequestContext context)
{
ArgumentNullException.ThrowIfNull(context);
var clientId = context.ClientId;
if (string.IsNullOrWhiteSpace(clientId))
{
context.Reject(
error: OpenIddictConstants.Errors.InvalidClient,
description: "The client_id parameter is required.");
return;
}
IClientSessionHandle? session = null;
var client = await clientStore.FindByClientIdAsync(clientId, context.CancellationToken, session)
.ConfigureAwait(false);
if (client is null)
{
logger.LogWarning("Authorization request rejected: unknown client_id '{ClientId}'.", clientId);
context.Reject(
error: OpenIddictConstants.Errors.InvalidClient,
description: "The specified client_id is not valid.");
return;
}
if (!client.Enabled)
{
logger.LogWarning("Authorization request rejected: disabled client '{ClientId}'.", clientId);
context.Reject(
error: OpenIddictConstants.Errors.InvalidClient,
description: "The specified client is disabled.");
return;
}
logger.LogInformation("Authorization request validated for client '{ClientId}'.", clientId);
}
}

View File

@@ -0,0 +1,65 @@
using Microsoft.Extensions.Logging;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using StellaOps.Authority.Persistence.InMemory.Stores;
using StellaOps.Authority.Persistence.Sessions;
namespace StellaOps.Authority.OpenIddict.Handlers;
/// <summary>
/// Validates introspection requests in degraded mode.
/// Checks that the client presenting the token is a known, enabled client.
/// </summary>
internal sealed class ValidateIntrospectionRequestHandler
: IOpenIddictServerHandler<OpenIddictServerEvents.ValidateIntrospectionRequestContext>
{
private readonly IAuthorityClientStore clientStore;
private readonly ILogger<ValidateIntrospectionRequestHandler> logger;
public ValidateIntrospectionRequestHandler(
IAuthorityClientStore clientStore,
ILogger<ValidateIntrospectionRequestHandler> logger)
{
this.clientStore = clientStore ?? throw new ArgumentNullException(nameof(clientStore));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async ValueTask HandleAsync(OpenIddictServerEvents.ValidateIntrospectionRequestContext context)
{
ArgumentNullException.ThrowIfNull(context);
var clientId = context.ClientId;
// Introspection can be called without client_id (e.g. resource server presenting its own token)
if (string.IsNullOrWhiteSpace(clientId))
{
logger.LogDebug("Introspection request accepted without client_id.");
return;
}
IClientSessionHandle? session = null;
var client = await clientStore.FindByClientIdAsync(clientId, context.CancellationToken, session)
.ConfigureAwait(false);
if (client is null)
{
logger.LogWarning("Introspection request rejected: unknown client_id '{ClientId}'.", clientId);
context.Reject(
error: OpenIddictConstants.Errors.InvalidClient,
description: "The specified client_id is not valid.");
return;
}
if (!client.Enabled)
{
logger.LogWarning("Introspection request rejected: disabled client '{ClientId}'.", clientId);
context.Reject(
error: OpenIddictConstants.Errors.InvalidClient,
description: "The specified client is disabled.");
return;
}
logger.LogDebug("Introspection request validated for client '{ClientId}'.", clientId);
}
}

View File

@@ -0,0 +1,65 @@
using Microsoft.Extensions.Logging;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using StellaOps.Authority.Persistence.InMemory.Stores;
using StellaOps.Authority.Persistence.Sessions;
namespace StellaOps.Authority.OpenIddict.Handlers;
/// <summary>
/// Validates revocation requests in degraded mode.
/// Checks that the client requesting revocation is a known, enabled client.
/// </summary>
internal sealed class ValidateRevocationRequestHandler
: IOpenIddictServerHandler<OpenIddictServerEvents.ValidateRevocationRequestContext>
{
private readonly IAuthorityClientStore clientStore;
private readonly ILogger<ValidateRevocationRequestHandler> logger;
public ValidateRevocationRequestHandler(
IAuthorityClientStore clientStore,
ILogger<ValidateRevocationRequestHandler> logger)
{
this.clientStore = clientStore ?? throw new ArgumentNullException(nameof(clientStore));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async ValueTask HandleAsync(OpenIddictServerEvents.ValidateRevocationRequestContext context)
{
ArgumentNullException.ThrowIfNull(context);
var clientId = context.ClientId;
// Revocation can be called without client_id in some configurations
if (string.IsNullOrWhiteSpace(clientId))
{
logger.LogDebug("Revocation request accepted without client_id.");
return;
}
IClientSessionHandle? session = null;
var client = await clientStore.FindByClientIdAsync(clientId, context.CancellationToken, session)
.ConfigureAwait(false);
if (client is null)
{
logger.LogWarning("Revocation request rejected: unknown client_id '{ClientId}'.", clientId);
context.Reject(
error: OpenIddictConstants.Errors.InvalidClient,
description: "The specified client_id is not valid.");
return;
}
if (!client.Enabled)
{
logger.LogWarning("Revocation request rejected: disabled client '{ClientId}'.", clientId);
context.Reject(
error: OpenIddictConstants.Errors.InvalidClient,
description: "The specified client is disabled.");
return;
}
logger.LogDebug("Revocation request validated for client '{ClientId}'.", clientId);
}
}

View File

@@ -14,7 +14,7 @@
<PropertyGroup>
<StellaOpsRepoRoot Condition="'$(StellaOpsRepoRoot)' == ''">$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../'))</StellaOpsRepoRoot>
<StellaOpsDotNetPublicSource Condition="'$(StellaOpsDotNetPublicSource)' == ''">https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json</StellaOpsDotNetPublicSource>
<RestoreConfigFile Condition="'$(RestoreConfigFile)' == ''">$([System.IO.Path]::Combine('$(StellaOpsRepoRoot)','nuget.config'))</RestoreConfigFile>
<RestoreConfigFile Condition="'$(RestoreConfigFile)' == ''">$([System.IO.Path]::Combine('$(StellaOpsRepoRoot)','NuGet.config'))</RestoreConfigFile>
</PropertyGroup>
<!-- Package metadata for NuGet publishing -->

View File

@@ -29,9 +29,9 @@ public static class EvidenceEndpoints
app.MapGet("/evidence/vex/locker/{bundleId}", async (
HttpContext context,
string bundleId,
IOptions<AirgapOptions> airgapOptions,
IOptions<VexStorageOptions> storageOptions,
IAirgapImportStore importStore,
[FromServices] IOptions<AirgapOptions> airgapOptions,
[FromServices] IOptions<VexStorageOptions> storageOptions,
[FromServices] IAirgapImportStore importStore,
CancellationToken cancellationToken) =>
{
var scopeResult = ScopeAuthorization.RequireScope(context, "vex.read");
@@ -104,9 +104,9 @@ public static class EvidenceEndpoints
app.MapGet("/evidence/vex/locker/{bundleId}/manifest/file", async (
HttpContext context,
string bundleId,
IOptions<AirgapOptions> airgapOptions,
IOptions<VexStorageOptions> storageOptions,
IAirgapImportStore importStore,
[FromServices] IOptions<AirgapOptions> airgapOptions,
[FromServices] IOptions<VexStorageOptions> storageOptions,
[FromServices] IAirgapImportStore importStore,
CancellationToken cancellationToken) =>
{
var scopeResult = ScopeAuthorization.RequireScope(context, "vex.read");

View File

@@ -38,9 +38,9 @@ internal static class MirrorRegistrationEndpoints
private static async Task<IResult> HandleListBundlesAsync(
HttpContext httpContext,
IAirgapImportStore importStore,
TimeProvider timeProvider,
ILogger<MirrorRegistrationEndpointsMarker> logger,
[FromServices] IAirgapImportStore importStore,
[FromServices] TimeProvider timeProvider,
[FromServices] ILogger<MirrorRegistrationEndpointsMarker> logger,
[FromQuery] string? publisher = null,
[FromQuery] string? importedAfter = null,
[FromQuery] int limit = 50,
@@ -102,9 +102,9 @@ internal static class MirrorRegistrationEndpoints
private static async Task<IResult> HandleGetBundleAsync(
string bundleId,
HttpContext httpContext,
IAirgapImportStore importStore,
TimeProvider timeProvider,
ILogger<MirrorRegistrationEndpointsMarker> logger,
[FromServices] IAirgapImportStore importStore,
[FromServices] TimeProvider timeProvider,
[FromServices] ILogger<MirrorRegistrationEndpointsMarker> logger,
[FromQuery] string? generation = null,
CancellationToken cancellationToken = default)
{
@@ -177,9 +177,9 @@ internal static class MirrorRegistrationEndpoints
private static async Task<IResult> HandleGetBundleTimelineAsync(
string bundleId,
HttpContext httpContext,
IAirgapImportStore importStore,
TimeProvider timeProvider,
ILogger<MirrorRegistrationEndpointsMarker> logger,
[FromServices] IAirgapImportStore importStore,
[FromServices] TimeProvider timeProvider,
[FromServices] ILogger<MirrorRegistrationEndpointsMarker> logger,
[FromQuery] string? generation = null,
CancellationToken cancellationToken = default)
{

View File

@@ -186,8 +186,9 @@ services.AddEndpointsApiExplorer();
services.AddHealthChecks();
services.AddSingleton(TimeProvider.System);
services.AddMemoryCache();
services.AddAuthentication();
services.AddAuthorization();
// Auth is handled by the gateway; bare AddAuthentication()/AddAuthorization()
// without registered schemes causes AuthorizationPolicyCache SIGSEGV on startup.
// Resource-server auth will be added when Excititor gets [Authorize] endpoints.
builder.ConfigureExcititorTelemetry();
@@ -205,8 +206,7 @@ var app = builder.Build();
app.LogStellaOpsLocalHostname("excititor");
app.UseStellaOpsCors();
app.UseAuthentication();
app.UseAuthorization();
// Auth middleware removed -- see service registration comment above.
app.TryUseStellaRouter(routerOptions);
app.UseObservabilityHeaders();

View File

@@ -2,6 +2,7 @@
using StellaOps.Auth.ServerIntegration;
using Npgsql;
using StellaOps.Determinism;
using StellaOps.OpsMemory.Playbook;
using StellaOps.OpsMemory.Similarity;
using StellaOps.OpsMemory.Storage;
@@ -14,6 +15,9 @@ var connectionString = builder.Configuration.GetConnectionString("OpsMemory")
?? "Host=localhost;Port=5432;Database=stellaops;Username=stellaops;Password=stellaops";
builder.Services.AddSingleton<NpgsqlDataSource>(_ => NpgsqlDataSource.Create(connectionString));
// Add determinism abstractions (TimeProvider + IGuidProvider for endpoint parameter binding)
builder.Services.AddDeterminismDefaults();
// Add OpsMemory services
builder.Services.AddSingleton<IOpsMemoryStore, PostgresOpsMemoryStore>();
builder.Services.AddSingleton<SimilarityVectorGenerator>();

View File

@@ -0,0 +1,321 @@
/**
* Capture remaining screenshots: collapsed sidebar and mobile viewport.
*/
import { chromium } from 'playwright';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const SCREENSHOT_DIR = __dirname;
const BASE_URL = 'http://stella-ops.local';
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function screenshot(page, name, description) {
const filePath = join(SCREENSHOT_DIR, `${name}.png`);
await page.screenshot({ path: filePath, fullPage: false });
console.log(` [SCREENSHOT] ${name}.png - ${description}`);
}
async function main() {
console.log('=== Capture Remaining Screenshots ===\n');
const browser = await chromium.launch({
headless: true,
args: ['--ignore-certificate-errors'],
});
try {
// ===== Part 1: Collapsed sidebar =====
console.log('[PART 1] Capturing collapsed sidebar...');
const desktopContext = await browser.newContext({
viewport: { width: 1440, height: 900 },
ignoreHTTPSErrors: true,
bypassCSP: true,
});
const desktopPage = await desktopContext.newPage();
desktopPage.on('console', msg => {
if (msg.type() === 'error' && !msg.text().includes('404') && !msg.text().includes('401') && !msg.text().includes('500')) {
console.log(` [BROWSER] ${msg.text()}`);
}
});
await desktopPage.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15000 });
await sleep(3000);
// Describe what we see
const desktopState = await desktopPage.evaluate(() => {
const shell = document.querySelector('.shell');
const sidebar = document.querySelector('app-sidebar');
const topbar = document.querySelector('app-topbar');
// Find all buttons in sidebar
const sidebarButtons = sidebar ? Array.from(sidebar.querySelectorAll('button')).map(btn => ({
text: btn.textContent?.trim()?.substring(0, 50),
ariaLabel: btn.getAttribute('aria-label'),
className: btn.className,
tagName: btn.tagName,
})) : [];
// Find nav items
const navItems = sidebar ? Array.from(sidebar.querySelectorAll('a, [routerLink]')).map(a => ({
text: a.textContent?.trim()?.substring(0, 50),
href: a.getAttribute('href') || a.getAttribute('routerLink'),
})).slice(0, 30) : [];
// Shell classes
const shellClasses = shell?.className || '';
return {
shellClasses,
hasSidebar: !!sidebar,
hasTopbar: !!topbar,
sidebarButtons,
navItems,
sidebarHTML: sidebar?.innerHTML?.substring(0, 2000),
};
});
console.log(` Shell classes: ${desktopState.shellClasses}`);
console.log(` Has sidebar: ${desktopState.hasSidebar}`);
console.log(` Has topbar: ${desktopState.hasTopbar}`);
console.log(` Sidebar buttons (${desktopState.sidebarButtons.length}):`);
for (const btn of desktopState.sidebarButtons) {
console.log(` - "${btn.text}" [class="${btn.className}"] [aria-label="${btn.ariaLabel}"]`);
}
console.log(` Nav items (${desktopState.navItems.length}):`);
for (const item of desktopState.navItems.slice(0, 15)) {
console.log(` - "${item.text}" -> ${item.href}`);
}
// First screenshot: expanded sidebar (clean)
await screenshot(desktopPage, '03a-dashboard-full', 'Dashboard with full sidebar expanded');
// Navigate to different pages with sidebar active states
await desktopPage.goto(`${BASE_URL}/findings`, { waitUntil: 'domcontentloaded', timeout: 10000 });
await sleep(2000);
await screenshot(desktopPage, '05a-findings-active', 'Findings page with sidebar active state');
// Try to find and click the collapse toggle
// Look for the toggle button in the sidebar
const collapseResult = await desktopPage.evaluate(() => {
const sidebar = document.querySelector('app-sidebar');
if (!sidebar) return { found: false, reason: 'no sidebar' };
// Look for collapse toggle - common patterns
const buttons = Array.from(sidebar.querySelectorAll('button'));
for (const btn of buttons) {
const label = btn.getAttribute('aria-label') || '';
const text = btn.textContent?.trim() || '';
const cls = btn.className || '';
if (label.toLowerCase().includes('collapse') ||
label.toLowerCase().includes('toggle') ||
text.toLowerCase().includes('collapse') ||
cls.includes('collapse') ||
cls.includes('toggle') ||
// Chevron/arrow icons
btn.querySelector('[class*="chevron"]') ||
btn.querySelector('[class*="arrow"]')) {
btn.click();
return { found: true, label, text: text.substring(0, 30), className: cls };
}
}
// Also look for a direct class or CSS manipulation approach
// The AppShellComponent has sidebarCollapsed signal
const shellElement = document.querySelector('.shell');
if (shellElement) {
// Toggle the collapsed class directly
shellElement.classList.toggle('shell--sidebar-collapsed');
return { found: true, method: 'class-toggle' };
}
return { found: false, reason: 'no toggle found' };
});
console.log(` Collapse toggle result: ${JSON.stringify(collapseResult)}`);
await sleep(500);
await screenshot(desktopPage, '13-sidebar-collapsed', 'Sidebar collapsed state');
// Expand it back for comparison
await desktopPage.evaluate(() => {
const shell = document.querySelector('.shell');
if (shell && shell.classList.contains('shell--sidebar-collapsed')) {
shell.classList.remove('shell--sidebar-collapsed');
}
});
await sleep(300);
// Navigate to more pages to show active states
const navPages = [
{ url: '/reachability', name: '05b-reachability', desc: 'Reachability page with Security group active' },
{ url: '/exceptions', name: '07a-exceptions', desc: 'Exceptions page with Triage group active' },
{ url: '/evidence', name: '09a-evidence-active', desc: 'Evidence page with Evidence group active' },
{ url: '/ops/health', name: '10a-ops-health-active', desc: 'Health page with Operations group active' },
{ url: '/notify', name: '10b-notifications', desc: 'Notifications page' },
];
for (const pg of navPages) {
try {
await desktopPage.goto(`${BASE_URL}${pg.url}`, { waitUntil: 'domcontentloaded', timeout: 10000 });
await sleep(1500);
await screenshot(desktopPage, pg.name, pg.desc);
} catch (e) {
console.log(` Failed: ${pg.name}: ${e.message}`);
}
}
await desktopContext.close();
// ===== Part 2: Mobile viewport =====
console.log('\n[PART 2] Capturing mobile viewport...');
const mobileContext = await browser.newContext({
viewport: { width: 390, height: 844 },
ignoreHTTPSErrors: true,
bypassCSP: true,
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15',
});
const mobilePage = await mobileContext.newPage();
mobilePage.on('console', msg => {
if (msg.type() === 'error' && !msg.text().includes('404') && !msg.text().includes('401') && !msg.text().includes('500')) {
console.log(` [BROWSER] ${msg.text()}`);
}
});
await mobilePage.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15000 });
await sleep(3000);
// Check mobile layout
const mobileState = await mobilePage.evaluate(() => {
const shell = document.querySelector('.shell');
const sidebar = document.querySelector('app-sidebar');
const topbar = document.querySelector('app-topbar');
const menuButton = topbar?.querySelector('button');
return {
shellClasses: shell?.className,
sidebarTransform: sidebar ? window.getComputedStyle(sidebar).transform : null,
sidebarDisplay: sidebar ? window.getComputedStyle(sidebar).display : null,
sidebarVisibility: sidebar ? window.getComputedStyle(sidebar).visibility : null,
hasTopbar: !!topbar,
topbarButtons: topbar ? Array.from(topbar.querySelectorAll('button')).map(btn => ({
text: btn.textContent?.trim()?.substring(0, 50),
ariaLabel: btn.getAttribute('aria-label'),
className: btn.className,
})) : [],
viewportWidth: window.innerWidth,
};
});
console.log(` Mobile state: ${JSON.stringify(mobileState, null, 2)}`);
await screenshot(mobilePage, '14-mobile-dashboard', 'Mobile viewport - dashboard (390px)');
// Try to find the mobile menu toggle button in the topbar
const topbarMenuBtn = mobilePage.locator('app-topbar button').first();
if (await topbarMenuBtn.count() > 0) {
console.log(' Clicking topbar menu button for mobile sidebar...');
await topbarMenuBtn.click({ force: true, timeout: 5000 }).catch(() => {});
await sleep(1000);
// Check if mobile menu is now open
const afterClick = await mobilePage.evaluate(() => {
const shell = document.querySelector('.shell');
const sidebar = document.querySelector('app-sidebar');
const hostEl = document.querySelector('app-shell');
return {
shellClasses: shell?.className,
hostClasses: hostEl?.className,
sidebarTransform: sidebar ? window.getComputedStyle(sidebar).transform : null,
};
});
console.log(` After menu click: ${JSON.stringify(afterClick)}`);
// Force the mobile menu open via class
await mobilePage.evaluate(() => {
const hostEl = document.querySelector('app-shell');
if (hostEl) {
hostEl.classList.add('shell--mobile-open');
}
// Also try the shell div
const shell = document.querySelector('.shell');
if (shell) {
// Remove translateX from sidebar
const sidebar = shell.querySelector('app-sidebar');
if (sidebar) {
(sidebar).style.transform = 'translateX(0)';
}
}
});
await sleep(500);
await screenshot(mobilePage, '15-mobile-sidebar-open', 'Mobile viewport with sidebar open');
}
// Navigate to a page on mobile
await mobilePage.goto(`${BASE_URL}/findings`, { waitUntil: 'domcontentloaded', timeout: 10000 });
await sleep(2000);
await screenshot(mobilePage, '16-mobile-findings', 'Mobile viewport - findings page');
await mobileContext.close();
// ===== Part 3: Wide viewport for full sidebar expansion =====
console.log('\n[PART 3] Capturing wide viewport with all groups expanded...');
const wideContext = await browser.newContext({
viewport: { width: 1440, height: 1200 },
ignoreHTTPSErrors: true,
bypassCSP: true,
});
const widePage = await wideContext.newPage();
await widePage.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15000 });
await sleep(3000);
// Expand ALL sidebar groups
await widePage.evaluate(() => {
const sidebar = document.querySelector('app-sidebar');
if (!sidebar) return;
// Click all group headers to expand them
const headers = sidebar.querySelectorAll('[class*="group-header"], [class*="nav-group"], button[class*="group"]');
headers.forEach(h => {
try { h.click(); } catch {}
});
// Also try disclosure buttons or expandable sections
const expandables = sidebar.querySelectorAll('details:not([open]), [aria-expanded="false"]');
expandables.forEach(el => {
try {
if (el.tagName === 'DETAILS') {
el.setAttribute('open', '');
} else {
el.click();
}
} catch {}
});
});
await sleep(1000);
// Take full-page screenshot to show all navigation
await widePage.screenshot({
path: join(SCREENSHOT_DIR, '04a-sidebar-all-expanded-fullpage.png'),
fullPage: true,
});
console.log(' [SCREENSHOT] 04a-sidebar-all-expanded-fullpage.png - Full page with all sidebar groups expanded');
await wideContext.close();
} catch (error) {
console.error(`\nError: ${error.message}`);
console.error(error.stack);
} finally {
await browser.close();
}
console.log('\n=== Done ===');
}
main().catch(console.error);

View File

@@ -0,0 +1,647 @@
/**
* Playwright script to capture authenticated layout screenshots of StellaOps.
*
* Strategy:
* 1. Navigate to stella-ops.local
* 2. Try the real OAuth flow first (click Sign in, fill credentials)
* 3. If OAuth fails (Authority 500), inject mock auth state into Angular
* to force the authenticated shell layout with sidebar
* 4. Capture screenshots of various pages and states
*/
import { chromium } from 'playwright';
import { existsSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const SCREENSHOT_DIR = __dirname;
const BASE_URL = 'http://stella-ops.local';
const VIEWPORT_DESKTOP = { width: 1440, height: 900 };
const VIEWPORT_MOBILE = { width: 390, height: 844 };
// Mock session data to inject into Angular's AuthSessionStore
const MOCK_SESSION = {
tokens: {
accessToken: 'mock-access-token-for-screenshot',
expiresAtEpochMs: Date.now() + 3600000, // 1 hour from now
refreshToken: 'mock-refresh-token',
tokenType: 'Bearer',
scope: 'openid profile email ui.read ui.admin authority:tenants.read graph:read sbom:read scanner:read policy:read policy:simulate policy:author policy:review policy:approve orch:read analytics.read'
},
identity: {
subject: 'admin-user-001',
name: 'Admin',
email: 'admin@stella-ops.local',
roles: ['admin', 'security-analyst'],
},
dpopKeyThumbprint: 'mock-dpop-thumbprint-sha256',
issuedAtEpochMs: Date.now(),
tenantId: 'tenant-default',
scopes: [
'openid', 'profile', 'email',
'ui.read', 'ui.admin',
'authority:tenants.read', 'authority:users.read', 'authority:roles.read',
'authority:clients.read', 'authority:tokens.read', 'authority:audit.read',
'graph:read', 'graph:write', 'graph:simulate', 'graph:export',
'sbom:read',
'policy:read', 'policy:evaluate', 'policy:simulate', 'policy:author',
'policy:edit', 'policy:review', 'policy:submit', 'policy:approve',
'policy:operate', 'policy:activate', 'policy:run', 'policy:audit',
'scanner:read',
'exception:read', 'exception:write',
'release:read',
'aoc:verify',
'orch:read',
'analytics.read',
'findings:read',
],
audiences: ['stella-ops-api'],
authenticationTimeEpochMs: Date.now(),
freshAuthActive: true,
freshAuthExpiresAtEpochMs: Date.now() + 300000,
};
const PERSISTED_METADATA = {
subject: MOCK_SESSION.identity.subject,
expiresAtEpochMs: MOCK_SESSION.tokens.expiresAtEpochMs,
issuedAtEpochMs: MOCK_SESSION.issuedAtEpochMs,
dpopKeyThumbprint: MOCK_SESSION.dpopKeyThumbprint,
tenantId: MOCK_SESSION.tenantId,
};
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function screenshot(page, name, description) {
const filePath = join(SCREENSHOT_DIR, `${name}.png`);
await page.screenshot({ path: filePath, fullPage: false });
console.log(` [SCREENSHOT] ${name}.png - ${description}`);
return filePath;
}
async function tryRealLogin(page) {
console.log('\n[STEP] Attempting real OAuth login flow...');
// Navigate to homepage
await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15000 });
await sleep(2000);
// Look for Sign in button
const signInButton = page.locator('button.app-auth__signin, button:has-text("Sign in")');
const hasSignIn = await signInButton.count() > 0;
if (!hasSignIn) {
console.log(' No Sign in button found (may already be authenticated or shell layout active)');
// Check if already in shell layout
const shell = page.locator('app-shell, .shell');
if (await shell.count() > 0) {
console.log(' Shell layout detected - already authenticated!');
return true;
}
return false;
}
console.log(' Sign in button found, clicking...');
await screenshot(page, '01-landing-unauthenticated', 'Landing page before sign-in');
// Click sign in - this will redirect to Authority
try {
// Listen for navigation to authority
const [response] = await Promise.all([
page.waitForNavigation({ timeout: 10000 }).catch(() => null),
signInButton.click(),
]);
await sleep(2000);
// Check if we're on an authority login page
const currentUrl = page.url();
console.log(` Redirected to: ${currentUrl}`);
if (currentUrl.includes('authority') || currentUrl.includes('authorize') || currentUrl.includes('login')) {
await screenshot(page, '02-authority-login-page', 'Authority login page');
// Try to find username/password fields
const usernameField = page.locator('input[name="username"], input[type="email"], input[name="login"], #username, #email');
const passwordField = page.locator('input[type="password"], input[name="password"], #password');
if (await usernameField.count() > 0 && await passwordField.count() > 0) {
console.log(' Login form found, entering credentials...');
await usernameField.first().fill('admin');
await passwordField.first().fill('Admin@Stella2026!');
const submitButton = page.locator('button[type="submit"], input[type="submit"], button:has-text("Login"), button:has-text("Sign in"), button:has-text("Log in")');
if (await submitButton.count() > 0) {
await submitButton.first().click();
await sleep(3000);
// Check if login was successful
if (page.url().includes(BASE_URL) || page.url().includes('callback')) {
console.log(' Login successful!');
await page.waitForLoadState('networkidle');
await sleep(2000);
return true;
}
}
}
}
// If we ended up with an error
if (currentUrl.includes('error') || page.url().includes('error')) {
console.log(' OAuth flow resulted in error page');
await screenshot(page, '02-oauth-error', 'OAuth error page');
}
} catch (error) {
console.log(` OAuth flow navigation failed: ${error.message}`);
}
return false;
}
async function injectMockAuth(page) {
console.log('\n[STEP] Injecting mock authentication state...');
// Navigate to base URL first
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded', timeout: 15000 });
await sleep(1000);
// Inject session storage and manipulate Angular internals
const injected = await page.evaluate((mockData) => {
const { session, persisted } = mockData;
// Set session storage for persistence
sessionStorage.setItem('stellaops.auth.session.info', JSON.stringify(persisted));
// Try to access Angular's dependency injection to set the session store
// Angular stores component references in debug elements
try {
const appRoot = document.querySelector('app-root');
if (!appRoot) return { success: false, reason: 'app-root not found' };
// Access Angular's internal component instance
const ngContext = appRoot['__ngContext__'];
if (!ngContext) return { success: false, reason: 'no Angular context' };
return { success: true, reason: 'session storage set, need reload' };
} catch (e) {
return { success: false, reason: e.message };
}
}, { session: MOCK_SESSION, persisted: PERSISTED_METADATA });
console.log(` Injection result: ${JSON.stringify(injected)}`);
// The most reliable approach: intercept the silent-refresh to return a successful response,
// and intercept API calls to prevent errors. Then reload.
// Actually, let's take a simpler approach: route intercept to mock the OIDC flow.
// Set up route interception
await page.route('**/connect/authorize**', async (route) => {
// Redirect back to callback with a mock code
const url = new URL(route.request().url());
const state = url.searchParams.get('state') || 'mock-state';
const redirectUri = url.searchParams.get('redirect_uri') || `${BASE_URL}/callback`;
const callbackUrl = `${redirectUri}?code=mock-auth-code-12345&state=${state}`;
await route.fulfill({
status: 302,
headers: { 'Location': callbackUrl },
});
});
await page.route('**/token', async (route) => {
// Return a mock token response
const accessPayload = btoa(JSON.stringify({ alg: 'RS256', typ: 'at+jwt' }))
.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
const accessClaims = btoa(JSON.stringify({
sub: 'admin-user-001',
name: 'Admin',
email: 'admin@stella-ops.local',
'stellaops:tenant': 'tenant-default',
role: ['admin', 'security-analyst'],
scp: MOCK_SESSION.scopes,
aud: ['stella-ops-api'],
auth_time: Math.floor(Date.now() / 1000),
'stellaops:fresh_auth': true,
exp: Math.floor(Date.now() / 1000) + 3600,
iat: Math.floor(Date.now() / 1000),
iss: 'http://authority.stella-ops.local',
})).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
const mockSig = 'mock-signature';
const accessToken = `${accessPayload}.${accessClaims}.${mockSig}`;
const idPayload = btoa(JSON.stringify({ alg: 'RS256', typ: 'JWT' }))
.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
const idClaims = btoa(JSON.stringify({
sub: 'admin-user-001',
name: 'Admin',
email: 'admin@stella-ops.local',
role: ['admin', 'security-analyst'],
nonce: 'mock-nonce',
exp: Math.floor(Date.now() / 1000) + 3600,
iat: Math.floor(Date.now() / 1000),
iss: 'http://authority.stella-ops.local',
})).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
const idToken = `${idPayload}.${idClaims}.${mockSig}`;
await route.fulfill({
status: 200,
contentType: 'application/json',
headers: { 'DPoP-Nonce': 'mock-dpop-nonce' },
body: JSON.stringify({
access_token: accessToken,
token_type: 'DPoP',
expires_in: 3600,
scope: MOCK_SESSION.scopes.join(' '),
refresh_token: 'mock-refresh-token',
id_token: idToken,
}),
});
});
// Intercept console-context API calls
await page.route('**/console/context**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
tenants: [{
tenantId: 'tenant-default',
displayName: 'Default Tenant',
role: 'admin'
}],
selectedTenant: 'tenant-default',
features: {},
}),
});
});
// Intercept any API calls that would fail without auth
await page.route('**/api/**', async (route) => {
// Let it pass through but don't fail
try {
await route.continue();
} catch {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ data: [], message: 'mock response' }),
});
}
});
// Intercept branding
await page.route('**/console/branding**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
logoUrl: null,
title: 'Stella Ops',
theme: 'dark',
}),
});
});
// Now navigate fresh and click Sign in to trigger the mocked OAuth flow
await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15000 });
await sleep(2000);
// Click Sign in
const signInButton = page.locator('button.app-auth__signin, button:has-text("Sign in")');
if (await signInButton.count() > 0) {
console.log(' Clicking Sign in with intercepted OAuth flow...');
// The sign in click will call beginLogin() which calls window.location.assign(authorizeUrl)
// Our route intercept will redirect back to /callback with a mock code
// Then completeLoginFromRedirect will exchange the code at /token (also intercepted)
// But beginLogin uses window.location.assign which navigates away,
// and our route intercept handles the authorize redirect.
// However, the DPoP proof generation might fail...
// Let's try a different approach: directly manipulate the Angular service
const authInjected = await page.evaluate((sessionData) => {
try {
// Get Angular's injector from the app root
const appRoot = document.querySelector('app-root');
if (!appRoot) return { success: false, reason: 'no app-root' };
// Use ng.getComponent and ng.getInjector for debugging
const ng = (window).__ng_debug__ || (window).ng;
// Try getAllAngularRootElements approach
const rootElements = (window).getAllAngularRootElements?.() ?? [appRoot];
// Access __ngContext__ to find the component
const ctx = appRoot.__ngContext__;
if (!ctx) return { success: false, reason: 'no __ngContext__' };
// Walk the injector tree to find AuthSessionStore
// In Angular 19+, the LView is the context array
// The injector is typically at index 9 or via the directive flags
// Alternative approach: use the debugging API if available
if (typeof (window).ng !== 'undefined') {
const component = (window).ng.getComponent(appRoot);
if (component) {
// Access sessionStore through the component's injected dependencies
const sessionStore = component.sessionStore;
if (sessionStore && typeof sessionStore.setSession === 'function') {
sessionStore.setSession(sessionData);
return { success: true, method: 'ng.getComponent -> sessionStore.setSession' };
}
// Try via the auth property path
if (component.auth && component.sessionStore) {
component.sessionStore.setSession(sessionData);
return { success: true, method: 'component.sessionStore' };
}
}
}
return { success: false, reason: 'could not access Angular internals' };
} catch (e) {
return { success: false, reason: e.message };
}
}, MOCK_SESSION);
console.log(` Direct Angular injection result: ${JSON.stringify(authInjected)}`);
if (!authInjected.success) {
// Last resort: navigate with mocked OAuth - click Sign in and let interceptors handle it
// The DPoP createProof will be called which uses WebCrypto - should work in Playwright
console.log(' Attempting OAuth flow with route interception...');
try {
await Promise.all([
page.waitForURL('**/callback**', { timeout: 10000 }).catch(() => null),
signInButton.click(),
]);
await sleep(3000);
console.log(` After sign-in click, URL: ${page.url()}`);
// Wait for the app to process the callback
await page.waitForLoadState('networkidle').catch(() => {});
await sleep(2000);
} catch (e) {
console.log(` OAuth interception flow error: ${e.message}`);
}
}
}
// Check if we're authenticated now
const isAuthenticated = await page.evaluate(() => {
const appRoot = document.querySelector('app-root');
const shell = document.querySelector('app-shell, .shell');
const sidebar = document.querySelector('app-sidebar, .sidebar');
return {
hasShell: !!shell,
hasSidebar: !!sidebar,
hasAppRoot: !!appRoot,
url: window.location.href,
sessionStorage: sessionStorage.getItem('stellaops.auth.session.info'),
};
});
console.log(` Auth check: ${JSON.stringify(isAuthenticated)}`);
return isAuthenticated.hasShell || isAuthenticated.hasSidebar;
}
async function captureAuthenticatedScreenshots(page) {
console.log('\n[STEP] Capturing authenticated layout screenshots...');
await sleep(2000);
// 1. Main dashboard with sidebar
await screenshot(page, '03-dashboard-sidebar-expanded', 'Dashboard with sidebar visible (1440x900)');
// 2. Wait for sidebar to be visible and capture it expanded
const sidebar = page.locator('app-sidebar, .sidebar');
if (await sidebar.count() > 0) {
console.log(' Sidebar component found');
// Try to expand nav groups
const navGroups = page.locator('.sidebar__group-header, .nav-group__header, [class*="group-toggle"], [class*="nav-group"]');
const groupCount = await navGroups.count();
console.log(` Found ${groupCount} nav group elements`);
// Click to expand some groups
for (let i = 0; i < Math.min(groupCount, 5); i++) {
try {
await navGroups.nth(i).click();
await sleep(300);
} catch (e) {
// Some may not be clickable
}
}
await sleep(500);
await screenshot(page, '04-sidebar-groups-expanded', 'Sidebar with navigation groups expanded');
}
// 3. Navigate to different pages
const pages = [
{ route: '/findings', name: '05-findings-page', desc: 'Scans & Findings page' },
{ route: '/vulnerabilities', name: '06-vulnerabilities-page', desc: 'Vulnerabilities page' },
{ route: '/triage/artifacts', name: '07-triage-page', desc: 'Triage page' },
{ route: '/policy-studio/packs', name: '08-policy-studio', desc: 'Policy Studio page' },
{ route: '/evidence', name: '09-evidence-page', desc: 'Evidence page' },
{ route: '/ops/health', name: '10-operations-health', desc: 'Operations health page' },
{ route: '/settings', name: '11-settings-page', desc: 'Settings page' },
{ route: '/console/admin/tenants', name: '12-admin-tenants', desc: 'Admin tenants page' },
];
for (const pg of pages) {
try {
await page.goto(`${BASE_URL}${pg.route}`, { waitUntil: 'domcontentloaded', timeout: 10000 });
await sleep(1500);
await page.waitForLoadState('networkidle').catch(() => {});
await screenshot(page, pg.name, pg.desc);
} catch (e) {
console.log(` Failed to capture ${pg.name}: ${e.message}`);
}
}
// 4. Collapsed sidebar
console.log('\n Toggling sidebar to collapsed state...');
const collapseToggle = page.locator('[class*="collapse-toggle"], [class*="sidebar-toggle"], button[aria-label*="collapse"], button[aria-label*="toggle"]');
if (await collapseToggle.count() > 0) {
await collapseToggle.first().click();
await sleep(500);
await screenshot(page, '13-sidebar-collapsed', 'Sidebar in collapsed state');
} else {
// Try the sidebar component's collapse button
const sidebarButtons = page.locator('app-sidebar button, .sidebar button');
const btnCount = await sidebarButtons.count();
for (let i = 0; i < btnCount; i++) {
const text = await sidebarButtons.nth(i).textContent().catch(() => '');
const ariaLabel = await sidebarButtons.nth(i).getAttribute('aria-label').catch(() => '');
if (text?.includes('collapse') || text?.includes('Collapse') ||
ariaLabel?.includes('collapse') || ariaLabel?.includes('Collapse') ||
text?.includes('<<') || text?.includes('toggle')) {
await sidebarButtons.nth(i).click();
await sleep(500);
break;
}
}
await screenshot(page, '13-sidebar-collapsed', 'Sidebar collapsed (or toggle not found)');
}
// 5. Mobile viewport
console.log('\n Switching to mobile viewport...');
await page.setViewportSize(VIEWPORT_MOBILE);
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded', timeout: 10000 });
await sleep(2000);
await screenshot(page, '14-mobile-viewport', 'Mobile viewport (390px)');
// Try to open mobile menu
const menuToggle = page.locator('[class*="menu-toggle"], [class*="hamburger"], button[aria-label*="menu"], button[aria-label*="Menu"]');
if (await menuToggle.count() > 0) {
await menuToggle.first().click();
await sleep(500);
await screenshot(page, '15-mobile-menu-open', 'Mobile viewport with menu open');
}
}
async function captureUnauthenticatedScreenshots(page) {
console.log('\n[STEP] Capturing what is visible on the live site...');
await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15000 });
await sleep(3000);
// Check what's actually rendered
const pageState = await page.evaluate(() => {
const elements = {
appRoot: !!document.querySelector('app-root'),
appShell: !!document.querySelector('app-shell'),
sidebar: !!document.querySelector('app-sidebar'),
topbar: !!document.querySelector('app-topbar'),
header: !!document.querySelector('.app-header'),
signIn: !!document.querySelector('.app-auth__signin'),
splash: !!document.querySelector('#stella-splash'),
shell: !!document.querySelector('.shell'),
navigation: !!document.querySelector('app-navigation-menu'),
breadcrumb: !!document.querySelector('app-breadcrumb'),
};
return elements;
});
console.log(` Page element state: ${JSON.stringify(pageState, null, 2)}`);
await screenshot(page, '01-landing-page', 'Landing page state');
}
async function main() {
console.log('=== StellaOps Authenticated Layout Screenshot Capture ===\n');
console.log(`Target: ${BASE_URL}`);
console.log(`Output: ${SCREENSHOT_DIR}\n`);
const browser = await chromium.launch({
headless: true,
args: ['--ignore-certificate-errors', '--allow-insecure-localhost'],
});
const context = await browser.newContext({
viewport: VIEWPORT_DESKTOP,
ignoreHTTPSErrors: true,
bypassCSP: true,
});
const page = await context.newPage();
// Enable console log forwarding
page.on('console', msg => {
if (msg.type() === 'error') {
console.log(` [BROWSER ERROR] ${msg.text()}`);
}
});
try {
// Step 1: Capture current state
await captureUnauthenticatedScreenshots(page);
// Step 2: Try real login
const realLoginSuccess = await tryRealLogin(page);
if (realLoginSuccess) {
console.log('\n Real login succeeded!');
await captureAuthenticatedScreenshots(page);
} else {
console.log('\n Real login failed. Attempting mock auth injection...');
// Close and create new context for clean state
await page.close();
const newPage = await context.newPage();
page.on('console', msg => {
if (msg.type() === 'error') {
console.log(` [BROWSER ERROR] ${msg.text()}`);
}
});
const mockSuccess = await injectMockAuth(newPage);
if (mockSuccess) {
console.log('\n Mock auth injection succeeded!');
await captureAuthenticatedScreenshots(newPage);
} else {
console.log('\n Mock auth injection did not produce shell layout.');
console.log(' Capturing available state with additional details...');
// Capture whatever we can see
await newPage.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 15000 });
await sleep(3000);
// Get detailed DOM info
const domInfo = await newPage.evaluate(() => {
const getTextContent = (selector) => {
const el = document.querySelector(selector);
return el ? el.textContent?.trim()?.substring(0, 200) : null;
};
const getInnerHTML = (selector) => {
const el = document.querySelector(selector);
return el ? el.innerHTML?.substring(0, 500) : null;
};
return {
title: document.title,
bodyClasses: document.body.className,
appRootHTML: getInnerHTML('app-root'),
mainContent: getTextContent('.app-content') || getTextContent('main'),
allComponents: Array.from(document.querySelectorAll('*'))
.filter(el => el.tagName.includes('-'))
.map(el => el.tagName.toLowerCase())
.filter((v, i, a) => a.indexOf(v) === i)
.slice(0, 30),
};
});
console.log('\n DOM Info:');
console.log(` Title: ${domInfo.title}`);
console.log(` Body classes: ${domInfo.bodyClasses}`);
console.log(` Custom elements: ${domInfo.allComponents.join(', ')}`);
if (domInfo.appRootHTML) {
console.log(` app-root HTML (first 500 chars): ${domInfo.appRootHTML}`);
}
await screenshot(newPage, '99-final-state', 'Final page state after all attempts');
await newPage.close();
}
}
} catch (error) {
console.error(`\nFatal error: ${error.message}`);
console.error(error.stack);
try {
await screenshot(page, '99-error-state', 'Error state');
} catch {}
} finally {
await browser.close();
}
console.log('\n=== Screenshot capture complete ===');
}
main().catch(console.error);

View File

@@ -0,0 +1,124 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
ignoreHTTPSErrors: true,
});
const page = await context.newPage();
const screenshotDir = __dirname + '/review';
const routes = [
// Main pages
['01-dashboard', '/'],
['02-findings', '/findings'],
['03-vulnerabilities', '/vulnerabilities'],
['04-reachability', '/reachability'],
['05-graph', '/graph'],
['06-lineage', '/lineage'],
['07-vex-hub', '/admin/vex-hub'],
// Triage & Policy
['08-triage', '/triage'],
['09-triage-artifacts', '/triage/artifacts'],
['10-triage-inbox', '/triage/inbox'],
['11-exceptions', '/exceptions'],
['12-risk', '/risk'],
['13-policy', '/policy'],
['14-policy-studio', '/policy-studio/packs'],
// Evidence
['15-evidence', '/evidence'],
['16-evidence-packs', '/evidence-packs'],
['17-evidence-thread', '/evidence-thread'],
// Operations
['18-sbom-sources', '/sbom-sources'],
['19-platform-health', '/ops/health'],
['20-quotas', '/ops/quotas'],
['21-dead-letter', '/ops/orchestrator/dead-letter'],
['22-slo', '/ops/orchestrator/slo'],
['23-feed-mirror', '/ops/feeds'],
['24-offline-kit', '/ops/offline-kit'],
['25-aoc-compliance', '/ops/aoc'],
['26-scanner-ops', '/ops/scanner'],
['27-doctor', '/ops/doctor'],
['28-agent-fleet', '/ops/agents'],
['29-signals', '/ops/signals'],
['30-pack-registry', '/ops/packs'],
// Releases
['31-releases', '/releases'],
['32-environments', '/environments'],
['33-deployments', '/deployments'],
['34-approvals', '/approvals'],
['35-release-orchestrator', '/release-orchestrator'],
// Admin
['36-settings', '/settings'],
['37-notifications-admin', '/admin/notifications'],
['38-trust-management', '/admin/trust'],
['39-audit-log', '/admin/audit'],
['40-registry-admin', '/admin/registries'],
['41-issuer-trust', '/admin/issuers'],
['42-policy-governance', '/admin/policy/governance'],
['43-policy-simulation', '/admin/policy/simulation'],
['44-console-admin', '/console/admin'],
// Other
['45-analytics', '/analytics'],
['46-timeline', '/timeline'],
['47-notifications', '/notify'],
['48-ai-autofix', '/ai/autofix'],
['49-ai-chat', '/ai/chat'],
['50-scheduler', '/scheduler'],
['51-security', '/security'],
];
const results = [];
for (const [name, path] of routes) {
const url = 'http://stella-ops.local' + path;
try {
console.log(`Navigating to: ${name} (${url})`);
const response = await page.goto(url, { waitUntil: 'networkidle', timeout: 15000 });
// Wait a bit for Angular rendering
await page.waitForTimeout(1500);
const filePath = screenshotDir + '/' + name + '.png';
await page.screenshot({ path: filePath, fullPage: false });
// Get page title and current URL (in case of redirects)
const title = await page.title();
const currentUrl = page.url();
console.log(` OK: ${name} -> ${currentUrl} (title: "${title}")`);
results.push({ name, path, status: 'ok', title, currentUrl, statusCode: response?.status() });
} catch (e) {
console.log(` FAIL: ${name} - ${e.message.substring(0, 200)}`);
// Try to capture whatever is on screen anyway
try {
const filePath = screenshotDir + '/' + name + '-error.png';
await page.screenshot({ path: filePath, fullPage: false });
} catch (e2) {
// ignore screenshot error
}
results.push({ name, path, status: 'error', error: e.message.substring(0, 200) });
}
}
console.log('\n=== SUMMARY ===');
console.log(`Total: ${results.length}`);
console.log(`OK: ${results.filter(r => r.status === 'ok').length}`);
console.log(`Errors: ${results.filter(r => r.status === 'error').length}`);
for (const r of results) {
if (r.status === 'error') {
console.log(` FAILED: ${r.name} (${r.path}) - ${r.error}`);
}
}
await browser.close();
})();

View File

@@ -46,6 +46,7 @@ import { LegacyUrlBannerComponent } from './shared/ui/legacy-url-banner/legacy-u
export class AppComponent {
private static readonly SHELL_EXCLUDED_ROUTES = [
'/setup',
'/welcome',
'/callback',
'/silent-refresh',
'/auth/callback',

View File

@@ -62,7 +62,7 @@ import {
VEX_DECISIONS_API_BASE_URL,
VexDecisionsHttpClient,
} from './core/api/vex-decisions.client';
import { VEX_HUB_API_BASE_URL, VEX_LENS_API_BASE_URL } from './core/api/vex-hub.client';
import { VEX_HUB_API, VEX_HUB_API_BASE_URL, VEX_LENS_API_BASE_URL, VexHubApiHttpClient } from './core/api/vex-hub.client';
import {
AUDIT_BUNDLES_API,
AUDIT_BUNDLES_API_BASE_URL,
@@ -342,6 +342,11 @@ export const appConfig: ApplicationConfig = {
}
},
},
VexHubApiHttpClient,
{
provide: VEX_HUB_API,
useExisting: VexHubApiHttpClient,
},
VexEvidenceHttpClient,
{
provide: VEX_EVIDENCE_API,

View File

@@ -1,6 +1,7 @@
import { Routes } from '@angular/router';
import {
requireAuthGuard,
requireOrchViewerGuard,
requireOrchOperatorGuard,
requirePolicyAuthorGuard,
@@ -27,7 +28,7 @@ export const routes: Routes = [
path: '',
pathMatch: 'full',
title: 'Control Plane',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/control-plane/control-plane.routes').then(
(m) => m.CONTROL_PLANE_ROUTES
@@ -38,7 +39,7 @@ export const routes: Routes = [
{
path: 'approvals',
title: 'Approvals',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/approvals/approvals.routes').then(
(m) => m.APPROVALS_ROUTES
@@ -49,7 +50,7 @@ export const routes: Routes = [
{
path: 'environments',
title: 'Environments',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/release-orchestrator/environments/environments.routes').then(
(m) => m.ENVIRONMENT_ROUTES
@@ -58,7 +59,7 @@ export const routes: Routes = [
{
path: 'releases',
title: 'Releases',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/release-orchestrator/releases/releases.routes').then(
(m) => m.RELEASE_ROUTES
@@ -67,7 +68,7 @@ export const routes: Routes = [
{
path: 'deployments',
title: 'Deployments',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/release-orchestrator/deployments/deployments.routes').then(
(m) => m.DEPLOYMENT_ROUTES
@@ -78,7 +79,7 @@ export const routes: Routes = [
{
path: 'operations',
title: 'Operations',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/operations/operations.routes').then(
(m) => m.OPERATIONS_ROUTES
@@ -89,7 +90,7 @@ export const routes: Routes = [
{
path: 'security',
title: 'Security Overview',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/security/security.routes').then(
(m) => m.SECURITY_ROUTES
@@ -111,7 +112,7 @@ export const routes: Routes = [
{
path: 'policy',
title: 'Policy',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/policy/policy.routes').then(
(m) => m.POLICY_ROUTES
@@ -122,7 +123,7 @@ export const routes: Routes = [
{
path: 'settings',
title: 'Settings',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/settings/settings.routes').then(
(m) => m.SETTINGS_ROUTES
@@ -136,7 +137,7 @@ export const routes: Routes = [
// Legacy Home Dashboard - redirects or will be removed
{
path: 'home',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/home/home-dashboard.component').then(
(m) => m.HomeDashboardComponent
@@ -144,7 +145,7 @@ export const routes: Routes = [
},
{
path: 'dashboard/sources',
canMatch: [requireConfigGuard, requireBackendsReachableGuard],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/dashboard/sources-dashboard.component').then(
(m) => m.SourcesDashboardComponent
@@ -152,7 +153,7 @@ export const routes: Routes = [
},
{
path: 'console/profile',
canMatch: [requireConfigGuard, requireBackendsReachableGuard],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/console/console-profile.component').then(
(m) => m.ConsoleProfileComponent
@@ -160,7 +161,7 @@ export const routes: Routes = [
},
{
path: 'console/status',
canMatch: [requireConfigGuard, requireBackendsReachableGuard],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/console/console-status.component').then(
(m) => m.ConsoleStatusComponent
@@ -169,7 +170,7 @@ export const routes: Routes = [
// Console Admin routes - gated by ui.admin scope
{
path: 'console/admin',
canMatch: [requireConfigGuard, requireBackendsReachableGuard],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/console-admin/console-admin.routes').then(
(m) => m.consoleAdminRoutes
@@ -211,7 +212,7 @@ export const routes: Routes = [
// Release Orchestrator - Dashboard and management UI (SPRINT_20260110_111_001)
{
path: 'release-orchestrator',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/release-orchestrator/dashboard/dashboard.routes').then(
(m) => m.DASHBOARD_ROUTES
@@ -283,7 +284,7 @@ export const routes: Routes = [
},
{
path: 'concelier/trivy-db-settings',
canMatch: [requireConfigGuard, requireBackendsReachableGuard],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/trivy-db-settings/trivy-db-settings-page.component').then(
(m) => m.TrivyDbSettingsPageComponent
@@ -291,7 +292,7 @@ export const routes: Routes = [
},
{
path: 'scans/:scanId',
canMatch: [requireConfigGuard, requireBackendsReachableGuard],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/scans/scan-detail-page.component').then(
(m) => m.ScanDetailPageComponent
@@ -307,7 +308,7 @@ export const routes: Routes = [
},
{
path: 'risk',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/risk/risk-dashboard.component').then(
(m) => m.RiskDashboardComponent
@@ -315,7 +316,7 @@ export const routes: Routes = [
},
{
path: 'graph',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/graph/graph-explorer.component').then(
(m) => m.GraphExplorerComponent
@@ -323,13 +324,13 @@ export const routes: Routes = [
},
{
path: 'lineage',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/lineage/lineage.routes').then((m) => m.lineageRoutes),
},
{
path: 'reachability',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/reachability/reachability-center.component').then(
(m) => m.ReachabilityCenterComponent
@@ -337,19 +338,19 @@ export const routes: Routes = [
},
{
path: 'timeline',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/timeline/timeline.routes').then((m) => m.TIMELINE_ROUTES),
},
{
path: 'evidence-thread',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/evidence-thread/evidence-thread.routes').then((m) => m.EVIDENCE_THREAD_ROUTES),
},
{
path: 'vulnerabilities',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/vulnerabilities/vulnerability-explorer.component').then(
(m) => m.VulnerabilityExplorerComponent
@@ -357,7 +358,7 @@ export const routes: Routes = [
},
{
path: 'vulnerabilities/triage',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/vulnerabilities/components/vuln-triage-dashboard/vuln-triage-dashboard.component').then(
(m) => m.VulnTriageDashboardComponent
@@ -366,7 +367,7 @@ export const routes: Routes = [
// Findings container with diff-first default (SPRINT_1227_0005_0001)
{
path: 'findings',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/findings/container/findings-container.component').then(
(m) => m.FindingsContainerComponent
@@ -374,7 +375,7 @@ export const routes: Routes = [
},
{
path: 'findings/:scanId',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/findings/container/findings-container.component').then(
(m) => m.FindingsContainerComponent
@@ -382,7 +383,7 @@ export const routes: Routes = [
},
{
path: 'triage',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/triage/components/triage-canvas/triage-canvas.component').then(
(m) => m.TriageCanvasComponent
@@ -390,7 +391,7 @@ export const routes: Routes = [
},
{
path: 'triage/artifacts',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/triage/triage-artifacts.component').then(
(m) => m.TriageArtifactsComponent
@@ -398,7 +399,7 @@ export const routes: Routes = [
},
{
path: 'triage/inbox',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/triage-inbox/triage-inbox.component').then(
(m) => m.TriageInboxComponent
@@ -406,7 +407,7 @@ export const routes: Routes = [
},
{
path: 'triage/artifacts/:artifactId',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/triage/triage-workspace.component').then(
(m) => m.TriageWorkspaceComponent
@@ -414,7 +415,7 @@ export const routes: Routes = [
},
{
path: 'triage/audit-bundles',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/triage/triage-audit-bundles.component').then(
(m) => m.TriageAuditBundlesComponent
@@ -422,7 +423,7 @@ export const routes: Routes = [
},
{
path: 'triage/audit-bundles/new',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/triage/triage-audit-bundle-new.component').then(
(m) => m.TriageAuditBundleNewComponent
@@ -430,7 +431,7 @@ export const routes: Routes = [
},
{
path: 'triage/ai-recommendations',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/triage/ai-recommendation-workbench.component').then(
(m) => m.AiRecommendationWorkbenchComponent
@@ -438,7 +439,7 @@ export const routes: Routes = [
},
{
path: 'triage/quiet-lane',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/triage/quiet-lane-workbench.component').then(
(m) => m.QuietLaneWorkbenchComponent
@@ -446,7 +447,7 @@ export const routes: Routes = [
},
{
path: 'audit/reasons',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/triage/reason-capsule-workbench.component').then(
(m) => m.ReasonCapsuleWorkbenchComponent
@@ -454,7 +455,7 @@ export const routes: Routes = [
},
{
path: 'qa/web-recheck',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/qa/web-feature-recheck-workbench.component').then(
(m) => m.WebFeatureRecheckWorkbenchComponent
@@ -462,7 +463,7 @@ export const routes: Routes = [
},
{
path: 'qa/sbom-component-detail',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/sbom/pages/component-detail/component-detail.page').then(
(m) => m.ComponentDetailPage
@@ -470,7 +471,7 @@ export const routes: Routes = [
},
{
path: 'ops/binary-index',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/binary-index/binary-index-ops.component').then(
(m) => m.BinaryIndexOpsComponent
@@ -478,7 +479,7 @@ export const routes: Routes = [
},
{
path: 'settings/determinization-config',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/settings/determinization-config-pane.component').then(
(m) => m.DeterminizationConfigPaneComponent
@@ -486,7 +487,7 @@ export const routes: Routes = [
},
{
path: 'compare/:currentId',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/compare/components/compare-view/compare-view.component').then(
(m) => m.CompareViewComponent
@@ -494,7 +495,7 @@ export const routes: Routes = [
},
{
path: 'proofs/:subjectDigest',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/proof-chain/proof-chain.component').then(
(m) => m.ProofChainComponent
@@ -502,7 +503,7 @@ export const routes: Routes = [
},
{
path: 'vulnerabilities/:vulnId',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/vulnerabilities/vulnerability-detail.component').then(
(m) => m.VulnerabilityDetailComponent
@@ -510,13 +511,13 @@ export const routes: Routes = [
},
{
path: 'cvss/receipts/:receiptId',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/cvss/cvss-receipt.component').then((m) => m.CvssReceiptComponent),
},
{
path: 'notify',
canMatch: [requireConfigGuard, requireBackendsReachableGuard],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/notify/notify-panel.component').then(
(m) => m.NotifyPanelComponent
@@ -525,28 +526,28 @@ export const routes: Routes = [
// Admin - VEX Hub (SPRINT_20251229_018a)
{
path: 'admin/vex-hub',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/vex-hub/vex-hub.routes').then((m) => m.vexHubRoutes),
},
// Admin - Notifications (SPRINT_20251229_018b)
{
path: 'admin/notifications',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/admin-notifications/admin-notifications.routes').then((m) => m.adminNotificationsRoutes),
},
// Admin - Trust Management (SPRINT_20251229_018c)
{
path: 'admin/trust',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/trust-admin/trust-admin.routes').then((m) => m.trustAdminRoutes),
},
// Ops - Feed Mirror (SPRINT_20251229_020)
{
path: 'ops/feeds',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/feed-mirror/feed-mirror.routes').then((m) => m.feedMirrorRoutes),
},
@@ -554,7 +555,7 @@ export const routes: Routes = [
{
path: 'ops/signals',
title: 'Signals Runtime Dashboard',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/signals/signals.routes').then((m) => m.SIGNALS_ROUTES),
},
@@ -568,35 +569,35 @@ export const routes: Routes = [
},
{
path: 'sbom-sources',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/sbom-sources/sbom-sources.routes').then((m) => m.SBOM_SOURCES_ROUTES),
},
// Admin - Policy Governance (SPRINT_20251229_021a)
{
path: 'admin/policy/governance',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/policy-governance/policy-governance.routes').then((m) => m.policyGovernanceRoutes),
},
// Admin - Policy Simulation (SPRINT_20251229_021b)
{
path: 'admin/policy/simulation',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/policy-simulation/policy-simulation.routes').then((m) => m.policySimulationRoutes),
},
// Evidence/Export/Replay (SPRINT_20251229_016)
{
path: 'evidence',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/evidence-export/evidence-export.routes').then((m) => m.evidenceExportRoutes),
},
// Scheduler Ops (SPRINT_20251229_017)
{
path: 'scheduler',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/scheduler-ops/scheduler-ops.routes').then((m) => m.schedulerOpsRoutes),
},
@@ -617,7 +618,7 @@ export const routes: Routes = [
// Exceptions route
{
path: 'exceptions',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/triage/triage-artifacts.component').then(
(m) => m.TriageArtifactsComponent
@@ -626,105 +627,105 @@ export const routes: Routes = [
// Integration Hub (SPRINT_20251229_011_FE_integration_hub_ui)
{
path: 'integrations',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/integration-hub/integration-hub.routes').then((m) => m.integrationHubRoutes),
},
// Admin - Registry Token Service (SPRINT_20251229_023)
{
path: 'admin/registries',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/registry-admin/registry-admin.routes').then((m) => m.registryAdminRoutes),
},
// Admin - Issuer Trust (SPRINT_20251229_024)
{
path: 'admin/issuers',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/issuer-trust/issuer-trust.routes').then((m) => m.issuerTrustRoutes),
},
// Ops - Scanner Operations (SPRINT_20251229_025)
{
path: 'ops/scanner',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/scanner-ops/scanner-ops.routes').then((m) => m.scannerOpsRoutes),
},
// Ops - Offline Kit Management (SPRINT_20251229_026)
{
path: 'ops/offline-kit',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/offline-kit/offline-kit.routes').then((m) => m.offlineKitRoutes),
},
// Ops - AOC Compliance Dashboard (SPRINT_20251229_027)
{
path: 'ops/aoc',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/aoc-compliance/aoc-compliance.routes').then((m) => m.AOC_COMPLIANCE_ROUTES),
},
// Admin - Unified Audit Log (SPRINT_20251229_028)
{
path: 'admin/audit',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/audit-log/audit-log.routes').then((m) => m.auditLogRoutes),
},
// Ops - Quota Dashboard (SPRINT_20251229_029)
{
path: 'ops/quotas',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/quota-dashboard/quota.routes').then((m) => m.quotaRoutes),
},
// Ops - Dead-Letter Management (SPRINT_20251229_030)
{
path: 'ops/orchestrator/dead-letter',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/deadletter/deadletter.routes').then((m) => m.deadletterRoutes),
},
// Ops - SLO Burn Rate Monitoring (SPRINT_20251229_031)
{
path: 'ops/orchestrator/slo',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/slo-monitoring/slo.routes').then((m) => m.sloRoutes),
},
// Ops - Platform Health Dashboard (SPRINT_20251229_032)
{
path: 'ops/health',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/platform-health/platform-health.routes').then((m) => m.platformHealthRoutes),
},
// Ops - Doctor Diagnostics (SPRINT_20260112_001_008)
{
path: 'ops/doctor',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/doctor/doctor.routes').then((m) => m.DOCTOR_ROUTES),
},
// Ops - Agent Fleet (SPRINT_20260118_023_FE)
{
path: 'ops/agents',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/agents/agents.routes').then((m) => m.AGENTS_ROUTES),
},
// Analyze - Unknowns Tracking (SPRINT_20251229_033)
{
path: 'analyze/unknowns',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/unknowns-tracking/unknowns.routes').then((m) => m.unknownsRoutes),
},
// Analyze - Patch Map Explorer (SPRINT_20260103_003_FE_patch_map_explorer)
{
path: 'analyze/patch-map',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/binary-index/patch-map.component').then(
(m) => m.PatchMapComponent
@@ -733,7 +734,7 @@ export const routes: Routes = [
// Evidence Packs (SPRINT_20260109_011_005)
{
path: 'evidence-packs',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/evidence-pack/evidence-pack-list.component').then(
(m) => m.EvidencePackListComponent
@@ -741,7 +742,7 @@ export const routes: Routes = [
},
{
path: 'evidence-packs/:packId',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/evidence-pack/evidence-pack-viewer.component').then(
(m) => m.EvidencePackViewerComponent
@@ -750,7 +751,7 @@ export const routes: Routes = [
// Advisory AI Autofix workbench (strict Tier 2 UI verification surface)
{
path: 'ai/autofix',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/advisory-ai/autofix-workbench.component').then(
(m) => m.AutofixWorkbenchComponent
@@ -758,7 +759,7 @@ export const routes: Routes = [
},
{
path: 'aoc/verify',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/aoc/aoc-verification-workbench.component').then(
(m) => m.AocVerificationWorkbenchComponent
@@ -766,7 +767,7 @@ export const routes: Routes = [
},
{
path: 'ai/chat',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/advisory-ai/chat/chat.component').then(
(m) => m.ChatComponent
@@ -774,7 +775,7 @@ export const routes: Routes = [
},
{
path: 'ai/chips',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/advisory-ai/chip-showcase.component').then(
(m) => m.ChipShowcaseComponent
@@ -783,7 +784,7 @@ export const routes: Routes = [
// AI Runs (SPRINT_20260109_011_003)
{
path: 'ai-runs',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/ai-runs/ai-runs-list.component').then(
(m) => m.AiRunsListComponent
@@ -791,7 +792,7 @@ export const routes: Routes = [
},
{
path: 'ai-runs/:runId',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadComponent: () =>
import('./features/ai-runs/ai-run-viewer.component').then(
(m) => m.AiRunViewerComponent
@@ -800,7 +801,7 @@ export const routes: Routes = [
// Change Trace (SPRINT_20260112_200_007)
{
path: 'change-trace',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/change-trace/change-trace.routes').then((m) => m.changeTraceRoutes),
},
@@ -813,35 +814,35 @@ export const routes: Routes = [
// Configuration Pane (Sprint 6: Configuration Pane)
{
path: 'console/configuration',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/configuration-pane/configuration-pane.routes').then((m) => m.CONFIGURATION_PANE_ROUTES),
},
// SBOM Diff View (SPRINT_0127_0001_FE - FE-PERSONA-02)
{
path: 'sbom/diff',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/sbom-diff/sbom-diff.routes').then((m) => m.SBOM_DIFF_ROUTES),
},
// Deploy Diff View (SPRINT_20260125_006_FE_ab_deploy_diff_panel)
{
path: 'deploy/diff',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/deploy-diff/deploy-diff.routes').then((m) => m.DEPLOY_DIFF_ROUTES),
},
// VEX Timeline (SPRINT_0127_0001_FE - FE-PERSONA-03)
{
path: 'vex/timeline',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/vex-timeline/vex-timeline.routes').then((m) => m.VEX_TIMELINE_ROUTES),
},
// Developer Workspace (SPRINT_0127_0001_FE - FE-PERSONA-04)
{
path: 'workspace/dev',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/workspaces/developer/developer-workspace.routes').then(
(m) => m.DEVELOPER_WORKSPACE_ROUTES
@@ -850,7 +851,7 @@ export const routes: Routes = [
// Auditor Workspace (SPRINT_0127_0001_FE - FE-PERSONA-05)
{
path: 'workspace/audit',
canMatch: [requireConfigGuard, requireBackendsReachableGuard, () => import('./core/auth/auth.guard').then((m) => m.requireAuthGuard)],
canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard],
loadChildren: () =>
import('./features/workspaces/auditor/auditor-workspace.routes').then(
(m) => m.AUDITOR_WORKSPACE_ROUTES

View File

@@ -112,12 +112,12 @@ export function getUrgencyLabel(urgency: ApprovalUrgency): string {
export function getUrgencyColor(urgency: ApprovalUrgency): string {
const colors: Record<ApprovalUrgency, string> = {
low: '#6b7280',
normal: '#3b82f6',
high: '#f59e0b',
critical: '#ef4444',
low: 'var(--color-text-secondary)',
normal: 'var(--color-status-info)',
high: 'var(--color-status-warning)',
critical: 'var(--color-status-error)',
};
return colors[urgency] || '#6b7280';
return colors[urgency] || 'var(--color-text-secondary)';
}
export function getStatusLabel(status: ApprovalStatus): string {
@@ -132,12 +132,12 @@ export function getStatusLabel(status: ApprovalStatus): string {
export function getStatusColor(status: ApprovalStatus): string {
const colors: Record<ApprovalStatus, string> = {
pending: '#f59e0b',
approved: '#10b981',
rejected: '#ef4444',
expired: '#6b7280',
pending: 'var(--color-status-warning)',
approved: 'var(--color-status-success)',
rejected: 'var(--color-status-error)',
expired: 'var(--color-text-secondary)',
};
return colors[status] || '#6b7280';
return colors[status] || 'var(--color-text-secondary)';
}
export function getGateStatusIcon(status: GateStatus): string {
@@ -153,13 +153,13 @@ export function getGateStatusIcon(status: GateStatus): string {
export function getGateStatusColor(status: GateStatus): string {
const colors: Record<GateStatus, string> = {
passed: '#10b981',
failed: '#ef4444',
warning: '#f59e0b',
pending: '#6b7280',
skipped: '#9ca3af',
passed: 'var(--color-status-success)',
failed: 'var(--color-status-error)',
warning: 'var(--color-status-warning)',
pending: 'var(--color-text-secondary)',
skipped: 'var(--color-text-muted)',
};
return colors[status] || '#6b7280';
return colors[status] || 'var(--color-text-secondary)';
}
export function getGateTypeIcon(type: GateType): string {

View File

@@ -117,26 +117,26 @@ export function getStatusLabel(status: DeploymentStatus): string {
export function getStatusColor(status: DeploymentStatus): string {
const colors: Record<DeploymentStatus, string> = {
pending: '#6b7280',
running: '#3b82f6',
paused: '#f59e0b',
completed: '#10b981',
failed: '#ef4444',
cancelled: '#9ca3af',
rolling_back: '#f59e0b',
pending: 'var(--color-text-secondary)',
running: 'var(--color-status-info)',
paused: 'var(--color-status-warning)',
completed: 'var(--color-status-success)',
failed: 'var(--color-status-error)',
cancelled: 'var(--color-text-muted)',
rolling_back: 'var(--color-status-warning)',
};
return colors[status] || '#6b7280';
return colors[status] || 'var(--color-text-secondary)';
}
export function getTargetStatusColor(status: TargetStatus): string {
const colors: Record<TargetStatus, string> = {
pending: '#6b7280',
running: '#3b82f6',
completed: '#10b981',
failed: '#ef4444',
skipped: '#9ca3af',
pending: 'var(--color-text-secondary)',
running: 'var(--color-status-info)',
completed: 'var(--color-status-success)',
failed: 'var(--color-status-error)',
skipped: 'var(--color-text-muted)',
};
return colors[status] || '#6b7280';
return colors[status] || 'var(--color-text-secondary)';
}
export function getTargetTypeIcon(type: TargetType): string {
@@ -151,12 +151,12 @@ export function getTargetTypeIcon(type: TargetType): string {
export function getLogLevelColor(level: LogLevel): string {
const colors: Record<LogLevel, string> = {
debug: '#6b7280',
info: '#3b82f6',
warn: '#f59e0b',
error: '#ef4444',
debug: 'var(--color-text-secondary)',
info: 'var(--color-status-info)',
warn: 'var(--color-status-warning)',
error: 'var(--color-status-error)',
};
return colors[level] || '#6b7280';
return colors[level] || 'var(--color-text-secondary)';
}
export function getStrategyLabel(strategy: DeploymentStrategy): string {

View File

@@ -201,12 +201,12 @@ export const EXCEPTION_TRANSITIONS: ExceptionTransition[] = [
];
export const KANBAN_COLUMNS: { status: ExceptionStatus; label: string; color: string }[] = [
{ status: 'draft', label: 'Draft', color: '#9ca3af' },
{ status: 'pending_review', label: 'Pending Review', color: '#f59e0b' },
{ status: 'approved', label: 'Approved', color: '#3b82f6' },
{ status: 'rejected', label: 'Rejected', color: '#f472b6' },
{ status: 'expired', label: 'Expired', color: '#6b7280' },
{ status: 'revoked', label: 'Revoked', color: '#ef4444' },
{ status: 'draft', label: 'Draft', color: 'var(--color-text-muted)' },
{ status: 'pending_review', label: 'Pending Review', color: 'var(--color-status-warning)' },
{ status: 'approved', label: 'Approved', color: 'var(--color-status-info)' },
{ status: 'rejected', label: 'Rejected', color: 'var(--color-status-excepted-border)' },
{ status: 'expired', label: 'Expired', color: 'var(--color-text-secondary)' },
{ status: 'revoked', label: 'Revoked', color: 'var(--color-status-error)' },
];
/**

View File

@@ -259,36 +259,36 @@ export const VERIFICATION_STATUS_DISPLAY: Record<VerificationStatus, Verificatio
status: 'verified',
label: 'Verified',
description: 'All paths meet coverage thresholds',
color: '#059669',
lightColor: '#D1FAE5',
color: 'var(--color-status-success-text)',
lightColor: 'var(--color-status-success-bg)',
},
not_verified: {
status: 'not_verified',
label: 'Not Verified',
description: 'Verification has not been run',
color: '#6B7280',
lightColor: '#F3F4F6',
color: 'var(--color-text-secondary)',
lightColor: 'var(--color-surface-secondary)',
},
degraded: {
status: 'degraded',
label: 'Degraded',
description: 'Some paths below coverage thresholds',
color: '#F59E0B',
lightColor: '#FEF3C7',
color: 'var(--color-status-warning)',
lightColor: 'var(--color-status-warning-bg)',
},
stale: {
status: 'stale',
label: 'Stale',
description: 'Verification data is outdated',
color: '#6B7280',
lightColor: '#F3F4F6',
color: 'var(--color-text-secondary)',
lightColor: 'var(--color-surface-secondary)',
},
error: {
status: 'error',
label: 'Error',
description: 'Verification failed with errors',
color: '#DC2626',
lightColor: '#FEE2E2',
color: 'var(--color-status-error)',
lightColor: 'var(--color-status-error-bg)',
},
};
@@ -296,10 +296,10 @@ export const VERIFICATION_STATUS_DISPLAY: Record<VerificationStatus, Verificatio
* Coverage status display metadata.
*/
export const COVERAGE_STATUS_DISPLAY: Record<CoverageStatus, { label: string; color: string }> = {
complete: { label: 'Complete', color: '#059669' },
adequate: { label: 'Adequate', color: '#CA8A04' },
sparse: { label: 'Sparse', color: '#EA580C' },
insufficient: { label: 'Insufficient', color: '#DC2626' },
complete: { label: 'Complete', color: 'var(--color-status-success-text)' },
adequate: { label: 'Adequate', color: 'var(--color-severity-medium)' },
sparse: { label: 'Sparse', color: 'var(--color-severity-high)' },
insufficient: { label: 'Insufficient', color: 'var(--color-status-error)' },
};
/**

View File

@@ -224,13 +224,13 @@ export function getProfileTypeLabel(type: PolicyProfileType): string {
export function getProfileTypeColor(type: PolicyProfileType): string {
const colors: Record<PolicyProfileType, string> = {
lenient_dev: '#10b981', // green
standard: '#3b82f6', // blue
strict_prod: '#f59e0b', // amber
gov_defense: '#8b5cf6', // purple
custom: '#6b7280', // gray
lenient_dev: 'var(--color-status-success)', // green
standard: 'var(--color-status-info)', // blue
strict_prod: 'var(--color-status-warning)', // amber
gov_defense: 'var(--color-status-excepted)', // purple
custom: 'var(--color-text-secondary)', // gray
};
return colors[type] || '#6b7280';
return colors[type] || 'var(--color-text-secondary)';
}
export function getSimulationStatusLabel(status: PolicySimulationStatus): string {
@@ -245,12 +245,12 @@ export function getSimulationStatusLabel(status: PolicySimulationStatus): string
export function getSimulationStatusColor(status: PolicySimulationStatus): string {
const colors: Record<PolicySimulationStatus, string> = {
pass: '#10b981', // green
fail: '#ef4444', // red
warn: '#f59e0b', // amber
error: '#6b7280', // gray
pass: 'var(--color-status-success)', // green
fail: 'var(--color-status-error)', // red
warn: 'var(--color-status-warning)', // amber
error: 'var(--color-text-secondary)', // gray
};
return colors[status] || '#6b7280';
return colors[status] || 'var(--color-text-secondary)';
}
export function getFeedStatusLabel(status: FeedStalenessStatus): string {
@@ -265,12 +265,12 @@ export function getFeedStatusLabel(status: FeedStalenessStatus): string {
export function getFeedStatusColor(status: FeedStalenessStatus): string {
const colors: Record<FeedStalenessStatus, string> = {
fresh: '#10b981', // green
warning: '#f59e0b', // amber
stale: '#ef4444', // red
unknown: '#6b7280', // gray
fresh: 'var(--color-status-success)', // green
warning: 'var(--color-status-warning)', // amber
stale: 'var(--color-status-error)', // red
unknown: 'var(--color-text-secondary)', // gray
};
return colors[status] || '#6b7280';
return colors[status] || 'var(--color-text-secondary)';
}
export function formatStalenessTime(seconds: number): string {

View File

@@ -236,7 +236,7 @@ export class MockReachabilityApi implements ReachabilityApi {
// For PNG/SVG, return a placeholder data URL
const svgContent = `<svg xmlns="http://www.w3.org/2000/svg" width="${request.width ?? 800}" height="${request.height ?? 600}">
<rect width="100%" height="100%" fill="#f5f5f5"/>
<rect width="100%" height="100%" fill="var(--color-surface-secondary)"/>
<text x="50%" y="50%" text-anchor="middle" fill="#666">Call Graph Visualization</text>
</svg>`;
const dataUrl = `data:image/svg+xml;base64,${btoa(svgContent)}`;

View File

@@ -111,13 +111,13 @@ export interface ProofBundle {
* Edge explanation type display names and colors.
*/
export const EDGE_TYPE_CONFIG: Record<EdgeExplanationType, { label: string; color: string; icon: string }> = {
import: { label: 'Import', color: '#28a745', icon: '📦' },
dynamic_load: { label: 'Dynamic Load', color: '#fd7e14', icon: '🔄' },
reflection: { label: 'Reflection', color: '#6f42c1', icon: '🔮' },
ffi: { label: 'FFI', color: '#17a2b8', icon: '🔗' },
env_guard: { label: 'Env Guard', color: '#ffc107', icon: '🔐' },
feature_flag: { label: 'Feature Flag', color: '#20c997', icon: '🚩' },
platform_arch: { label: 'Platform', color: '#6c757d', icon: '💻' },
taint_gate: { label: 'Taint Gate', color: '#dc3545', icon: '🛡️' },
loader_rule: { label: 'Loader', color: '#007bff', icon: '⚙️' },
import: { label: 'Import', color: 'var(--color-status-success)', icon: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>' },
dynamic_load: { label: 'Dynamic Load', color: 'var(--color-severity-high)', icon: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>' },
reflection: { label: 'Reflection', color: 'var(--color-status-excepted)', icon: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>' },
ffi: { label: 'FFI', color: 'var(--color-status-info)', icon: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>' },
env_guard: { label: 'Env Guard', color: 'var(--color-status-warning)', icon: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>' },
feature_flag: { label: 'Feature Flag', color: 'var(--color-status-success)', icon: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/></svg>' },
platform_arch: { label: 'Platform', color: 'var(--color-text-secondary)', icon: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>' },
taint_gate: { label: 'Taint Gate', color: 'var(--color-status-error)', icon: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>' },
loader_rule: { label: 'Loader', color: 'var(--color-status-info)', icon: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4"/></svg>' },
};

View File

@@ -186,10 +186,10 @@ export interface EvidenceListResponse {
export function getSignatureStatusColor(status: SignatureStatus): string {
const colors: Record<SignatureStatus, string> = {
valid: '#22c55e',
invalid: '#ef4444',
unsigned: '#6b7280',
expired: '#f59e0b',
valid: 'var(--color-status-success)',
invalid: 'var(--color-status-error)',
unsigned: 'var(--color-text-secondary)',
expired: 'var(--color-status-warning)',
};
return colors[status];
}

View File

@@ -113,14 +113,14 @@ export function getStatusLabel(status: ReleaseWorkflowStatus): string {
export function getStatusColor(status: ReleaseWorkflowStatus): string {
const colors: Record<ReleaseWorkflowStatus, string> = {
draft: '#6c757d',
ready: '#17a2b8',
deploying: '#ffc107',
deployed: '#28a745',
failed: '#dc3545',
rolled_back: '#fd7e14',
draft: 'var(--color-text-secondary)',
ready: 'var(--color-status-info)',
deploying: 'var(--color-status-warning)',
deployed: 'var(--color-status-success)',
failed: 'var(--color-status-error)',
rolled_back: 'var(--color-severity-high)',
};
return colors[status] || '#6c757d';
return colors[status] || 'var(--color-text-secondary)';
}
export function getEventIcon(type: ReleaseEventType): string {

View File

@@ -426,8 +426,8 @@ export const BUCKET_DISPLAY: BucketDisplayInfo[] = [
description: 'Critical - requires immediate attention',
minScore: 90,
maxScore: 100,
backgroundColor: '#DC2626', // red-600
textColor: '#FFFFFF',
backgroundColor: 'var(--color-status-error)', // red-600
textColor: 'var(--color-surface-primary)',
},
{
bucket: 'ScheduleNext',
@@ -435,8 +435,8 @@ export const BUCKET_DISPLAY: BucketDisplayInfo[] = [
description: 'High priority - schedule for next sprint',
minScore: 70,
maxScore: 89,
backgroundColor: '#F59E0B', // amber-500
textColor: '#1C1200',
backgroundColor: 'var(--color-status-warning)', // amber-500
textColor: 'var(--color-surface-inverse)',
},
{
bucket: 'Investigate',
@@ -444,8 +444,8 @@ export const BUCKET_DISPLAY: BucketDisplayInfo[] = [
description: 'Medium priority - investigate when possible',
minScore: 40,
maxScore: 69,
backgroundColor: '#3B82F6', // blue-500
textColor: '#FFFFFF',
backgroundColor: 'var(--color-status-info)', // blue-500
textColor: 'var(--color-surface-primary)',
},
{
bucket: 'Watchlist',
@@ -453,8 +453,8 @@ export const BUCKET_DISPLAY: BucketDisplayInfo[] = [
description: 'Low priority - monitor for changes',
minScore: 0,
maxScore: 39,
backgroundColor: '#6B7280', // gray-500
textColor: '#FFFFFF',
backgroundColor: 'var(--color-text-secondary)', // gray-500
textColor: 'var(--color-surface-primary)',
},
];
@@ -498,32 +498,32 @@ export const FLAG_DISPLAY: Record<ScoreFlag, FlagDisplayInfo> = {
label: 'Live Signal',
description: 'Active runtime signals detected from deployed environments',
icon: '[R]',
backgroundColor: '#059669', // emerald-600
textColor: '#FFFFFF',
backgroundColor: 'var(--color-status-success-text)', // emerald-600
textColor: 'var(--color-surface-primary)',
},
'proven-path': {
flag: 'proven-path',
label: 'Proven Path',
description: 'Verified reachability path to vulnerable code',
icon: '[P]',
backgroundColor: '#2563EB', // blue-600
textColor: '#FFFFFF',
backgroundColor: 'var(--color-status-info-text)', // blue-600
textColor: 'var(--color-surface-primary)',
},
'vendor-na': {
flag: 'vendor-na',
label: 'Vendor N/A',
description: 'Vendor has marked this as not affected',
icon: '[NA]',
backgroundColor: '#6B7280', // gray-500
textColor: '#FFFFFF',
backgroundColor: 'var(--color-text-secondary)', // gray-500
textColor: 'var(--color-surface-primary)',
},
speculative: {
flag: 'speculative',
label: 'Speculative',
description: 'Evidence is speculative or unconfirmed',
icon: '[?]',
backgroundColor: '#F97316', // orange-500
textColor: '#000000',
backgroundColor: 'var(--color-severity-high)', // orange-500
textColor: 'var(--color-text-heading)',
},
// Sprint: SPRINT_20260112_004_FE_attested_score_ui (FE-ATT-001)
anchored: {
@@ -531,16 +531,16 @@ export const FLAG_DISPLAY: Record<ScoreFlag, FlagDisplayInfo> = {
label: 'Anchored',
description: 'Score is anchored with DSSE attestation and/or Rekor transparency log',
icon: '[A]',
backgroundColor: '#7C3AED', // violet-600
textColor: '#FFFFFF',
backgroundColor: 'var(--color-status-excepted)', // violet-600
textColor: 'var(--color-surface-primary)',
},
'hard-fail': {
flag: 'hard-fail',
label: 'Hard Fail',
description: 'Policy hard-fail triggered - requires immediate remediation',
icon: '[!]',
backgroundColor: '#DC2626', // red-600
textColor: '#FFFFFF',
backgroundColor: 'var(--color-status-error)', // red-600
textColor: 'var(--color-surface-primary)',
},
};
@@ -711,9 +711,9 @@ export const UNKNOWNS_BAND_DISPLAY: UnknownsBandDisplayInfo[] = [
description: 'All critical signals present, high confidence in score',
minU: 0.0,
maxU: 0.2,
backgroundColor: '#059669', // emerald-600
textColor: '#FFFFFF',
lightBackground: '#D1FAE5', // emerald-100
backgroundColor: 'var(--color-status-success-text)', // emerald-600
textColor: 'var(--color-surface-primary)',
lightBackground: 'var(--color-status-success-bg)', // emerald-100
},
{
band: 'Adequate',
@@ -721,9 +721,9 @@ export const UNKNOWNS_BAND_DISPLAY: UnknownsBandDisplayInfo[] = [
description: 'Most signals present, reasonable confidence',
minU: 0.2,
maxU: 0.4,
backgroundColor: '#CA8A04', // yellow-600
textColor: '#1C1200',
lightBackground: '#FEF9C3', // yellow-100
backgroundColor: 'var(--color-severity-medium)', // yellow-600
textColor: 'var(--color-surface-inverse)',
lightBackground: 'var(--color-status-warning-bg)', // yellow-100
},
{
band: 'Sparse',
@@ -731,9 +731,9 @@ export const UNKNOWNS_BAND_DISPLAY: UnknownsBandDisplayInfo[] = [
description: 'Significant signals missing, limited confidence',
minU: 0.4,
maxU: 0.6,
backgroundColor: '#EA580C', // orange-600
textColor: '#FFFFFF',
lightBackground: '#FFEDD5', // orange-100
backgroundColor: 'var(--color-severity-high)', // orange-600
textColor: 'var(--color-surface-primary)',
lightBackground: 'var(--color-severity-high-bg)', // orange-100
},
{
band: 'Insufficient',
@@ -741,9 +741,9 @@ export const UNKNOWNS_BAND_DISPLAY: UnknownsBandDisplayInfo[] = [
description: 'Most signals missing, score is unreliable',
minU: 0.6,
maxU: 1.0,
backgroundColor: '#DC2626', // red-600
textColor: '#FFFFFF',
lightBackground: '#FEE2E2', // red-100
backgroundColor: 'var(--color-status-error)', // red-600
textColor: 'var(--color-surface-primary)',
lightBackground: 'var(--color-status-error-bg)', // red-100
},
];

View File

@@ -156,9 +156,9 @@ export const OBSERVATION_TYPE_LABELS: Record<string, string> = {
/** Observation type colors for badges. */
export const OBSERVATION_TYPE_COLORS: Record<string, string> = {
static: '#6c757d', // Gray
runtime: '#fd7e14', // Orange
confirmed: '#28a745', // Green
static: 'var(--color-text-secondary)', // Gray
runtime: 'var(--color-severity-high)', // Orange
confirmed: 'var(--color-status-success)', // Green
};
/**
@@ -315,11 +315,11 @@ export interface StateFlipSummary {
* Confidence tier badge colors.
*/
export const CONFIDENCE_TIER_COLORS: Record<ConfidenceTier, string> = {
confirmed: '#dc3545', // Red - highest risk
likely: '#fd7e14', // Orange
present: '#6c757d', // Gray
unreachable: '#28a745', // Green - no risk
unknown: '#17a2b8', // Blue - needs analysis
confirmed: 'var(--color-status-error)', // Red - highest risk
likely: 'var(--color-severity-high)', // Orange
present: 'var(--color-text-secondary)', // Gray
unreachable: 'var(--color-status-success)', // Green - no risk
unknown: 'var(--color-status-info)', // Blue - needs analysis
};
/**

View File

@@ -68,7 +68,7 @@ export const STEP_TYPES: StepTypeDefinition[] = [
label: 'Script',
description: 'Execute a custom script or command',
icon: 'code',
color: '#D4920A',
color: 'var(--color-brand-secondary)',
defaultConfig: { command: '', timeout: 300 },
},
{
@@ -76,7 +76,7 @@ export const STEP_TYPES: StepTypeDefinition[] = [
label: 'Approval',
description: 'Wait for manual approval',
icon: 'check-circle',
color: '#10b981',
color: 'var(--color-status-success)',
defaultConfig: { requiredApprovers: 1, approverRoles: [] },
},
{
@@ -84,7 +84,7 @@ export const STEP_TYPES: StepTypeDefinition[] = [
label: 'Deploy',
description: 'Deploy to target environment',
icon: 'rocket',
color: '#3b82f6',
color: 'var(--color-status-info)',
defaultConfig: { targetEnvironment: '', strategy: 'rolling' },
},
{
@@ -92,7 +92,7 @@ export const STEP_TYPES: StepTypeDefinition[] = [
label: 'Notify',
description: 'Send notification',
icon: 'bell',
color: '#f59e0b',
color: 'var(--color-status-warning)',
defaultConfig: { channels: [], message: '' },
},
{
@@ -100,7 +100,7 @@ export const STEP_TYPES: StepTypeDefinition[] = [
label: 'Gate',
description: 'Policy gate check',
icon: 'shield',
color: '#ef4444',
color: 'var(--color-status-error)',
defaultConfig: { policies: [], failOnViolation: true },
},
{
@@ -108,7 +108,7 @@ export const STEP_TYPES: StepTypeDefinition[] = [
label: 'Wait',
description: 'Wait for a duration',
icon: 'clock',
color: '#8b5cf6',
color: 'var(--color-status-excepted)',
defaultConfig: { duration: 60, unit: 'seconds' },
},
{
@@ -116,7 +116,7 @@ export const STEP_TYPES: StepTypeDefinition[] = [
label: 'Parallel',
description: 'Execute steps in parallel',
icon: 'git-branch',
color: '#14b8a6',
color: 'var(--color-status-success)',
defaultConfig: { branches: [] },
},
{
@@ -124,7 +124,7 @@ export const STEP_TYPES: StepTypeDefinition[] = [
label: 'Manual',
description: 'Manual intervention step',
icon: 'hand',
color: '#f97316',
color: 'var(--color-severity-high)',
defaultConfig: { instructions: '' },
},
];
@@ -145,12 +145,12 @@ export function getStatusLabel(status: WorkflowStatus): string {
export function getStatusColor(status: WorkflowStatus): string {
const colors: Record<WorkflowStatus, string> = {
draft: '#6c757d',
active: '#28a745',
disabled: '#ffc107',
archived: '#6c757d',
draft: 'var(--color-text-secondary)',
active: 'var(--color-status-success)',
disabled: 'var(--color-status-warning)',
archived: 'var(--color-text-secondary)',
};
return colors[status] || '#6c757d';
return colors[status] || 'var(--color-text-secondary)';
}
// YAML helpers

View File

@@ -26,7 +26,7 @@ describe('BrandingService', () => {
logoUri: 'https://acme.test/logo.png',
faviconUri: 'https://acme.test/favicon.ico',
themeTokens: {
'--theme-brand-primary': '#ff0000',
'--theme-brand-primary': 'var(--color-status-error)',
},
};
@@ -36,7 +36,7 @@ describe('BrandingService', () => {
expect(response.branding.logoUrl).toBe('https://acme.test/logo.png');
expect(response.branding.faviconUrl).toBe('https://acme.test/favicon.ico');
expect(response.branding.themeTokens).toEqual({
'--theme-brand-primary': '#ff0000',
'--theme-brand-primary': 'var(--color-status-error)',
});
});

View File

@@ -63,9 +63,22 @@ export class BackendProbeService {
return;
}
const normalized = authorityBase.endsWith('/')
let normalized = authorityBase.endsWith('/')
? authorityBase.slice(0, -1)
: authorityBase;
// Upgrade http → https when the SPA itself was loaded over HTTPS.
// This prevents mixed-content blocks when envsettings.json specifies
// http:// URLs but the browser enforces HTTPS-only fetch from an
// HTTPS origin.
if (
typeof window !== 'undefined' &&
window.location.protocol === 'https:' &&
normalized.startsWith('http://')
) {
normalized = normalized.replace(/^http:\/\//, 'https://');
}
const wellKnownUrl = `${normalized}/.well-known/openid-configuration`;
const body = await firstValueFrom(

View File

@@ -116,42 +116,42 @@ export const SEGMENT_TYPE_META: Record<ProofSegmentType, SegmentTypeMeta> = {
label: 'Component Identified',
icon: 'inventory_2',
description: 'Component identification from SBOM',
color: '#1976d2'
color: 'var(--color-status-info-text)'
},
Match: {
type: 'Match',
label: 'Vulnerability Matched',
icon: 'search',
description: 'Vulnerability matched from advisory',
color: '#f44336'
color: 'var(--color-status-error)'
},
Reachability: {
type: 'Reachability',
label: 'Reachability Analyzed',
icon: 'call_split',
description: 'Call path reachability analysis',
color: '#9c27b0'
color: 'var(--color-status-excepted)'
},
GuardAnalysis: {
type: 'GuardAnalysis',
label: 'Mitigations Checked',
icon: 'shield',
description: 'Guard and mitigation detection',
color: '#4caf50'
color: 'var(--color-status-success)'
},
RuntimeObservation: {
type: 'RuntimeObservation',
label: 'Runtime Signals',
icon: 'sensors',
description: 'Runtime signal observations',
color: '#ff9800'
color: 'var(--color-status-warning)'
},
PolicyEval: {
type: 'PolicyEval',
label: 'Policy Evaluated',
icon: 'gavel',
description: 'Policy evaluation result',
color: '#607d8b'
color: 'var(--color-text-secondary)'
}
};

View File

@@ -405,90 +405,90 @@ type NotifyAdminTab = 'channels' | 'rules' | 'templates' | 'deliveries' | 'incid
.admin-notifications-container { padding: 1.5rem; max-width: 1400px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.page-header h1 { margin: 0; font-size: 1.75rem; }
.subtitle { color: #666; margin-top: 0.25rem; }
.subtitle { color: var(--color-text-secondary); margin-top: 0.25rem; }
.stats-row { display: flex; gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap; }
.stat-card {
flex: 1; min-width: 100px; padding: 1rem; border-radius: 8px;
text-align: center; background: #f8f9fa; border: 1px solid #e9ecef;
flex: 1; min-width: 100px; padding: 1rem; border-radius: var(--radius-lg);
text-align: center; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary);
}
.stat-value { display: block; font-size: 1.5rem; font-weight: 700; color: #1976d2; }
.stat-label { font-size: 0.75rem; color: #666; text-transform: uppercase; }
.stat-value { display: block; font-size: 1.5rem; font-weight: var(--font-weight-bold); color: var(--color-status-info-text); }
.stat-label { font-size: 0.75rem; color: var(--color-text-secondary); text-transform: uppercase; }
.tabs { display: flex; gap: 0.5rem; margin-bottom: 1rem; border-bottom: 1px solid #ddd; flex-wrap: wrap; }
.tabs { display: flex; gap: 0.5rem; margin-bottom: 1rem; border-bottom: 1px solid var(--color-border-primary); flex-wrap: wrap; }
.tab {
padding: 0.75rem 1rem; border: none; background: none; cursor: pointer;
font-size: 0.875rem; color: #666; border-bottom: 2px solid transparent;
font-size: 0.875rem; color: var(--color-text-secondary); border-bottom: 2px solid transparent;
}
.tab.active { color: #1976d2; border-bottom-color: #1976d2; font-weight: 600; }
.tab.active { color: var(--color-status-info-text); border-bottom-color: var(--color-status-info-text); font-weight: var(--font-weight-semibold); }
.tab-content { background: white; border-radius: 8px; }
.tab-content { background: white; border-radius: var(--radius-lg); }
.tab-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; }
.tab-header h2 { margin: 0; font-size: 1.25rem; }
.btn-primary {
background: #1976d2; color: white; border: none; padding: 0.5rem 1rem;
border-radius: 4px; cursor: pointer; font-weight: 600;
background: var(--color-status-info-text); color: white; border: none; padding: 0.5rem 1rem;
border-radius: var(--radius-sm); cursor: pointer; font-weight: var(--font-weight-semibold);
}
.btn-secondary { background: #f5f5f5; border: 1px solid #ddd; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; }
.btn-icon { background: none; border: none; color: #1976d2; cursor: pointer; padding: 0.25rem 0.5rem; }
.btn-icon.danger { color: #d32f2f; }
.btn-secondary { background: var(--color-surface-secondary); border: 1px solid var(--color-border-primary); padding: 0.5rem 1rem; border-radius: var(--radius-sm); cursor: pointer; }
.btn-icon { background: none; border: none; color: var(--color-status-info-text); cursor: pointer; padding: 0.25rem 0.5rem; }
.btn-icon.danger { color: var(--color-severity-critical); }
.data-table { width: 100%; border-collapse: collapse; }
.data-table th, .data-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #eee; }
.data-table th { background: #f8f9fa; font-weight: 600; font-size: 0.875rem; color: #666; }
.text-muted { color: #666; font-size: 0.875rem; }
.data-table th, .data-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--color-surface-secondary); }
.data-table th { background: var(--color-surface-primary); font-weight: var(--font-weight-semibold); font-size: 0.875rem; color: var(--color-text-secondary); }
.text-muted { color: var(--color-text-secondary); font-size: 0.875rem; }
.channel-type { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; background: #e3f2fd; color: #1565c0; }
.type-slack { background: #4a154b20; color: #4a154b; }
.type-teams { background: #6264a720; color: #6264a7; }
.type-email { background: #ea433520; color: #ea4335; }
.type-webhook { background: #34a85320; color: #34a853; }
.channel-type { padding: 0.25rem 0.5rem; border-radius: var(--radius-sm); font-size: 0.75rem; font-weight: var(--font-weight-semibold); background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.type-slack { background: var(--color-status-excepted)20; color: var(--color-status-excepted); }
.type-teams { background: var(--color-status-excepted)20; color: var(--color-status-excepted); }
.type-email { background: var(--color-status-error)20; color: var(--color-status-error); }
.type-webhook { background: var(--color-status-success)20; color: var(--color-status-success); }
.target-cell { font-family: monospace; font-size: 0.875rem; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }
.status-badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
.status-badge.enabled { background: #c8e6c9; color: #2e7d32; }
.status-badge.disabled { background: #ffcdd2; color: #c62828; }
.status-badge { padding: 0.25rem 0.5rem; border-radius: var(--radius-sm); font-size: 0.75rem; font-weight: var(--font-weight-semibold); }
.status-badge.enabled { background: var(--color-status-success-border); color: var(--color-status-success-text); }
.status-badge.disabled { background: var(--color-status-error-border); color: var(--color-status-error-text); }
.health-indicator { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; }
.health-indicator.healthy { background: #c8e6c9; color: #2e7d32; }
.health-indicator { padding: 0.25rem 0.5rem; border-radius: var(--radius-sm); font-size: 0.75rem; }
.health-indicator.healthy { background: var(--color-status-success-border); color: var(--color-status-success-text); }
.event-types { display: flex; gap: 0.25rem; flex-wrap: wrap; }
.tag { background: #e3f2fd; color: #1565c0; padding: 0.125rem 0.5rem; border-radius: 4px; font-size: 0.75rem; }
.tag { background: var(--color-status-info-bg); color: var(--color-status-info-text); padding: 0.125rem 0.5rem; border-radius: var(--radius-sm); font-size: 0.75rem; }
.severity { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; }
.severity-critical { background: #ffebee; color: #c62828; }
.severity-high { background: #fff3e0; color: #e65100; }
.severity-medium { background: #fff8e1; color: #f9a825; }
.severity-low { background: #e3f2fd; color: #1565c0; }
.severity { padding: 0.25rem 0.5rem; border-radius: var(--radius-sm); font-size: 0.75rem; font-weight: var(--font-weight-semibold); text-transform: uppercase; }
.severity-critical { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.severity-high { background: var(--color-status-warning-bg); color: var(--color-severity-high); }
.severity-medium { background: var(--color-status-warning-bg); color: var(--color-status-warning); }
.severity-low { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.filters-row { margin-bottom: 1rem; }
.filters-row select { padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; }
.filters-row select { padding: 0.5rem; border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); }
.delivery-status { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
.status-sent { background: #c8e6c9; color: #2e7d32; }
.status-pending { background: #fff8e1; color: #f9a825; }
.status-failed { background: #ffcdd2; color: #c62828; }
.status-throttled { background: #e3f2fd; color: #1565c0; }
.status-digested { background: #e8eaf6; color: #3949ab; }
.delivery-status { padding: 0.25rem 0.5rem; border-radius: var(--radius-sm); font-size: 0.75rem; font-weight: var(--font-weight-semibold); }
.status-sent { background: var(--color-status-success-border); color: var(--color-status-success-text); }
.status-pending { background: var(--color-status-warning-bg); color: var(--color-status-warning); }
.status-failed { background: var(--color-status-error-border); color: var(--color-status-error-text); }
.status-throttled { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.status-digested { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.error-text { color: #c62828; font-size: 0.875rem; }
.error-text { color: var(--color-status-error-text); font-size: 0.875rem; }
.incident-id { font-family: monospace; font-size: 0.875rem; }
.incident-status { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
.status-open { background: #ffcdd2; color: #c62828; }
.status-acknowledged { background: #fff8e1; color: #f9a825; }
.status-resolved { background: #c8e6c9; color: #2e7d32; }
.incident-status { padding: 0.25rem 0.5rem; border-radius: var(--radius-sm); font-size: 0.75rem; font-weight: var(--font-weight-semibold); }
.status-open { background: var(--color-status-error-border); color: var(--color-status-error-text); }
.status-acknowledged { background: var(--color-status-warning-bg); color: var(--color-status-warning); }
.status-resolved { background: var(--color-status-success-border); color: var(--color-status-success-text); }
.empty-state { text-align: center; padding: 3rem; color: #666; }
.loading { text-align: center; padding: 2rem; color: #666; }
.error-banner { background: #ffebee; color: #c62828; padding: 1rem; border-radius: 4px; margin-top: 1rem; }
.empty-state { text-align: center; padding: 3rem; color: var(--color-text-secondary); }
.loading { text-align: center; padding: 2rem; color: var(--color-text-secondary); }
.error-banner { background: var(--color-status-error-bg); color: var(--color-status-error-text); padding: 1rem; border-radius: var(--radius-sm); margin-top: 1rem; }
.config-sections { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; }
.config-section { background: #f8f9fa; padding: 1.5rem; border-radius: 8px; }
.config-section { background: var(--color-surface-primary); padding: 1.5rem; border-radius: var(--radius-lg); }
.config-section h3 { margin: 0 0 0.5rem; font-size: 1rem; }
.section-desc { color: #666; font-size: 0.875rem; margin: 0 0 1rem; }
.config-item { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem; background: white; border-radius: 4px; margin-bottom: 0.5rem; }
.section-desc { color: var(--color-text-secondary); font-size: 0.875rem; margin: 0 0 1rem; }
.config-item { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem; background: white; border-radius: var(--radius-sm); margin-bottom: 0.5rem; }
`],
changeDetection: ChangeDetectionStrategy.OnPush,
})

View File

@@ -374,14 +374,14 @@ interface ChannelTypeOption {
.search-box input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-md);
}
.filter-group select {
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-md);
background: white;
}
@@ -394,13 +394,13 @@ interface ChannelTypeOption {
.channel-card {
padding: 1rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
transition: box-shadow 0.2s;
}
.channel-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
box-shadow: var(--shadow-md);
}
.channel-card.disabled {
@@ -417,7 +417,7 @@ interface ChannelTypeOption {
.channel-type-icon {
width: 40px;
height: 40px;
border-radius: 8px;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
@@ -425,11 +425,11 @@ interface ChannelTypeOption {
color: white;
}
.type-email { background: #ea4335; }
.type-slack { background: #4a154b; }
.type-teams { background: #6264a7; }
.type-webhook { background: #34a853; }
.type-pagerduty { background: #06ac38; }
.type-email { background: var(--color-status-error); }
.type-slack { background: var(--color-status-excepted); }
.type-teams { background: var(--color-status-excepted); }
.type-webhook { background: var(--color-status-success); }
.type-pagerduty { background: var(--color-status-success); }
.channel-info {
flex: 1;
@@ -438,31 +438,31 @@ interface ChannelTypeOption {
.channel-info h4 {
margin: 0;
font-size: 0.9375rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.channel-type-label {
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.health-indicator {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.625rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
}
.health-healthy { background: #dcfce7; color: #166534; }
.health-degraded { background: #fef3c7; color: #92400e; }
.health-unhealthy { background: #fef2f2; color: #991b1b; }
.health-unknown { background: #f3f4f6; color: #6b7280; }
.health-healthy { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.health-degraded { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.health-unhealthy { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.health-unknown { background: var(--color-surface-secondary); color: var(--color-text-secondary); }
.channel-description {
margin: 0 0 0.75rem;
font-size: 0.875rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.channel-target {
@@ -471,12 +471,12 @@ interface ChannelTypeOption {
}
.target-label {
color: #6b7280;
color: var(--color-text-secondary);
}
.target-value {
font-family: monospace;
color: #374151;
color: var(--color-text-primary);
}
.channel-footer {
@@ -484,18 +484,18 @@ interface ChannelTypeOption {
justify-content: space-between;
align-items: center;
padding-top: 0.75rem;
border-top: 1px solid #e5e7eb;
border-top: 1px solid var(--color-border-primary);
}
.status-badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.status-badge.enabled { background: #dcfce7; color: #166534; }
.status-badge:not(.enabled) { background: #f3f4f6; color: #6b7280; }
.status-badge.enabled { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-badge:not(.enabled) { background: var(--color-surface-secondary); color: var(--color-text-secondary); }
.action-buttons {
display: flex;
@@ -506,15 +506,15 @@ interface ChannelTypeOption {
padding: 0.25rem 0.5rem;
background: transparent;
border: none;
color: #1976d2;
color: var(--color-status-info-text);
font-size: 0.75rem;
cursor: pointer;
border-radius: 4px;
border-radius: var(--radius-sm);
}
.btn-icon:hover { background: #e3f2fd; }
.btn-icon.btn-danger { color: #dc2626; }
.btn-icon.btn-danger:hover { background: #fef2f2; }
.btn-icon:hover { background: var(--color-status-info-bg); }
.btn-icon.btn-danger { color: var(--color-status-error); }
.btn-icon.btn-danger:hover { background: var(--color-status-error-bg); }
/* Editor Styles */
.channel-editor {
@@ -531,8 +531,8 @@ interface ChannelTypeOption {
.btn-back {
padding: 0.5rem 1rem;
background: transparent;
border: 1px solid #d1d5db;
border-radius: 6px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-md);
cursor: pointer;
}
@@ -544,14 +544,14 @@ interface ChannelTypeOption {
.form-section {
margin-bottom: 1.5rem;
padding: 1rem;
background: #f9fafb;
border-radius: 8px;
background: var(--color-surface-primary);
border-radius: var(--radius-lg);
}
.form-section h4 {
margin: 0 0 1rem;
font-size: 0.9375rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.type-grid {
@@ -566,19 +566,19 @@ interface ChannelTypeOption {
align-items: center;
padding: 1rem;
background: white;
border: 2px solid #e5e7eb;
border-radius: 8px;
border: 2px solid var(--color-border-primary);
border-radius: var(--radius-lg);
cursor: pointer;
transition: all 0.2s;
text-align: center;
}
.type-card:hover { border-color: #1976d2; }
.type-card.selected { border-color: #1976d2; background: #e3f2fd; }
.type-card:hover { border-color: var(--color-status-info-text); }
.type-card.selected { border-color: var(--color-status-info-text); background: var(--color-status-info-bg); }
.type-icon { font-size: 1.5rem; margin-bottom: 0.5rem; }
.type-name { font-weight: 600; font-size: 0.875rem; }
.type-desc { font-size: 0.625rem; color: #6b7280; margin-top: 0.25rem; }
.type-name { font-weight: var(--font-weight-semibold); font-size: 0.875rem; }
.type-desc { font-size: 0.625rem; color: var(--color-text-secondary); margin-top: 0.25rem; }
.form-group {
margin-bottom: 1rem;
@@ -588,7 +588,7 @@ interface ChannelTypeOption {
display: block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.form-group input,
@@ -596,8 +596,8 @@ interface ChannelTypeOption {
.form-group textarea {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-md);
font-size: 0.875rem;
}
@@ -605,7 +605,7 @@ interface ChannelTypeOption {
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #1976d2;
border-color: var(--color-status-info-text);
}
.form-row {
@@ -626,7 +626,7 @@ interface ChannelTypeOption {
display: block;
margin-top: 0.25rem;
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.form-footer {
@@ -634,40 +634,40 @@ interface ChannelTypeOption {
justify-content: flex-end;
gap: 1rem;
padding-top: 1rem;
border-top: 1px solid #e5e7eb;
border-top: 1px solid var(--color-border-primary);
}
.btn {
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
}
.btn-primary {
background: #1976d2;
background: var(--color-status-info-text);
color: var(--color-text-heading);
border: none;
}
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.btn-secondary { background: white; color: #374151; border: 1px solid #d1d5db; }
.btn-secondary { background: white; color: var(--color-text-primary); border: 1px solid var(--color-border-secondary); }
.loading-state, .empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 3rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.spinner {
width: 32px;
height: 32px;
border: 3px solid #e5e7eb;
border-top-color: #1976d2;
border-radius: 50%;
border: 3px solid var(--color-border-primary);
border-top-color: var(--color-status-info-text);
border-radius: var(--radius-full);
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@@ -677,9 +677,9 @@ interface ChannelTypeOption {
.error-banner {
margin-top: 1rem;
padding: 0.75rem 1rem;
background: #fef2f2;
color: #991b1b;
border-radius: 6px;
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
border-radius: var(--radius-md);
}
.test-result {
@@ -691,15 +691,15 @@ interface ChannelTypeOption {
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
z-index: 1000;
}
.test-result.success { background: #dcfce7; color: #166534; }
.test-result.failure { background: #fef2f2; color: #991b1b; }
.test-result.success { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.test-result.failure { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.test-icon { font-weight: 700; }
.test-icon { font-weight: var(--font-weight-bold); }
.dismiss-btn {
margin-left: 1rem;

View File

@@ -249,8 +249,8 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
gap: 1rem;
}
.section-header h3 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: 600; }
.section-header p { margin: 0; color: #6b7280; font-size: 0.875rem; }
.section-header h3 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: var(--font-weight-semibold); }
.section-header p { margin: 0; color: var(--color-text-secondary); font-size: 0.875rem; }
.header-actions {
display: flex;
@@ -259,15 +259,15 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
.header-actions select {
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-md);
font-size: 0.875rem;
}
.btn { padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
.btn-secondary { background: white; color: #374151; border: 1px solid #d1d5db; }
.btn { padding: 0.5rem 1rem; border-radius: var(--radius-md); font-size: 0.875rem; font-weight: var(--font-weight-medium); cursor: pointer; }
.btn-secondary { background: white; color: var(--color-text-primary); border: 1px solid var(--color-border-secondary); }
.loading-state { padding: 3rem; text-align: center; color: #6b7280; }
.loading-state { padding: 3rem; text-align: center; color: var(--color-text-secondary); }
/* Key Metrics */
.key-metrics {
@@ -280,14 +280,14 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
.metric-card {
padding: 1rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
}
.metric-card.success-rate {
grid-column: span 2;
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
border-color: #86efac;
background: linear-gradient(135deg, var(--color-status-success-bg) 0%, var(--color-status-success-bg) 100%);
border-color: var(--color-status-success-border);
}
.metric-header {
@@ -300,76 +300,76 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
.metric-icon {
width: 24px;
height: 24px;
border-radius: 4px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-weight: var(--font-weight-bold);
font-size: 0.75rem;
background: #e5e7eb;
color: #374151;
background: var(--color-border-primary);
color: var(--color-text-primary);
}
.sent-icon { background: #dcfce7; color: #166534; }
.failed-icon { background: #fef2f2; color: #991b1b; }
.pending-icon { background: #fef3c7; color: #92400e; }
.throttled-icon { background: #dbeafe; color: #1e40af; }
.latency-icon { background: #f3e8ff; color: #7c3aed; }
.sent-icon { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.failed-icon { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.pending-icon { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.throttled-icon { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.latency-icon { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.metric-label {
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
text-transform: uppercase;
}
.metric-value {
font-size: 1.5rem;
font-weight: 700;
color: #1a1a2e;
font-weight: var(--font-weight-bold);
color: var(--color-surface-inverse);
margin-bottom: 0.25rem;
}
.metric-value.failed { color: #dc2626; }
.metric-value.pending { color: #d97706; }
.metric-value.throttled { color: #2563eb; }
.metric-value.failed { color: var(--color-status-error); }
.metric-value.pending { color: var(--color-status-warning-text); }
.metric-value.throttled { color: var(--color-status-info-text); }
.metric-value.rate-excellent { color: #16a34a; }
.metric-value.rate-good { color: #65a30d; }
.metric-value.rate-warning { color: #d97706; }
.metric-value.rate-critical { color: #dc2626; }
.metric-value.rate-excellent { color: var(--color-status-success); }
.metric-value.rate-good { color: var(--color-status-success); }
.metric-value.rate-warning { color: var(--color-status-warning-text); }
.metric-value.rate-critical { color: var(--color-status-error); }
.metric-bar {
height: 6px;
background: #e5e7eb;
border-radius: 3px;
background: var(--color-border-primary);
border-radius: var(--radius-sm);
overflow: hidden;
}
.bar-fill {
height: 100%;
background: linear-gradient(90deg, #16a34a 0%, #22c55e 100%);
border-radius: 3px;
background: linear-gradient(90deg, var(--color-status-success) 0%, var(--color-status-success) 100%);
border-radius: var(--radius-sm);
transition: width 0.5s ease;
}
.metric-trend {
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
}
/* Analytics Sections */
.analytics-section {
padding: 1rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
margin-bottom: 1rem;
}
.analytics-section h4 {
margin: 0 0 1rem;
font-size: 0.9375rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
/* Breakdown */
@@ -381,8 +381,8 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
.breakdown-item {
padding: 0.75rem;
background: #f9fafb;
border-radius: 6px;
background: var(--color-surface-primary);
border-radius: var(--radius-md);
}
.breakdown-header {
@@ -393,31 +393,31 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
}
.channel-name, .event-name {
font-weight: 500;
font-weight: var(--font-weight-medium);
font-size: 0.875rem;
}
.channel-rate, .event-rate {
font-weight: 600;
font-weight: var(--font-weight-semibold);
font-size: 0.875rem;
}
.breakdown-bar {
height: 8px;
background: #e5e7eb;
border-radius: 4px;
background: var(--color-border-primary);
border-radius: var(--radius-sm);
overflow: hidden;
display: flex;
margin-bottom: 0.5rem;
}
.bar-sent {
background: #22c55e;
background: var(--color-status-success);
transition: width 0.3s ease;
}
.bar-failed {
background: #ef4444;
background: var(--color-status-error);
transition: width 0.3s ease;
}
@@ -428,11 +428,11 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
.breakdown-stats .stat {
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.breakdown-stats .stat.sent::before { content: ''; display: inline-block; width: 8px; height: 8px; background: #22c55e; border-radius: 2px; margin-right: 0.25rem; }
.breakdown-stats .stat.failed::before { content: ''; display: inline-block; width: 8px; height: 8px; background: #ef4444; border-radius: 2px; margin-right: 0.25rem; }
.breakdown-stats .stat.sent::before { content: ''; display: inline-block; width: 8px; height: 8px; background: var(--color-status-success); border-radius: var(--radius-sm); margin-right: 0.25rem; }
.breakdown-stats .stat.failed::before { content: ''; display: inline-block; width: 8px; height: 8px; background: var(--color-status-error); border-radius: var(--radius-sm); margin-right: 0.25rem; }
/* Failures List */
.failures-list {
@@ -443,9 +443,9 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
.failure-item {
padding: 0.75rem;
background: #fef2f2;
border: 1px solid #fecaca;
border-radius: 6px;
background: var(--color-status-error-bg);
border: 1px solid var(--color-status-error-border);
border-radius: var(--radius-md);
}
.failure-header {
@@ -455,25 +455,25 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
}
.failure-channel {
font-weight: 500;
font-weight: var(--font-weight-medium);
font-size: 0.875rem;
}
.failure-time {
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.failure-target {
font-family: monospace;
font-size: 0.8125rem;
color: #374151;
color: var(--color-text-primary);
margin-bottom: 0.25rem;
}
.failure-error {
font-size: 0.8125rem;
color: #991b1b;
color: var(--color-status-error-text);
margin-bottom: 0.5rem;
}
@@ -484,11 +484,11 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
.meta-item {
font-size: 0.6875rem;
color: #6b7280;
color: var(--color-text-secondary);
}
/* Health Summary */
.health-summary { background: #f9fafb; }
.health-summary { background: var(--color-surface-primary); }
.health-grid {
display: grid;
@@ -502,13 +502,13 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
align-items: center;
padding: 1rem;
background: white;
border-radius: 8px;
border-radius: var(--radius-lg);
border: 2px solid;
}
.health-item.healthy { border-color: #22c55e; }
.health-item.warning { border-color: #f59e0b; }
.health-item.critical { border-color: #ef4444; }
.health-item.healthy { border-color: var(--color-status-success); }
.health-item.warning { border-color: var(--color-status-warning); }
.health-item.critical { border-color: var(--color-status-error); }
.health-icon {
font-size: 1.5rem;
@@ -517,33 +517,33 @@ import { NotifierDeliveryStats, NotifierDelivery } from '../../../core/api/notif
.health-label {
font-size: 0.6875rem;
color: #6b7280;
color: var(--color-text-secondary);
text-transform: uppercase;
margin-bottom: 0.25rem;
}
.health-status {
font-size: 0.875rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.health-item.healthy .health-status { color: #16a34a; }
.health-item.warning .health-status { color: #d97706; }
.health-item.critical .health-status { color: #dc2626; }
.health-item.healthy .health-status { color: var(--color-status-success); }
.health-item.warning .health-status { color: var(--color-status-warning-text); }
.health-item.critical .health-status { color: var(--color-status-error); }
.no-data {
padding: 2rem;
text-align: center;
color: #6b7280;
color: var(--color-text-secondary);
font-size: 0.875rem;
}
.error-banner {
margin-top: 1rem;
padding: 0.75rem 1rem;
background: #fef2f2;
color: #991b1b;
border-radius: 6px;
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
border-radius: var(--radius-md);
}
@media (max-width: 600px) {

View File

@@ -329,7 +329,7 @@ import {
margin-bottom: 1.5rem;
padding: 1rem;
background: var(--color-surface-secondary);
border-radius: 8px;
border-radius: var(--radius-lg);
flex-wrap: wrap;
}
@@ -342,18 +342,18 @@ import {
.stat-value {
font-size: 1.5rem;
font-weight: 700;
font-weight: var(--font-weight-bold);
}
.stat-value.sent { color: #16a34a; }
.stat-value.failed { color: #dc2626; }
.stat-value.pending { color: #d97706; }
.stat-value.throttled { color: #2563eb; }
.stat-value.sent { color: var(--color-status-success); }
.stat-value.failed { color: var(--color-status-error); }
.stat-value.pending { color: var(--color-status-warning-text); }
.stat-value.throttled { color: var(--color-status-info-text); }
.stat-value.rate { color: var(--color-brand-secondary); }
.stat-label {
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
text-transform: uppercase;
}
@@ -373,26 +373,26 @@ import {
.filter-group label {
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.filter-group select {
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-md);
background: white;
min-width: 150px;
}
.btn {
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
cursor: pointer;
}
.btn-primary { background: #1976d2; color: var(--color-text-heading); border: none; }
.btn-secondary { background: white; color: #374151; border: 1px solid #d1d5db; }
.btn-primary { background: var(--color-status-info-text); color: var(--color-text-heading); border: none; }
.btn-secondary { background: white; color: var(--color-text-primary); border: 1px solid var(--color-border-secondary); }
.table-container {
overflow-x: auto;
@@ -407,23 +407,23 @@ import {
.data-table td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #e5e7eb;
border-bottom: 1px solid var(--color-border-primary);
}
.data-table th {
font-size: 0.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
color: #6b7280;
background: #f9fafb;
color: var(--color-text-secondary);
background: var(--color-surface-primary);
}
.data-table tbody tr:hover {
background: #f9fafb;
background: var(--color-surface-primary);
}
.status-row-failed { background: #fef2f2; }
.status-row-failed:hover { background: #fee2e2 !important; }
.status-row-failed { background: var(--color-status-error-bg); }
.status-row-failed:hover { background: var(--color-status-error-bg) !important; }
.timestamp-cell {
font-size: 0.8125rem;
@@ -433,25 +433,25 @@ import {
.status-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
}
.status-sent { background: #dcfce7; color: #166534; }
.status-failed { background: #fef2f2; color: #991b1b; }
.status-pending { background: #fef3c7; color: #92400e; }
.status-throttled { background: #dbeafe; color: #1e40af; }
.status-retrying { background: #fae8ff; color: #86198f; }
.status-digested { background: #e0e7ff; color: #3730a3; }
.status-sent { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-failed { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.status-pending { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.status-throttled { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.status-retrying { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.status-digested { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.event-badge {
display: inline-block;
padding: 0.125rem 0.5rem;
background: #e0f2fe;
color: #0369a1;
border-radius: 4px;
background: var(--color-status-info-bg);
color: var(--color-status-info-text);
border-radius: var(--radius-sm);
font-size: 0.75rem;
}
@@ -469,13 +469,13 @@ import {
}
.attempt-count {
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.retry-indicator {
margin-left: 0.25rem;
font-size: 0.75rem;
color: #d97706;
color: var(--color-status-warning-text);
}
.details-cell {
@@ -483,17 +483,17 @@ import {
}
.error-text {
color: #dc2626;
color: var(--color-status-error);
font-size: 0.8125rem;
}
.subject-text {
font-size: 0.8125rem;
color: #374151;
color: var(--color-text-primary);
}
.text-muted {
color: #9ca3af;
color: var(--color-text-muted);
}
.action-buttons {
@@ -505,16 +505,16 @@ import {
padding: 0.25rem 0.5rem;
background: transparent;
border: none;
color: #1976d2;
color: var(--color-status-info-text);
font-size: 0.75rem;
cursor: pointer;
border-radius: 4px;
border-radius: var(--radius-sm);
}
.btn-icon:hover { background: #e3f2fd; }
.btn-icon:hover { background: var(--color-status-info-bg); }
.btn-icon:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-retry { color: #d97706; }
.btn-retry:hover { background: #fef3c7; }
.btn-retry { color: var(--color-status-warning-text); }
.btn-retry:hover { background: var(--color-status-warning-bg); }
.pagination {
display: flex;
@@ -522,12 +522,12 @@ import {
align-items: center;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #e5e7eb;
border-top: 1px solid var(--color-border-primary);
}
.page-info {
font-size: 0.875rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.loading-state, .empty-state {
@@ -535,15 +535,15 @@ import {
flex-direction: column;
align-items: center;
padding: 3rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.spinner {
width: 32px;
height: 32px;
border: 3px solid #e5e7eb;
border-top-color: #1976d2;
border-radius: 50%;
border: 3px solid var(--color-border-primary);
border-top-color: var(--color-status-info-text);
border-radius: var(--radius-full);
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@@ -552,7 +552,7 @@ import {
.hint {
font-size: 0.875rem;
color: #9ca3af;
color: var(--color-text-muted);
}
/* Modal Styles */
@@ -568,7 +568,7 @@ import {
.modal-content {
background: white;
border-radius: 8px;
border-radius: var(--radius-lg);
width: 90%;
max-width: 700px;
max-height: 90vh;
@@ -582,7 +582,7 @@ import {
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
border-bottom: 1px solid #e5e7eb;
border-bottom: 1px solid var(--color-border-primary);
}
.modal-header h3 {
@@ -594,10 +594,10 @@ import {
width: 32px;
height: 32px;
border: none;
background: #f3f4f6;
border-radius: 4px;
background: var(--color-surface-secondary);
border-radius: var(--radius-sm);
cursor: pointer;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.modal-body {
@@ -616,7 +616,7 @@ import {
.detail-item label {
display: block;
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
margin-bottom: 0.25rem;
}
@@ -632,15 +632,15 @@ import {
.detail-section {
margin-bottom: 1rem;
padding: 1rem;
background: #f9fafb;
border-radius: 6px;
background: var(--color-surface-primary);
border-radius: var(--radius-md);
}
.detail-section label {
display: block;
font-size: 0.75rem;
font-weight: 600;
color: #6b7280;
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
margin-bottom: 0.5rem;
}
@@ -656,8 +656,8 @@ import {
font-family: monospace;
}
.error-section { background: #fef2f2; }
.error-message { color: #991b1b; }
.error-section { background: var(--color-status-error-bg); }
.error-message { color: var(--color-status-error-text); }
.attempts-timeline {
display: flex;
@@ -668,13 +668,13 @@ import {
.attempt-item {
padding: 0.75rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
}
.attempt-item.attempt-success { border-left: 3px solid #16a34a; }
.attempt-item.attempt-failure { border-left: 3px solid #dc2626; }
.attempt-item.attempt-timeout { border-left: 3px solid #d97706; }
.attempt-item.attempt-success { border-left: 3px solid var(--color-status-success); }
.attempt-item.attempt-failure { border-left: 3px solid var(--color-status-error); }
.attempt-item.attempt-timeout { border-left: 3px solid var(--color-status-warning-text); }
.attempt-header {
display: flex;
@@ -683,27 +683,27 @@ import {
margin-bottom: 0.5rem;
}
.attempt-number { font-weight: 600; }
.attempt-time { font-size: 0.8125rem; color: #6b7280; }
.attempt-number { font-weight: var(--font-weight-semibold); }
.attempt-time { font-size: 0.8125rem; color: var(--color-text-secondary); }
.attempt-status { font-size: 0.75rem; text-transform: uppercase; }
.attempt-code { font-family: monospace; font-size: 0.8125rem; }
.attempt-duration { font-size: 0.8125rem; color: #6b7280; }
.attempt-error { margin: 0.5rem 0 0; color: #dc2626; font-size: 0.8125rem; }
.attempt-duration { font-size: 0.8125rem; color: var(--color-text-secondary); }
.attempt-error { margin: 0.5rem 0 0; color: var(--color-status-error); font-size: 0.8125rem; }
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 1rem;
padding: 1rem 1.5rem;
border-top: 1px solid #e5e7eb;
border-top: 1px solid var(--color-border-primary);
}
.error-banner {
margin-top: 1rem;
padding: 0.75rem 1rem;
background: #fef2f2;
color: #991b1b;
border-radius: 6px;
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
border-radius: var(--radius-md);
}
@media (max-width: 768px) {

View File

@@ -246,24 +246,24 @@ import {
margin-bottom: 1.5rem;
}
.section-header h3 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: 600; }
.section-header p { margin: 0; color: #6b7280; font-size: 0.875rem; }
.section-header h3 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: var(--font-weight-semibold); }
.section-header p { margin: 0; color: var(--color-text-secondary); font-size: 0.875rem; }
.btn { padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
.btn-primary { background: #1976d2; color: var(--color-text-heading); border: none; }
.btn { padding: 0.5rem 1rem; border-radius: var(--radius-md); font-size: 0.875rem; font-weight: var(--font-weight-medium); cursor: pointer; }
.btn-primary { background: var(--color-status-info-text); color: var(--color-text-heading); border: none; }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.btn-secondary { background: white; color: #374151; border: 1px solid #d1d5db; }
.btn-secondary { background: white; color: var(--color-text-primary); border: 1px solid var(--color-border-secondary); }
.btn-sm { padding: 0.375rem 0.75rem; font-size: 0.75rem; }
.btn-icon { padding: 0.25rem 0.5rem; background: transparent; border: none; color: #1976d2; font-size: 0.75rem; cursor: pointer; }
.btn-icon.btn-danger { color: #dc2626; }
.btn-icon { padding: 0.25rem 0.5rem; background: transparent; border: none; color: var(--color-status-info-text); font-size: 0.75rem; cursor: pointer; }
.btn-icon.btn-danger { color: var(--color-status-error); }
.policies-list { display: flex; flex-direction: column; gap: 1rem; }
.policy-card {
padding: 1rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
}
.policy-card.disabled { opacity: 0.7; }
@@ -279,21 +279,21 @@ import {
.status-badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.status-badge.enabled { background: #dcfce7; color: #166534; }
.status-badge:not(.enabled) { background: #f3f4f6; color: #6b7280; }
.status-badge.enabled { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-badge:not(.enabled) { background: var(--color-surface-secondary); color: var(--color-text-secondary); }
.card-description { margin: 0 0 1rem; color: #6b7280; font-size: 0.875rem; }
.card-description { margin: 0 0 1rem; color: var(--color-text-secondary); font-size: 0.875rem; }
/* Escalation Timeline */
.escalation-timeline {
padding: 1rem;
background: #f9fafb;
border-radius: 6px;
background: var(--color-surface-primary);
border-radius: var(--radius-md);
margin-bottom: 1rem;
}
@@ -310,8 +310,8 @@ import {
flex-shrink: 0;
width: 32px;
height: 32px;
background: #1976d2;
border-radius: 50%;
background: var(--color-status-info-text);
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
@@ -320,7 +320,7 @@ import {
.level-number {
color: white;
font-weight: 700;
font-weight: var(--font-weight-bold);
font-size: 0.875rem;
}
@@ -330,7 +330,7 @@ import {
top: 32px;
width: 2px;
height: calc(100% - 32px);
background: #d1d5db;
background: var(--color-border-secondary);
}
.timeline-content { flex: 1; }
@@ -341,16 +341,16 @@ import {
margin-bottom: 0.5rem;
}
.level-title { font-weight: 600; font-size: 0.875rem; }
.level-delay { font-size: 0.75rem; color: #6b7280; }
.level-title { font-weight: var(--font-weight-semibold); font-size: 0.875rem; }
.level-delay { font-size: 0.75rem; color: var(--color-text-secondary); }
.level-channels { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 0.5rem; }
.channel-badge {
padding: 0.125rem 0.5rem;
background: #e0f2fe;
color: #0369a1;
border-radius: 4px;
background: var(--color-status-info-bg);
color: var(--color-status-info-text);
border-radius: var(--radius-sm);
font-size: 0.75rem;
}
@@ -358,41 +358,41 @@ import {
.option-badge {
padding: 0.125rem 0.375rem;
background: #f3f4f6;
color: #6b7280;
border-radius: 4px;
background: var(--color-surface-secondary);
color: var(--color-text-secondary);
border-radius: var(--radius-sm);
font-size: 0.6875rem;
}
.card-actions { display: flex; gap: 0.5rem; padding-top: 0.75rem; border-top: 1px solid #e5e7eb; }
.card-actions { display: flex; gap: 0.5rem; padding-top: 0.75rem; border-top: 1px solid var(--color-border-primary); }
.loading-state, .empty-state { padding: 3rem; text-align: center; color: #6b7280; }
.empty-state .hint { font-size: 0.875rem; color: #9ca3af; }
.loading-state, .empty-state { padding: 3rem; text-align: center; color: var(--color-text-secondary); }
.empty-state .hint { font-size: 0.875rem; color: var(--color-text-muted); }
/* Edit Form */
.edit-form { max-width: 700px; }
.form-section { margin-bottom: 1.5rem; padding: 1rem; background: #f9fafb; border-radius: 8px; }
.form-section h4 { margin: 0 0 0.75rem; font-size: 0.9375rem; font-weight: 600; }
.section-desc { margin: 0 0 0.75rem; font-size: 0.875rem; color: #6b7280; }
.form-section { margin-bottom: 1.5rem; padding: 1rem; background: var(--color-surface-primary); border-radius: var(--radius-lg); }
.form-section h4 { margin: 0 0 0.75rem; font-size: 0.9375rem; font-weight: var(--font-weight-semibold); }
.section-desc { margin: 0 0 0.75rem; font-size: 0.875rem; color: var(--color-text-secondary); }
.form-group { margin-bottom: 1rem; }
.form-group label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: 500; }
.form-group label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: var(--font-weight-medium); }
.form-group input, .form-group select, .form-group textarea {
width: 100%; padding: 0.5rem 0.75rem; border: 1px solid #d1d5db; border-radius: 6px; font-size: 0.875rem;
width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--color-border-secondary); border-radius: var(--radius-md); font-size: 0.875rem;
}
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.checkbox-label { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; font-weight: normal; }
.help-text { display: block; margin-top: 0.25rem; font-size: 0.75rem; color: #6b7280; }
.help-text { display: block; margin-top: 0.25rem; font-size: 0.75rem; color: var(--color-text-secondary); }
.level-form {
padding: 1rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
margin-bottom: 1rem;
}
@@ -405,11 +405,11 @@ import {
.level-badge {
padding: 0.25rem 0.5rem;
background: #1976d2;
background: var(--color-status-info-text);
color: white;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.channels-selector {
@@ -423,19 +423,19 @@ import {
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
background: #f9fafb;
border: 1px solid #e5e7eb;
border-radius: 6px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.2s;
}
.channel-checkbox:hover { border-color: #1976d2; }
.channel-checkbox:has(input:checked) { background: #e3f2fd; border-color: #1976d2; }
.channel-checkbox:hover { border-color: var(--color-status-info-text); }
.channel-checkbox:has(input:checked) { background: var(--color-status-info-bg); border-color: var(--color-status-info-text); }
.channel-option { display: flex; flex-direction: column; }
.channel-name { font-size: 0.875rem; font-weight: 500; }
.channel-type { font-size: 0.6875rem; color: #6b7280; }
.channel-name { font-size: 0.875rem; font-weight: var(--font-weight-medium); }
.channel-type { font-size: 0.6875rem; color: var(--color-text-secondary); }
/* Preview Timeline */
.preview-timeline {
@@ -444,8 +444,8 @@ import {
gap: 0.5rem;
padding: 1rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
}
.preview-step {
@@ -457,27 +457,27 @@ import {
.preview-time {
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
margin-bottom: 0.25rem;
}
.preview-marker {
width: 12px;
height: 12px;
background: #1976d2;
border-radius: 50%;
background: var(--color-status-info-text);
border-radius: var(--radius-full);
margin-bottom: 0.25rem;
}
.preview-content {
font-size: 0.75rem;
text-align: center;
color: #374151;
color: var(--color-text-primary);
}
.form-footer { display: flex; justify-content: flex-end; gap: 1rem; padding-top: 1rem; border-top: 1px solid #e5e7eb; }
.form-footer { display: flex; justify-content: flex-end; gap: 1rem; padding-top: 1rem; border-top: 1px solid var(--color-border-primary); }
.error-banner { margin-top: 1rem; padding: 0.75rem 1rem; background: #fef2f2; color: #991b1b; border-radius: 6px; }
.error-banner { margin-top: 1rem; padding: 0.75rem 1rem; background: var(--color-status-error-bg); color: var(--color-status-error-text); border-radius: var(--radius-md); }
@media (max-width: 600px) {
.form-row { grid-template-columns: 1fr; }

View File

@@ -178,13 +178,13 @@ interface ConfigSubTab {
.header-content h1 {
margin: 0;
font-size: 1.75rem;
font-weight: 600;
color: #1a1a2e;
font-weight: var(--font-weight-semibold);
color: var(--color-surface-inverse);
}
.subtitle {
margin: 0.25rem 0 0;
color: #666;
color: var(--color-text-secondary);
font-size: 0.9375rem;
}
@@ -211,33 +211,33 @@ interface ConfigSubTab {
gap: 0.75rem;
padding: 1rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
transition: box-shadow 0.2s, transform 0.2s;
}
.stat-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
box-shadow: var(--shadow-md);
transform: translateY(-1px);
}
.stat-icon {
width: 40px;
height: 40px;
border-radius: 8px;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-weight: var(--font-weight-bold);
font-size: 1rem;
color: white;
}
.rules-icon { background: #3b82f6; }
.channels-icon { background: #8b5cf6; }
.sent-icon { background: #10b981; }
.failed-icon { background: #ef4444; }
.pending-icon { background: #f59e0b; }
.rules-icon { background: var(--color-status-info); }
.channels-icon { background: var(--color-status-excepted); }
.sent-icon { background: var(--color-status-success); }
.failed-icon { background: var(--color-status-error); }
.pending-icon { background: var(--color-status-warning); }
.rate-icon { background: var(--color-brand-secondary); }
.stat-content {
@@ -247,14 +247,14 @@ interface ConfigSubTab {
.stat-value {
font-size: 1.5rem;
font-weight: 700;
color: #1a1a2e;
font-weight: var(--font-weight-bold);
color: var(--color-surface-inverse);
line-height: 1.2;
}
.stat-label {
font-size: 0.75rem;
color: #666;
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.025em;
}
@@ -263,7 +263,7 @@ interface ConfigSubTab {
.tab-navigation {
display: flex;
gap: 0.25rem;
border-bottom: 1px solid #e5e7eb;
border-bottom: 1px solid var(--color-border-primary);
margin-bottom: 1.5rem;
overflow-x: auto;
scrollbar-width: thin;
@@ -276,9 +276,9 @@ interface ConfigSubTab {
padding: 0.75rem 1rem;
border: none;
background: transparent;
color: #666;
color: var(--color-text-secondary);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.2s;
@@ -286,13 +286,13 @@ interface ConfigSubTab {
}
.tab-button:hover {
color: #1976d2;
color: var(--color-status-info-text);
background: var(--color-surface-secondary);
}
.tab-button.active {
color: #1976d2;
border-bottom-color: #1976d2;
color: var(--color-status-info-text);
border-bottom-color: var(--color-status-info-text);
}
.tab-icon {
@@ -304,8 +304,8 @@ interface ConfigSubTab {
display: flex;
gap: 0.25rem;
padding: 0.5rem 1rem;
background: #f9fafb;
border-bottom: 1px solid #e5e7eb;
background: var(--color-surface-primary);
border-bottom: 1px solid var(--color-border-primary);
margin-bottom: 0;
}
@@ -313,10 +313,10 @@ interface ConfigSubTab {
padding: 0.5rem 1rem;
background: transparent;
border: 1px solid transparent;
border-radius: 6px;
color: #6b7280;
border-radius: var(--radius-md);
color: var(--color-text-secondary);
font-size: 0.8125rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
@@ -324,21 +324,21 @@ interface ConfigSubTab {
.sub-tab-button:hover {
background: white;
color: #1976d2;
color: var(--color-status-info-text);
}
.sub-tab-button.active {
background: white;
color: #1976d2;
border-color: #e5e7eb;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
color: var(--color-status-info-text);
border-color: var(--color-border-primary);
box-shadow: var(--shadow-sm);
}
/* Tab Content */
.tab-content {
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
min-height: 400px;
}
@@ -357,14 +357,14 @@ interface ConfigSubTab {
.section-header h2 {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
flex: 0 0 auto;
}
.section-header p {
flex: 1 1 100%;
margin: 0;
color: #666;
color: var(--color-text-secondary);
font-size: 0.875rem;
order: 3;
}
@@ -379,34 +379,34 @@ interface ConfigSubTab {
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
transition: all 0.2s;
border: 1px solid transparent;
}
.btn-primary {
background: #1976d2;
background: var(--color-status-info-text);
color: var(--color-text-heading);
border-color: #1976d2;
border-color: var(--color-status-info-text);
}
.btn-primary:hover {
background: #1565c0;
border-color: #1565c0;
background: var(--color-status-info-text);
border-color: var(--color-status-info-text);
}
.btn-secondary {
background: white;
color: #374151;
border-color: #d1d5db;
color: var(--color-text-primary);
border-color: var(--color-border-secondary);
}
.btn-secondary:hover {
background: #f9fafb;
border-color: #9ca3af;
background: var(--color-surface-primary);
border-color: var(--color-text-muted);
}
/* Error Banner */
@@ -419,24 +419,24 @@ interface ConfigSubTab {
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: #fef2f2;
border: 1px solid #fecaca;
border-radius: 8px;
color: #991b1b;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background: var(--color-status-error-bg);
border: 1px solid var(--color-status-error-border);
border-radius: var(--radius-lg);
color: var(--color-status-error-text);
box-shadow: var(--shadow-lg);
z-index: 1000;
}
.error-icon {
width: 24px;
height: 24px;
border-radius: 50%;
background: #ef4444;
border-radius: var(--radius-full);
background: var(--color-status-error);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-weight: var(--font-weight-bold);
font-size: 0.875rem;
}
@@ -449,7 +449,7 @@ interface ConfigSubTab {
padding: 0.25rem 0.5rem;
border: none;
background: transparent;
color: #991b1b;
color: var(--color-status-error-text);
font-size: 0.75rem;
cursor: pointer;
text-decoration: underline;

View File

@@ -119,8 +119,8 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
styles: [`
.notification-preview {
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
overflow: hidden;
}
@@ -129,8 +129,8 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
justify-content: space-between;
align-items: center;
padding: 0.75rem 1rem;
background: #f9fafb;
border-bottom: 1px solid #e5e7eb;
background: var(--color-surface-primary);
border-bottom: 1px solid var(--color-border-primary);
}
.channel-info {
@@ -142,33 +142,33 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
.channel-icon {
width: 28px;
height: 28px;
border-radius: 4px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-weight: var(--font-weight-bold);
font-size: 0.75rem;
color: white;
}
.type-email { background: #ea4335; }
.type-slack { background: #4a154b; }
.type-teams { background: #6264a7; }
.type-webhook { background: #34a853; }
.type-pagerduty { background: #06ac38; }
.type-email { background: var(--color-status-error); }
.type-slack { background: var(--color-status-excepted); }
.type-teams { background: var(--color-status-excepted); }
.type-webhook { background: var(--color-status-success); }
.type-pagerduty { background: var(--color-status-success); }
.channel-type {
font-weight: 600;
font-weight: var(--font-weight-semibold);
font-size: 0.875rem;
}
.format-badge {
padding: 0.125rem 0.5rem;
background: #e0f2fe;
color: #0369a1;
border-radius: 4px;
background: var(--color-status-info-bg);
color: var(--color-status-info-text);
border-radius: var(--radius-sm);
font-size: 0.625rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
}
@@ -176,10 +176,10 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
width: 24px;
height: 24px;
border: none;
background: #f3f4f6;
border-radius: 4px;
background: var(--color-surface-secondary);
border-radius: var(--radius-sm);
cursor: pointer;
font-weight: 600;
font-weight: var(--font-weight-semibold);
font-size: 0.75rem;
}
@@ -190,15 +190,15 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
/* Email Preview */
.email-preview {
border: 1px solid #e5e7eb;
border-radius: 4px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-sm);
overflow: hidden;
}
.email-header {
padding: 0.75rem 1rem;
background: #f9fafb;
border-bottom: 1px solid #e5e7eb;
background: var(--color-surface-primary);
border-bottom: 1px solid var(--color-border-primary);
}
.email-field {
@@ -207,12 +207,12 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
}
.email-field label {
color: #6b7280;
color: var(--color-text-secondary);
font-size: 0.875rem;
}
.email-field span {
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.email-body {
@@ -231,8 +231,8 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
/* Slack Preview */
.slack-preview {
background: #f8f8f8;
border-radius: 4px;
background: var(--color-surface-primary);
border-radius: var(--radius-sm);
padding: 1rem;
}
@@ -244,13 +244,13 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
.slack-avatar {
width: 36px;
height: 36px;
background: #1976d2;
background: var(--color-status-info-text);
color: white;
border-radius: 4px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-weight: var(--font-weight-bold);
font-size: 0.75rem;
flex-shrink: 0;
}
@@ -267,12 +267,12 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
}
.slack-username {
font-weight: 700;
font-weight: var(--font-weight-bold);
font-size: 0.9375rem;
}
.slack-time {
color: #6b7280;
color: var(--color-text-secondary);
font-size: 0.75rem;
}
@@ -285,22 +285,22 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
/* Teams Preview */
.teams-preview {
background: #f5f5f5;
background: var(--color-surface-secondary);
padding: 1rem;
border-radius: 4px;
border-radius: var(--radius-sm);
}
.teams-card {
display: flex;
background: white;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
border-radius: var(--radius-sm);
box-shadow: var(--shadow-sm);
overflow: hidden;
}
.teams-accent {
width: 4px;
background: #6264a7;
background: var(--color-status-excepted);
}
.teams-content {
@@ -311,7 +311,7 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
.teams-title {
margin: 0 0 0.5rem;
font-size: 1rem;
color: #252423;
color: var(--color-surface-inverse);
}
.teams-body pre {
@@ -322,22 +322,22 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
/* JSON Preview */
.json-preview {
background: #1e1e1e;
border-radius: 4px;
background: var(--color-surface-inverse);
border-radius: var(--radius-sm);
overflow: hidden;
}
.json-header {
padding: 0.5rem 1rem;
background: #2d2d2d;
color: #9ca3af;
background: var(--color-surface-inverse);
color: var(--color-text-muted);
font-size: 0.75rem;
}
.json-body {
margin: 0;
padding: 1rem;
color: #d4d4d4;
color: var(--color-border-primary);
font-family: 'Fira Code', 'Consolas', monospace;
font-size: 0.8125rem;
white-space: pre-wrap;
@@ -347,15 +347,15 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
/* Variables Section */
.variables-section {
padding: 1rem;
background: #f9fafb;
border-top: 1px solid #e5e7eb;
background: var(--color-surface-primary);
border-top: 1px solid var(--color-border-primary);
}
.variables-section h4 {
margin: 0 0 0.75rem;
font-size: 0.75rem;
font-weight: 600;
color: #6b7280;
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
text-transform: uppercase;
}
@@ -370,21 +370,21 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
justify-content: space-between;
padding: 0.5rem;
background: white;
border-radius: 4px;
border: 1px solid #e5e7eb;
border-radius: var(--radius-sm);
border: 1px solid var(--color-border-primary);
}
.var-key {
font-size: 0.8125rem;
color: #9333ea;
background: #faf5ff;
color: var(--color-status-excepted);
background: var(--color-status-excepted-bg);
padding: 0.125rem 0.25rem;
border-radius: 2px;
border-radius: var(--radius-sm);
}
.var-value {
font-size: 0.8125rem;
color: #374151;
color: var(--color-text-primary);
font-family: monospace;
}
@@ -392,10 +392,10 @@ import { NotifierPreviewResponse, NotifierChannelType } from '../../../core/api/
display: flex;
justify-content: space-between;
padding: 0.5rem 1rem;
background: #f9fafb;
border-top: 1px solid #e5e7eb;
background: var(--color-surface-primary);
border-top: 1px solid var(--color-border-primary);
font-size: 0.6875rem;
color: #9ca3af;
color: var(--color-text-muted);
font-family: monospace;
}
`],

View File

@@ -271,32 +271,32 @@ import {
.editor-header h2 {
margin: 0 0 0.25rem;
font-size: 1.5rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.subtitle {
margin: 0;
color: #6b7280;
color: var(--color-text-secondary);
}
.form-section {
margin-bottom: 2rem;
padding: 1.5rem;
background: #f9fafb;
border-radius: 8px;
background: var(--color-surface-primary);
border-radius: var(--radius-lg);
}
.form-section h3 {
margin: 0 0 0.5rem;
font-size: 1rem;
font-weight: 600;
color: #374151;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.section-description {
margin: 0 0 1rem;
font-size: 0.875rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.form-group {
@@ -307,8 +307,8 @@ import {
display: block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
color: #374151;
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
}
.form-group input[type="text"],
@@ -317,8 +317,8 @@ import {
.form-group textarea {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-md);
font-size: 0.875rem;
transition: border-color 0.2s;
}
@@ -327,8 +327,8 @@ import {
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #1976d2;
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1);
border-color: var(--color-status-info-text);
box-shadow: 0 0 0 2px var(--color-focus-ring);
}
.form-group textarea {
@@ -365,22 +365,22 @@ import {
display: block;
margin-top: 0.25rem;
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.error-text {
display: block;
margin-top: 0.25rem;
font-size: 0.75rem;
color: #dc2626;
color: var(--color-status-error);
}
.action-card {
padding: 1rem;
margin-bottom: 1rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
}
.action-header {
@@ -391,27 +391,27 @@ import {
}
.action-number {
font-weight: 600;
color: #374151;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.btn {
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: #1976d2;
background: var(--color-status-info-text);
color: var(--color-text-heading);
border: none;
}
.btn-primary:hover:not(:disabled) {
background: #1565c0;
background: var(--color-status-info-text);
}
.btn-primary:disabled {
@@ -421,25 +421,25 @@ import {
.btn-secondary {
background: white;
color: #374151;
border: 1px solid #d1d5db;
color: var(--color-text-primary);
border: 1px solid var(--color-border-secondary);
}
.btn-secondary:hover {
background: #f9fafb;
background: var(--color-surface-primary);
}
.btn-icon {
padding: 0.25rem 0.5rem;
background: transparent;
border: none;
color: #1976d2;
color: var(--color-status-info-text);
font-size: 0.75rem;
cursor: pointer;
}
.btn-icon.btn-danger {
color: #dc2626;
color: var(--color-status-error);
}
.form-footer {
@@ -447,15 +447,15 @@ import {
justify-content: flex-end;
gap: 1rem;
padding-top: 1rem;
border-top: 1px solid #e5e7eb;
border-top: 1px solid var(--color-border-primary);
}
.error-banner {
margin-top: 1rem;
padding: 0.75rem 1rem;
background: #fef2f2;
color: #991b1b;
border-radius: 6px;
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
border-radius: var(--radius-md);
}
@media (max-width: 640px) {

View File

@@ -203,54 +203,54 @@ import { NotifierRule, NotifierRuleStatus, NotifierSeverity } from '../../../cor
.search-box input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-md);
font-size: 0.875rem;
}
.search-box input:focus {
outline: none;
border-color: #1976d2;
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1);
border-color: var(--color-status-info-text);
box-shadow: 0 0 0 2px var(--color-focus-ring);
}
.filter-group select {
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-md);
font-size: 0.875rem;
background: white;
}
.btn {
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: #1976d2;
background: var(--color-status-info-text);
color: var(--color-text-heading);
border: none;
}
.btn-secondary {
background: white;
color: #374151;
border: 1px solid #d1d5db;
color: var(--color-text-primary);
border: 1px solid var(--color-border-secondary);
}
.btn-text {
background: transparent;
color: #1976d2;
color: var(--color-status-info-text);
border: none;
}
.btn-text:disabled {
color: #9ca3af;
color: var(--color-text-muted);
cursor: not-allowed;
}
@@ -262,15 +262,15 @@ import { NotifierRule, NotifierRuleStatus, NotifierSeverity } from '../../../cor
justify-content: center;
padding: 3rem;
text-align: center;
color: #666;
color: var(--color-text-secondary);
}
.spinner {
width: 32px;
height: 32px;
border: 3px solid #e5e7eb;
border-top-color: #1976d2;
border-radius: 50%;
border: 3px solid var(--color-border-primary);
border-top-color: var(--color-status-info-text);
border-radius: var(--radius-full);
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@@ -285,7 +285,7 @@ import { NotifierRule, NotifierRuleStatus, NotifierSeverity } from '../../../cor
.empty-state .hint {
font-size: 0.875rem;
color: #9ca3af;
color: var(--color-text-muted);
}
.table-container {
@@ -301,19 +301,19 @@ import { NotifierRule, NotifierRuleStatus, NotifierSeverity } from '../../../cor
.data-table td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #e5e7eb;
border-bottom: 1px solid var(--color-border-primary);
}
.data-table th {
font-size: 0.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
color: #6b7280;
background: #f9fafb;
color: var(--color-text-secondary);
background: var(--color-surface-primary);
}
.data-table tbody tr:hover {
background: #f9fafb;
background: var(--color-surface-primary);
}
.data-table tbody tr.disabled {
@@ -323,16 +323,16 @@ import { NotifierRule, NotifierRuleStatus, NotifierSeverity } from '../../../cor
.status-indicator {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
}
.status-active { background: #dcfce7; color: #166534; }
.status-paused { background: #fef3c7; color: #92400e; }
.status-draft { background: #e0e7ff; color: #3730a3; }
.status-disabled { background: #f3f4f6; color: #6b7280; }
.status-active { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-paused { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.status-draft { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.status-disabled { background: var(--color-surface-secondary); color: var(--color-text-secondary); }
.rule-name {
display: flex;
@@ -341,12 +341,12 @@ import { NotifierRule, NotifierRuleStatus, NotifierSeverity } from '../../../cor
}
.rule-name strong {
color: #1f2937;
color: var(--color-text-heading);
}
.rule-description {
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
@@ -362,44 +362,44 @@ import { NotifierRule, NotifierRuleStatus, NotifierSeverity } from '../../../cor
.tag {
display: inline-block;
padding: 0.125rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
}
.tag-event {
background: #e0f2fe;
color: #0369a1;
background: var(--color-status-info-bg);
color: var(--color-status-info-text);
}
.text-muted {
color: #9ca3af;
color: var(--color-text-muted);
font-size: 0.875rem;
}
.severity-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
}
.severity-critical { background: #fef2f2; color: #991b1b; }
.severity-high { background: #fff7ed; color: #c2410c; }
.severity-medium { background: #fefce8; color: #a16207; }
.severity-low { background: #eff6ff; color: #1d4ed8; }
.severity-info { background: #f0fdf4; color: #166534; }
.severity-critical { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.severity-high { background: var(--color-severity-high-bg); color: var(--color-severity-high); }
.severity-medium { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.severity-low { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.severity-info { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.kev-badge {
display: inline-block;
margin-left: 0.25rem;
padding: 0.125rem 0.375rem;
background: #fef2f2;
color: #991b1b;
border-radius: 4px;
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
border-radius: var(--radius-sm);
font-size: 0.625rem;
font-weight: 700;
font-weight: var(--font-weight-bold);
}
.channel-list {
@@ -411,23 +411,23 @@ import { NotifierRule, NotifierRuleStatus, NotifierSeverity } from '../../../cor
.channel-badge {
display: inline-block;
padding: 0.125rem 0.5rem;
background: #f3e8ff;
color: #7c3aed;
border-radius: 4px;
background: var(--color-status-excepted-bg);
color: var(--color-status-excepted);
border-radius: var(--radius-sm);
font-size: 0.75rem;
}
.digest-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.digest-instant { background: #dcfce7; color: #166534; }
.digest-hourly { background: #e0f2fe; color: #0369a1; }
.digest-daily { background: #fef3c7; color: #92400e; }
.digest-instant { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.digest-hourly { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.digest-daily { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.action-buttons {
display: flex;
@@ -438,30 +438,30 @@ import { NotifierRule, NotifierRuleStatus, NotifierSeverity } from '../../../cor
padding: 0.25rem 0.5rem;
background: transparent;
border: none;
color: #1976d2;
color: var(--color-status-info-text);
font-size: 0.75rem;
cursor: pointer;
border-radius: 4px;
border-radius: var(--radius-sm);
transition: all 0.2s;
}
.btn-icon:hover {
background: #e3f2fd;
background: var(--color-status-info-bg);
}
.btn-icon.btn-danger {
color: #dc2626;
color: var(--color-status-error);
}
.btn-icon.btn-danger:hover {
background: #fef2f2;
background: var(--color-status-error-bg);
}
.error-message {
padding: 1rem;
background: #fef2f2;
color: #991b1b;
border-radius: 6px;
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
border-radius: var(--radius-md);
margin-top: 1rem;
}

View File

@@ -251,52 +251,52 @@ import {
margin-bottom: 1.5rem;
}
.section-header h3 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: 600; }
.section-header p { margin: 0; color: #6b7280; font-size: 0.875rem; }
.section-header h3 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: var(--font-weight-semibold); }
.section-header p { margin: 0; color: var(--color-text-secondary); font-size: 0.875rem; }
.active-warning {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: #fef3c7;
border: 1px solid #fcd34d;
border-radius: 6px;
background: var(--color-status-warning-bg);
border: 1px solid var(--color-status-warning-border);
border-radius: var(--radius-md);
margin-bottom: 1rem;
color: #92400e;
color: var(--color-status-warning-text);
}
.warning-icon {
width: 24px;
height: 24px;
background: #f59e0b;
background: var(--color-status-warning);
color: white;
border-radius: 50%;
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-weight: var(--font-weight-bold);
}
.btn { padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
.btn-primary { background: #1976d2; color: var(--color-text-heading); border: none; }
.btn { padding: 0.5rem 1rem; border-radius: var(--radius-md); font-size: 0.875rem; font-weight: var(--font-weight-medium); cursor: pointer; }
.btn-primary { background: var(--color-status-info-text); color: var(--color-text-heading); border: none; }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.btn-secondary { background: white; color: #374151; border: 1px solid #d1d5db; }
.btn-icon { padding: 0.25rem 0.5rem; background: transparent; border: none; color: #1976d2; font-size: 0.75rem; cursor: pointer; }
.btn-icon.btn-danger { color: #dc2626; }
.btn-secondary { background: white; color: var(--color-text-primary); border: 1px solid var(--color-border-secondary); }
.btn-icon { padding: 0.25rem 0.5rem; background: transparent; border: none; color: var(--color-status-info-text); font-size: 0.75rem; cursor: pointer; }
.btn-icon.btn-danger { color: var(--color-status-error); }
.overrides-list { display: flex; flex-direction: column; gap: 1rem; }
.override-card {
padding: 1rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
transition: all 0.2s;
}
.override-card.disabled { opacity: 0.7; }
.override-card.expired { background: #f9fafb; border-color: #d1d5db; }
.override-card.expired { background: var(--color-surface-primary); border-color: var(--color-border-secondary); }
.card-header {
display: flex;
@@ -311,37 +311,37 @@ import {
.scope-badge, .action-badge {
padding: 0.125rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.6875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
}
.scope-badge { background: #e0f2fe; color: #0369a1; }
.scope-badge.scope-global { background: #dbeafe; color: #1e40af; }
.scope-badge.scope-channel { background: #f3e8ff; color: #7c3aed; }
.scope-badge.scope-rule { background: #fef3c7; color: #92400e; }
.scope-badge.scope-event { background: #dcfce7; color: #166534; }
.scope-badge { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.scope-badge.scope-global { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.scope-badge.scope-channel { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.scope-badge.scope-rule { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.scope-badge.scope-event { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.action-badge { background: #fef2f2; color: #991b1b; }
.action-badge.action-mute { background: #fef2f2; color: #991b1b; }
.action-badge.action-unmute { background: #dcfce7; color: #166534; }
.action-badge.action-redirect { background: #e0f2fe; color: #0369a1; }
.action-badge.action-escalate { background: #fef3c7; color: #92400e; }
.action-badge.action-suppress { background: #f3f4f6; color: #6b7280; }
.action-badge { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.action-badge.action-mute { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.action-badge.action-unmute { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.action-badge.action-redirect { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.action-badge.action-escalate { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.action-badge.action-suppress { background: var(--color-surface-secondary); color: var(--color-text-secondary); }
.status-badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.status-badge.enabled { background: #dcfce7; color: #166534; }
.status-badge:not(.enabled) { background: #f3f4f6; color: #6b7280; }
.status-badge.expired { background: #fef2f2; color: #991b1b; }
.status-badge.enabled { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-badge:not(.enabled) { background: var(--color-surface-secondary); color: var(--color-text-secondary); }
.status-badge.expired { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.card-description { margin: 0 0 0.75rem; color: #6b7280; font-size: 0.875rem; }
.card-description { margin: 0 0 0.75rem; color: var(--color-text-secondary); font-size: 0.875rem; }
.override-details {
display: grid;
@@ -349,64 +349,64 @@ import {
gap: 0.75rem;
margin-bottom: 0.75rem;
padding: 0.75rem;
background: #f9fafb;
border-radius: 6px;
background: var(--color-surface-primary);
border-radius: var(--radius-md);
}
.detail-item label {
display: block;
font-size: 0.6875rem;
color: #6b7280;
color: var(--color-text-secondary);
text-transform: uppercase;
margin-bottom: 0.25rem;
}
.detail-item span { font-size: 0.875rem; }
.detail-item .mono { font-family: monospace; font-size: 0.8125rem; }
.detail-item .expired { color: #dc2626; }
.time-remaining { color: #6b7280; font-size: 0.75rem; margin-left: 0.5rem; }
.detail-item .expired { color: var(--color-status-error); }
.time-remaining { color: var(--color-text-secondary); font-size: 0.75rem; margin-left: 0.5rem; }
.card-actions { display: flex; gap: 0.5rem; padding-top: 0.75rem; border-top: 1px solid #e5e7eb; }
.card-actions { display: flex; gap: 0.5rem; padding-top: 0.75rem; border-top: 1px solid var(--color-border-primary); }
.loading-state, .empty-state { padding: 3rem; text-align: center; color: #6b7280; }
.empty-state .hint { font-size: 0.875rem; color: #9ca3af; }
.loading-state, .empty-state { padding: 3rem; text-align: center; color: var(--color-text-secondary); }
.empty-state .hint { font-size: 0.875rem; color: var(--color-text-muted); }
/* Edit Form */
.edit-form { max-width: 600px; }
.form-section { margin-bottom: 1.5rem; padding: 1rem; background: #f9fafb; border-radius: 8px; }
.form-section h4 { margin: 0 0 1rem; font-size: 0.9375rem; font-weight: 600; }
.form-section { margin-bottom: 1.5rem; padding: 1rem; background: var(--color-surface-primary); border-radius: var(--radius-lg); }
.form-section h4 { margin: 0 0 1rem; font-size: 0.9375rem; font-weight: var(--font-weight-semibold); }
.form-group { margin-bottom: 1rem; }
.form-group label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: 500; }
.form-group label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: var(--font-weight-medium); }
.form-group input, .form-group select, .form-group textarea {
width: 100%; padding: 0.5rem 0.75rem; border: 1px solid #d1d5db; border-radius: 6px; font-size: 0.875rem;
width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--color-border-secondary); border-radius: var(--radius-md); font-size: 0.875rem;
}
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.checkbox-label { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; font-weight: normal; }
.help-text { display: block; margin-top: 0.25rem; font-size: 0.75rem; color: #6b7280; }
.help-text { display: block; margin-top: 0.25rem; font-size: 0.75rem; color: var(--color-text-secondary); }
.quick-presets { padding-top: 1rem; border-top: 1px solid #e5e7eb; }
.quick-presets label { display: block; margin-bottom: 0.5rem; font-size: 0.75rem; color: #6b7280; }
.quick-presets { padding-top: 1rem; border-top: 1px solid var(--color-border-primary); }
.quick-presets label { display: block; margin-bottom: 0.5rem; font-size: 0.75rem; color: var(--color-text-secondary); }
.preset-buttons { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.preset-btn {
padding: 0.375rem 0.75rem;
background: white;
border: 1px solid #d1d5db;
border-radius: 4px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-sm);
font-size: 0.75rem;
cursor: pointer;
transition: all 0.2s;
}
.preset-btn:hover { background: #e3f2fd; border-color: #1976d2; }
.preset-btn:hover { background: var(--color-status-info-bg); border-color: var(--color-status-info-text); }
.form-footer { display: flex; justify-content: flex-end; gap: 1rem; padding-top: 1rem; border-top: 1px solid #e5e7eb; }
.form-footer { display: flex; justify-content: flex-end; gap: 1rem; padding-top: 1rem; border-top: 1px solid var(--color-border-primary); }
.error-banner { margin-top: 1rem; padding: 0.75rem 1rem; background: #fef2f2; color: #991b1b; border-radius: 6px; }
.error-banner { margin-top: 1rem; padding: 0.75rem 1rem; background: var(--color-status-error-bg); color: var(--color-status-error-text); border-radius: var(--radius-md); }
@media (max-width: 600px) {
.form-row { grid-template-columns: 1fr; }

View File

@@ -323,41 +323,41 @@ import {
margin-bottom: 1.5rem;
}
.section-header h3 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: 600; }
.section-header p { margin: 0; color: #6b7280; font-size: 0.875rem; }
.section-header h3 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: var(--font-weight-semibold); }
.section-header p { margin: 0; color: var(--color-text-secondary); font-size: 0.875rem; }
.btn { padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
.btn-primary { background: #1976d2; color: var(--color-text-heading); border: none; }
.btn-secondary { background: white; color: #374151; border: 1px solid #d1d5db; }
.btn { padding: 0.5rem 1rem; border-radius: var(--radius-md); font-size: 0.875rem; font-weight: var(--font-weight-medium); cursor: pointer; }
.btn-primary { background: var(--color-status-info-text); color: var(--color-text-heading); border: none; }
.btn-secondary { background: white; color: var(--color-text-primary); border: 1px solid var(--color-border-secondary); }
.btn-sm { padding: 0.375rem 0.75rem; font-size: 0.75rem; }
.btn-icon { padding: 0.25rem 0.5rem; background: transparent; border: none; color: #1976d2; font-size: 0.75rem; cursor: pointer; }
.btn-icon.btn-danger { color: #dc2626; }
.btn-icon.btn-warning { color: #d97706; }
.btn-icon { padding: 0.25rem 0.5rem; background: transparent; border: none; color: var(--color-status-info-text); font-size: 0.75rem; cursor: pointer; }
.btn-icon.btn-danger { color: var(--color-status-error); }
.btn-icon.btn-warning { color: var(--color-status-warning-text); }
.active-banner {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: #fef3c7;
border: 1px solid #f59e0b;
border-radius: 6px;
background: var(--color-status-warning-bg);
border: 1px solid var(--color-status-warning);
border-radius: var(--radius-md);
margin-bottom: 1rem;
font-size: 0.875rem;
font-weight: 500;
color: #92400e;
font-weight: var(--font-weight-medium);
color: var(--color-status-warning-text);
}
.banner-icon {
width: 24px;
height: 24px;
background: #f59e0b;
background: var(--color-status-warning);
color: white;
border-radius: 50%;
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-weight: var(--font-weight-bold);
}
.filter-bar {
@@ -365,21 +365,21 @@ import {
gap: 1rem;
margin-bottom: 1rem;
padding: 0.75rem;
background: #f9fafb;
border-radius: 6px;
background: var(--color-surface-primary);
border-radius: var(--radius-md);
}
.filter-group { display: flex; flex-direction: column; gap: 0.25rem; }
.filter-group label { font-size: 0.75rem; color: #6b7280; }
.filter-group select { padding: 0.375rem 0.5rem; border: 1px solid #d1d5db; border-radius: 4px; font-size: 0.875rem; }
.filter-group label { font-size: 0.75rem; color: var(--color-text-secondary); }
.filter-group select { padding: 0.375rem 0.5rem; border: 1px solid var(--color-border-secondary); border-radius: var(--radius-sm); font-size: 0.875rem; }
.override-list { display: flex; flex-direction: column; gap: 1rem; }
.override-card {
padding: 1rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
transition: border-color 0.15s;
}
@@ -398,49 +398,49 @@ import {
.scope-badge {
padding: 0.125rem 0.375rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.625rem;
font-weight: 700;
font-weight: var(--font-weight-bold);
}
.scope-global { background: #fee2e2; color: #991b1b; }
.scope-channel { background: #dbeafe; color: #1e40af; }
.scope-rule { background: #fef3c7; color: #92400e; }
.scope-event { background: #e0e7ff; color: #3730a3; }
.scope-global { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.scope-channel { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.scope-rule { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.scope-event { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.header-meta { display: flex; gap: 0.5rem; }
.action-badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.action-mute { background: #fef2f2; color: #991b1b; }
.action-unmute { background: #dcfce7; color: #166534; }
.action-redirect { background: #dbeafe; color: #1e40af; }
.action-escalate { background: #fef3c7; color: #92400e; }
.action-suppress { background: #f3f4f6; color: #6b7280; }
.action-mute { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.action-unmute { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.action-redirect { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.action-escalate { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.action-suppress { background: var(--color-surface-secondary); color: var(--color-text-secondary); }
.status-badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.status-badge.active { background: #dcfce7; color: #166534; }
.status-badge.expired { background: #fef2f2; color: #991b1b; }
.status-badge.disabled { background: #f3f4f6; color: #6b7280; }
.status-badge.active { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-badge.expired { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.status-badge.disabled { background: var(--color-surface-secondary); color: var(--color-text-secondary); }
.card-description { margin: 0 0 0.75rem; color: #6b7280; font-size: 0.875rem; }
.card-description { margin: 0 0 0.75rem; color: var(--color-text-secondary); font-size: 0.875rem; }
.card-details {
margin-bottom: 0.75rem;
padding: 0.75rem;
background: #f9fafb;
border-radius: 6px;
background: var(--color-surface-primary);
border-radius: var(--radius-md);
}
.detail-row {
@@ -451,29 +451,29 @@ import {
.detail-row:last-child { margin-bottom: 0; }
.detail-label { font-size: 0.8125rem; color: #6b7280; min-width: 80px; }
.detail-value { font-size: 0.8125rem; color: #374151; }
.detail-value.target { font-family: monospace; background: #e5e7eb; padding: 0.125rem 0.25rem; border-radius: 2px; }
.detail-value.expired { color: #991b1b; text-decoration: line-through; }
.detail-label { font-size: 0.8125rem; color: var(--color-text-secondary); min-width: 80px; }
.detail-value { font-size: 0.8125rem; color: var(--color-text-primary); }
.detail-value.target { font-family: monospace; background: var(--color-border-primary); padding: 0.125rem 0.25rem; border-radius: var(--radius-sm); }
.detail-value.expired { color: var(--color-status-error-text); text-decoration: line-through; }
.card-actions { display: flex; gap: 0.5rem; padding-top: 0.75rem; border-top: 1px solid #e5e7eb; }
.card-actions { display: flex; gap: 0.5rem; padding-top: 0.75rem; border-top: 1px solid var(--color-border-primary); }
.loading-state, .empty-state { padding: 3rem; text-align: center; color: #6b7280; }
.empty-state .hint { font-size: 0.875rem; color: #9ca3af; }
.loading-state, .empty-state { padding: 3rem; text-align: center; color: var(--color-text-secondary); }
.empty-state .hint { font-size: 0.875rem; color: var(--color-text-muted); }
/* Edit Form */
.edit-form { max-width: 700px; }
.form-section { margin-bottom: 1.5rem; padding: 1rem; background: #f9fafb; border-radius: 8px; }
.form-section h4 { margin: 0 0 0.75rem; font-size: 0.9375rem; font-weight: 600; }
.form-section { margin-bottom: 1.5rem; padding: 1rem; background: var(--color-surface-primary); border-radius: var(--radius-lg); }
.form-section h4 { margin: 0 0 0.75rem; font-size: 0.9375rem; font-weight: var(--font-weight-semibold); }
.form-group { margin-bottom: 1rem; }
.form-group label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: 500; }
.form-group label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: var(--font-weight-medium); }
.form-group input, .form-group select, .form-group textarea {
width: 100%; padding: 0.5rem 0.75rem; border: 1px solid #d1d5db; border-radius: 6px; font-size: 0.875rem;
width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--color-border-secondary); border-radius: var(--radius-md); font-size: 0.875rem;
}
.field-hint { margin: 0.25rem 0 0; font-size: 0.75rem; color: #6b7280; }
.field-hint { margin: 0.25rem 0 0; font-size: 0.75rem; color: var(--color-text-secondary); }
.form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; }
@@ -482,9 +482,9 @@ import {
.quick-expire { display: flex; flex-direction: column; }
.quick-buttons { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.form-footer { display: flex; justify-content: flex-end; gap: 1rem; padding-top: 1rem; border-top: 1px solid #e5e7eb; }
.form-footer { display: flex; justify-content: flex-end; gap: 1rem; padding-top: 1rem; border-top: 1px solid var(--color-border-primary); }
.error-banner { margin-top: 1rem; padding: 0.75rem 1rem; background: #fef2f2; color: #991b1b; border-radius: 6px; }
.error-banner { margin-top: 1rem; padding: 0.75rem 1rem; background: var(--color-status-error-bg); color: var(--color-status-error-text); border-radius: var(--radius-md); }
`],
changeDetection: ChangeDetectionStrategy.OnPush,
})

View File

@@ -227,23 +227,23 @@ import { NotifierQuietHours, NotifierQuietHoursRequest, NotifierQuietWindow } fr
margin-bottom: 1.5rem;
}
.section-header h3 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: 600; }
.section-header p { margin: 0; color: #6b7280; font-size: 0.875rem; }
.section-header h3 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: var(--font-weight-semibold); }
.section-header p { margin: 0; color: var(--color-text-secondary); font-size: 0.875rem; }
.btn { padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
.btn-primary { background: #1976d2; color: var(--color-text-heading); border: none; }
.btn-secondary { background: white; color: #374151; border: 1px solid #d1d5db; }
.btn { padding: 0.5rem 1rem; border-radius: var(--radius-md); font-size: 0.875rem; font-weight: var(--font-weight-medium); cursor: pointer; }
.btn-primary { background: var(--color-status-info-text); color: var(--color-text-heading); border: none; }
.btn-secondary { background: white; color: var(--color-text-primary); border: 1px solid var(--color-border-secondary); }
.btn-sm { padding: 0.375rem 0.75rem; font-size: 0.75rem; }
.btn-icon { padding: 0.25rem 0.5rem; background: transparent; border: none; color: #1976d2; font-size: 0.75rem; cursor: pointer; }
.btn-icon.btn-danger { color: #dc2626; }
.btn-icon { padding: 0.25rem 0.5rem; background: transparent; border: none; color: var(--color-status-info-text); font-size: 0.75rem; cursor: pointer; }
.btn-icon.btn-danger { color: var(--color-status-error); }
.config-list { display: flex; flex-direction: column; gap: 1rem; }
.config-card {
padding: 1rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
}
.config-card.disabled { opacity: 0.6; }
@@ -259,52 +259,52 @@ import { NotifierQuietHours, NotifierQuietHoursRequest, NotifierQuietWindow } fr
.status-badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.status-badge.enabled { background: #dcfce7; color: #166534; }
.status-badge:not(.enabled) { background: #f3f4f6; color: #6b7280; }
.status-badge.enabled { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-badge:not(.enabled) { background: var(--color-surface-secondary); color: var(--color-text-secondary); }
.card-description { margin: 0 0 1rem; color: #6b7280; font-size: 0.875rem; }
.card-description { margin: 0 0 1rem; color: var(--color-text-secondary); font-size: 0.875rem; }
.windows-list, .exemptions-list { margin-bottom: 1rem; }
.windows-list h5, .exemptions-list h5 { margin: 0 0 0.5rem; font-size: 0.75rem; color: #6b7280; text-transform: uppercase; }
.windows-list h5, .exemptions-list h5 { margin: 0 0 0.5rem; font-size: 0.75rem; color: var(--color-text-secondary); text-transform: uppercase; }
.window-item, .exemption-item {
display: flex;
gap: 1rem;
padding: 0.5rem;
background: #f9fafb;
border-radius: 4px;
background: var(--color-surface-primary);
border-radius: var(--radius-sm);
margin-bottom: 0.25rem;
font-size: 0.875rem;
}
.window-item .days { flex: 1; }
.window-item .times { font-weight: 500; }
.window-item .timezone { color: #6b7280; }
.window-item .times { font-weight: var(--font-weight-medium); }
.window-item .timezone { color: var(--color-text-secondary); }
.exemption-item .event-kinds { flex: 1; font-family: monospace; font-size: 0.8125rem; }
.exemption-item .reason { color: #6b7280; }
.exemption-item .reason { color: var(--color-text-secondary); }
.card-actions { display: flex; gap: 0.5rem; padding-top: 0.75rem; border-top: 1px solid #e5e7eb; }
.card-actions { display: flex; gap: 0.5rem; padding-top: 0.75rem; border-top: 1px solid var(--color-border-primary); }
.loading-state, .empty-state { padding: 3rem; text-align: center; color: #6b7280; }
.empty-state .hint { font-size: 0.875rem; color: #9ca3af; }
.loading-state, .empty-state { padding: 3rem; text-align: center; color: var(--color-text-secondary); }
.empty-state .hint { font-size: 0.875rem; color: var(--color-text-muted); }
/* Edit Form */
.edit-form { max-width: 700px; }
.form-section { margin-bottom: 1.5rem; padding: 1rem; background: #f9fafb; border-radius: 8px; }
.form-section h4 { margin: 0 0 0.75rem; font-size: 0.9375rem; font-weight: 600; }
.section-desc { margin: 0 0 0.75rem; font-size: 0.875rem; color: #6b7280; }
.form-section { margin-bottom: 1.5rem; padding: 1rem; background: var(--color-surface-primary); border-radius: var(--radius-lg); }
.form-section h4 { margin: 0 0 0.75rem; font-size: 0.9375rem; font-weight: var(--font-weight-semibold); }
.section-desc { margin: 0 0 0.75rem; font-size: 0.875rem; color: var(--color-text-secondary); }
.form-group { margin-bottom: 1rem; }
.form-group label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: 500; }
.form-group label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: var(--font-weight-medium); }
.form-group input, .form-group select, .form-group textarea {
width: 100%; padding: 0.5rem 0.75rem; border: 1px solid #d1d5db; border-radius: 6px; font-size: 0.875rem;
width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--color-border-secondary); border-radius: var(--radius-md); font-size: 0.875rem;
}
.form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; align-items: end; }
@@ -315,11 +315,11 @@ import { NotifierQuietHours, NotifierQuietHoursRequest, NotifierQuietWindow } fr
.day-checkbox { display: flex; align-items: center; gap: 0.25rem; font-size: 0.75rem; cursor: pointer; }
.day-checkbox input { width: 14px; height: 14px; }
.window-form, .exemption-form { padding: 0.75rem; background: white; border: 1px solid #e5e7eb; border-radius: 6px; margin-bottom: 0.5rem; }
.window-form, .exemption-form { padding: 0.75rem; background: white; border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); margin-bottom: 0.5rem; }
.form-footer { display: flex; justify-content: flex-end; gap: 1rem; padding-top: 1rem; border-top: 1px solid #e5e7eb; }
.form-footer { display: flex; justify-content: flex-end; gap: 1rem; padding-top: 1rem; border-top: 1px solid var(--color-border-primary); }
.error-banner { margin-top: 1rem; padding: 0.75rem 1rem; background: #fef2f2; color: #991b1b; border-radius: 6px; }
.error-banner { margin-top: 1rem; padding: 0.75rem 1rem; background: var(--color-status-error-bg); color: var(--color-status-error-text); border-radius: var(--radius-md); }
`],
changeDetection: ChangeDetectionStrategy.OnPush
})

View File

@@ -261,15 +261,15 @@ import {
}
.config-panel, .results-panel {
background: #f9fafb;
border-radius: 8px;
background: var(--color-surface-primary);
border-radius: var(--radius-lg);
padding: 1.5rem;
}
.config-panel h3, .results-panel h4 {
margin: 0 0 1rem;
font-size: 1rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.form-group {
@@ -280,15 +280,15 @@ import {
display: block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.form-group select,
.form-group textarea {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-md);
font-size: 0.875rem;
font-family: inherit;
}
@@ -301,21 +301,21 @@ import {
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #1976d2;
border-color: var(--color-status-info-text);
}
.help-text {
display: block;
margin-top: 0.25rem;
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.error-text {
display: block;
margin-top: 0.25rem;
font-size: 0.75rem;
color: #dc2626;
color: var(--color-status-error);
}
.checkbox-label {
@@ -333,28 +333,28 @@ import {
.btn {
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
flex: 1;
}
.btn-primary { background: #1976d2; color: var(--color-text-heading); border: none; }
.btn-primary { background: var(--color-status-info-text); color: var(--color-text-heading); border: none; }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.btn-secondary { background: white; color: #374151; border: 1px solid #d1d5db; }
.btn-secondary { background: white; color: var(--color-text-primary); border: 1px solid var(--color-border-secondary); }
.btn-secondary:disabled { opacity: 0.6; cursor: not-allowed; }
.quick-templates {
padding-top: 1rem;
border-top: 1px solid #e5e7eb;
border-top: 1px solid var(--color-border-primary);
}
.quick-templates label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.template-buttons {
@@ -366,23 +366,23 @@ import {
.template-btn {
padding: 0.375rem 0.75rem;
background: white;
border: 1px solid #d1d5db;
border-radius: 4px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-sm);
font-size: 0.75rem;
cursor: pointer;
transition: all 0.2s;
}
.template-btn:hover {
background: #e3f2fd;
border-color: #1976d2;
background: var(--color-status-info-bg);
border-color: var(--color-status-info-text);
}
/* Results Panel */
.result-card {
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
padding: 1rem;
margin-bottom: 1rem;
}
@@ -397,33 +397,33 @@ import {
align-items: center;
gap: 0.75rem;
padding: 1rem;
background: #f3f4f6;
border-radius: 6px;
background: var(--color-surface-secondary);
border-radius: var(--radius-md);
margin-bottom: 1rem;
}
.result-summary.matched {
background: #dcfce7;
background: var(--color-status-success-bg);
}
.result-icon {
width: 32px;
height: 32px;
border-radius: 50%;
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
background: #9ca3af;
font-weight: var(--font-weight-bold);
background: var(--color-text-muted);
color: white;
}
.result-summary.matched .result-icon {
background: #16a34a;
background: var(--color-status-success);
}
.result-text {
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.result-section {
@@ -433,8 +433,8 @@ import {
.result-section label {
display: block;
font-size: 0.75rem;
font-weight: 600;
color: #6b7280;
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
margin-bottom: 0.5rem;
}
@@ -458,17 +458,17 @@ import {
display: flex;
justify-content: space-between;
padding: 0.5rem;
background: #f9fafb;
border-radius: 4px;
background: var(--color-surface-primary);
border-radius: var(--radius-sm);
}
.channel-name {
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.digest-mode {
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
text-transform: uppercase;
}
@@ -480,23 +480,23 @@ import {
.flag {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.flag.warning { background: #fef3c7; color: #92400e; }
.flag.info { background: #dbeafe; color: #1e40af; }
.flag.warning { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.flag.info { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.trace-info {
font-size: 0.75rem;
color: #9ca3af;
color: var(--color-text-muted);
font-family: monospace;
}
/* Preview Card */
.preview-card {
border-color: #1976d2;
border-color: var(--color-status-info-text);
}
.preview-meta {
@@ -507,11 +507,11 @@ import {
.channel-type, .format-type {
padding: 0.25rem 0.5rem;
background: #e0f2fe;
color: #0369a1;
border-radius: 4px;
background: var(--color-status-info-bg);
color: var(--color-status-info-text);
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.preview-section {
@@ -520,14 +520,14 @@ import {
.preview-subject {
margin: 0;
font-weight: 600;
font-weight: var(--font-weight-semibold);
font-size: 0.9375rem;
}
.preview-body {
padding: 1rem;
background: #f9fafb;
border-radius: 6px;
background: var(--color-surface-primary);
border-radius: var(--radius-md);
overflow-x: auto;
}
@@ -552,14 +552,14 @@ import {
display: flex;
justify-content: space-between;
padding: 0.375rem 0.5rem;
background: #f3f4f6;
border-radius: 4px;
background: var(--color-surface-secondary);
border-radius: var(--radius-sm);
font-size: 0.8125rem;
}
.var-name {
font-family: monospace;
color: #6b7280;
color: var(--color-text-secondary);
}
.var-value {
@@ -572,7 +572,7 @@ import {
align-items: center;
padding: 3rem;
text-align: center;
color: #6b7280;
color: var(--color-text-secondary);
}
.instructions {
@@ -589,9 +589,9 @@ import {
.error-banner {
margin-top: 1rem;
padding: 0.75rem 1rem;
background: #fef2f2;
color: #991b1b;
border-radius: 6px;
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
border-radius: var(--radius-md);
}
@media (max-width: 900px) {

View File

@@ -226,8 +226,8 @@ import {
.btn-back {
padding: 0.5rem 1rem;
background: transparent;
border: 1px solid #d1d5db;
border-radius: 6px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-md);
cursor: pointer;
}
@@ -243,8 +243,8 @@ import {
}
.form-panel, .preview-panel {
background: #f9fafb;
border-radius: 8px;
background: var(--color-surface-primary);
border-radius: var(--radius-lg);
padding: 1.5rem;
}
@@ -255,13 +255,13 @@ import {
.form-section h3 {
margin: 0 0 0.75rem;
font-size: 0.9375rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.section-desc {
margin: 0 0 0.75rem;
font-size: 0.875rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.form-group {
@@ -272,7 +272,7 @@ import {
display: block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.form-group input,
@@ -280,8 +280,8 @@ import {
.form-group textarea {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-md);
font-size: 0.875rem;
}
@@ -289,7 +289,7 @@ import {
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #1976d2;
border-color: var(--color-status-info-text);
}
.code-editor {
@@ -315,7 +315,7 @@ import {
display: block;
margin-top: 0.25rem;
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.variable-row {
@@ -339,15 +339,15 @@ import {
.btn {
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
}
.btn-primary { background: #1976d2; color: var(--color-text-heading); border: none; }
.btn-primary { background: var(--color-status-info-text); color: var(--color-text-heading); border: none; }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.btn-secondary { background: white; color: #374151; border: 1px solid #d1d5db; }
.btn-secondary { background: white; color: var(--color-text-primary); border: 1px solid var(--color-border-secondary); }
.btn-sm { padding: 0.375rem 0.75rem; font-size: 0.75rem; }
.btn-icon {
@@ -355,25 +355,25 @@ import {
height: 28px;
padding: 0;
border: none;
background: #f3f4f6;
border-radius: 4px;
background: var(--color-surface-secondary);
border-radius: var(--radius-sm);
cursor: pointer;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.btn-icon.btn-danger { color: #dc2626; }
.btn-icon.btn-danger { color: var(--color-status-error); }
.quick-insert {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #e5e7eb;
border-top: 1px solid var(--color-border-primary);
}
.quick-insert label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.insert-buttons {
@@ -384,17 +384,17 @@ import {
.insert-btn {
padding: 0.25rem 0.5rem;
background: #e0f2fe;
color: #0369a1;
background: var(--color-status-info-bg);
color: var(--color-status-info-text);
border: none;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.6875rem;
font-family: monospace;
cursor: pointer;
}
.insert-btn:hover {
background: #bae6fd;
background: var(--color-status-info-border);
}
.form-footer {
@@ -402,14 +402,14 @@ import {
justify-content: flex-end;
gap: 1rem;
padding-top: 1rem;
border-top: 1px solid #e5e7eb;
border-top: 1px solid var(--color-border-primary);
}
/* Preview Panel */
.preview-panel h3 {
margin: 0 0 1rem;
font-size: 0.9375rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.preview-controls {
@@ -420,38 +420,38 @@ import {
display: block;
margin-bottom: 0.5rem;
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.preview-result {
background: white;
border: 1px solid #e5e7eb;
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
padding: 1rem;
}
.preview-subject {
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e5e7eb;
border-bottom: 1px solid var(--color-border-primary);
}
.preview-subject label,
.preview-body label {
display: block;
font-size: 0.75rem;
color: #6b7280;
color: var(--color-text-secondary);
margin-bottom: 0.25rem;
}
.preview-subject p {
margin: 0;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.preview-content {
background: #f9fafb;
border-radius: 4px;
background: var(--color-surface-primary);
border-radius: var(--radius-sm);
padding: 1rem;
overflow-x: auto;
}
@@ -474,9 +474,9 @@ import {
.error-banner {
margin-top: 1rem;
padding: 0.75rem 1rem;
background: #fef2f2;
color: #991b1b;
border-radius: 6px;
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
border-radius: var(--radius-md);
}
@media (max-width: 900px) {

View File

@@ -290,15 +290,15 @@ type ThrottleScope = 'global' | 'channel' | 'rule' | 'event';
margin-bottom: 1.5rem;
}
.section-header h3 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: 600; }
.section-header p { margin: 0; color: #6b7280; font-size: 0.875rem; }
.section-header h3 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: var(--font-weight-semibold); }
.section-header p { margin: 0; color: var(--color-text-secondary); font-size: 0.875rem; }
.btn { padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
.btn-primary { background: #1976d2; color: var(--color-text-heading); border: none; }
.btn { padding: 0.5rem 1rem; border-radius: var(--radius-md); font-size: 0.875rem; font-weight: var(--font-weight-medium); cursor: pointer; }
.btn-primary { background: var(--color-status-info-text); color: var(--color-text-heading); border: none; }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.btn-secondary { background: white; color: #374151; border: 1px solid #d1d5db; }
.btn-icon { padding: 0.25rem 0.5rem; background: transparent; border: none; color: #1976d2; font-size: 0.75rem; cursor: pointer; }
.btn-icon.btn-danger { color: #dc2626; }
.btn-secondary { background: white; color: var(--color-text-primary); border: 1px solid var(--color-border-secondary); }
.btn-icon { padding: 0.25rem 0.5rem; background: transparent; border: none; color: var(--color-status-info-text); font-size: 0.75rem; cursor: pointer; }
.btn-icon.btn-danger { color: var(--color-status-error); }
.throttles-grid {
display: grid;
@@ -310,8 +310,8 @@ type ThrottleScope = 'global' | 'channel' | 'rule' | 'event';
.throttle-card {
padding: 1rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
}
.throttle-card.disabled { opacity: 0.7; }
@@ -329,28 +329,28 @@ type ThrottleScope = 'global' | 'channel' | 'rule' | 'event';
.scope-badge {
display: inline-block;
padding: 0.125rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.6875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
}
.scope-badge.scope-global { background: #dbeafe; color: #1e40af; }
.scope-badge.scope-channel { background: #f3e8ff; color: #7c3aed; }
.scope-badge.scope-rule { background: #fef3c7; color: #92400e; }
.scope-badge.scope-event { background: #dcfce7; color: #166534; }
.scope-badge.scope-global { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.scope-badge.scope-channel { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.scope-badge.scope-rule { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.scope-badge.scope-event { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.status-badge.enabled { background: #dcfce7; color: #166534; }
.status-badge:not(.enabled) { background: #f3f4f6; color: #6b7280; }
.status-badge.enabled { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-badge:not(.enabled) { background: var(--color-surface-secondary); color: var(--color-text-secondary); }
.card-description { margin: 0 0 0.75rem; color: #6b7280; font-size: 0.875rem; }
.card-description { margin: 0 0 0.75rem; color: var(--color-text-secondary); font-size: 0.875rem; }
/* Rate Limit Visual */
.rate-limit-visual {
@@ -359,9 +359,9 @@ type ThrottleScope = 'global' | 'channel' | 'rule' | 'event';
justify-content: center;
gap: 0.75rem;
padding: 1rem;
background: #f0f9ff;
border: 1px solid #bae6fd;
border-radius: 8px;
background: var(--color-status-info-bg);
border: 1px solid var(--color-status-info-border);
border-radius: var(--radius-lg);
margin-bottom: 0.75rem;
}
@@ -373,19 +373,19 @@ type ThrottleScope = 'global' | 'channel' | 'rule' | 'event';
.rate-value {
font-size: 1.5rem;
font-weight: 700;
color: #0369a1;
font-weight: var(--font-weight-bold);
color: var(--color-status-info-text);
}
.rate-unit {
font-size: 0.6875rem;
color: #6b7280;
color: var(--color-text-secondary);
text-transform: uppercase;
}
.rate-separator {
font-size: 0.875rem;
color: #6b7280;
color: var(--color-text-secondary);
}
.throttle-details {
@@ -394,14 +394,14 @@ type ThrottleScope = 'global' | 'channel' | 'rule' | 'event';
gap: 0.5rem;
margin-bottom: 0.75rem;
padding: 0.75rem;
background: #f9fafb;
border-radius: 6px;
background: var(--color-surface-primary);
border-radius: var(--radius-md);
}
.detail-item label {
display: block;
font-size: 0.6875rem;
color: #6b7280;
color: var(--color-text-secondary);
text-transform: uppercase;
margin-bottom: 0.125rem;
}
@@ -409,19 +409,19 @@ type ThrottleScope = 'global' | 'channel' | 'rule' | 'event';
.detail-item span { font-size: 0.8125rem; }
.detail-item .mono { font-family: monospace; }
.card-actions { display: flex; gap: 0.5rem; padding-top: 0.75rem; border-top: 1px solid #e5e7eb; }
.card-actions { display: flex; gap: 0.5rem; padding-top: 0.75rem; border-top: 1px solid var(--color-border-primary); }
.loading-state, .empty-state { padding: 3rem; text-align: center; color: #6b7280; }
.empty-state .hint { font-size: 0.875rem; color: #9ca3af; }
.loading-state, .empty-state { padding: 3rem; text-align: center; color: var(--color-text-secondary); }
.empty-state .hint { font-size: 0.875rem; color: var(--color-text-muted); }
/* Throttle Summary */
.throttle-summary {
padding: 1rem;
background: #f9fafb;
border-radius: 8px;
background: var(--color-surface-primary);
border-radius: var(--radius-lg);
}
.throttle-summary h4 { margin: 0 0 1rem; font-size: 0.875rem; font-weight: 600; }
.throttle-summary h4 { margin: 0 0 1rem; font-size: 0.875rem; font-weight: var(--font-weight-semibold); }
.summary-grid {
display: grid;
@@ -435,22 +435,22 @@ type ThrottleScope = 'global' | 'channel' | 'rule' | 'event';
align-items: center;
padding: 0.75rem;
background: white;
border-radius: 6px;
border-radius: var(--radius-md);
}
.summary-label { font-size: 0.6875rem; color: #6b7280; text-transform: uppercase; }
.summary-value { font-size: 1.25rem; font-weight: 700; color: #1976d2; }
.summary-label { font-size: 0.6875rem; color: var(--color-text-secondary); text-transform: uppercase; }
.summary-value { font-size: 1.25rem; font-weight: var(--font-weight-bold); color: var(--color-status-info-text); }
/* Edit Form */
.edit-form { max-width: 600px; }
.form-section { margin-bottom: 1.5rem; padding: 1rem; background: #f9fafb; border-radius: 8px; }
.form-section h4 { margin: 0 0 0.75rem; font-size: 0.9375rem; font-weight: 600; }
.form-section { margin-bottom: 1.5rem; padding: 1rem; background: var(--color-surface-primary); border-radius: var(--radius-lg); }
.form-section h4 { margin: 0 0 0.75rem; font-size: 0.9375rem; font-weight: var(--font-weight-semibold); }
.form-group { margin-bottom: 1rem; }
.form-group label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: 500; }
.form-group label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: var(--font-weight-medium); }
.form-group input, .form-group select, .form-group textarea {
width: 100%; padding: 0.5rem 0.75rem; border: 1px solid #d1d5db; border-radius: 6px; font-size: 0.875rem;
width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--color-border-secondary); border-radius: var(--radius-md); font-size: 0.875rem;
}
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
@@ -458,32 +458,32 @@ type ThrottleScope = 'global' | 'channel' | 'rule' | 'event';
.checkbox-label { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; font-weight: normal; }
.help-text { display: block; margin-top: 0.25rem; font-size: 0.75rem; color: #6b7280; }
.help-text { display: block; margin-top: 0.25rem; font-size: 0.75rem; color: var(--color-text-secondary); }
.quick-presets { padding: 1rem 0; border-bottom: 1px solid #e5e7eb; margin-bottom: 1rem; }
.quick-presets label { display: block; margin-bottom: 0.5rem; font-size: 0.75rem; color: #6b7280; }
.quick-presets { padding: 1rem 0; border-bottom: 1px solid var(--color-border-primary); margin-bottom: 1rem; }
.quick-presets label { display: block; margin-bottom: 0.5rem; font-size: 0.75rem; color: var(--color-text-secondary); }
.preset-buttons { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.preset-btn {
padding: 0.375rem 0.75rem;
background: white;
border: 1px solid #d1d5db;
border-radius: 4px;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-sm);
font-size: 0.75rem;
cursor: pointer;
transition: all 0.2s;
}
.preset-btn:hover { background: #e3f2fd; border-color: #1976d2; }
.preset-btn:hover { background: var(--color-status-info-bg); border-color: var(--color-status-info-text); }
.rate-preview {
padding: 1rem;
background: #f0f9ff;
border: 1px solid #bae6fd;
border-radius: 6px;
background: var(--color-status-info-bg);
border: 1px solid var(--color-status-info-border);
border-radius: var(--radius-md);
margin-bottom: 1rem;
}
.rate-preview label { display: block; font-size: 0.75rem; color: #6b7280; margin-bottom: 0.5rem; }
.rate-preview label { display: block; font-size: 0.75rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.preview-display {
display: flex;
@@ -491,12 +491,12 @@ type ThrottleScope = 'global' | 'channel' | 'rule' | 'event';
gap: 0.5rem;
}
.preview-rate { font-size: 1rem; font-weight: 600; color: #0369a1; }
.preview-burst { font-size: 0.875rem; color: #6b7280; }
.preview-rate { font-size: 1rem; font-weight: var(--font-weight-semibold); color: var(--color-status-info-text); }
.preview-burst { font-size: 0.875rem; color: var(--color-text-secondary); }
.form-footer { display: flex; justify-content: flex-end; gap: 1rem; padding-top: 1rem; border-top: 1px solid #e5e7eb; }
.form-footer { display: flex; justify-content: flex-end; gap: 1rem; padding-top: 1rem; border-top: 1px solid var(--color-border-primary); }
.error-banner { margin-top: 1rem; padding: 0.75rem 1rem; background: #fef2f2; color: #991b1b; border-radius: 6px; }
.error-banner { margin-top: 1rem; padding: 0.75rem 1rem; background: var(--color-status-error-bg); color: var(--color-status-error-text); border-radius: var(--radius-md); }
@media (max-width: 600px) {
.form-row { grid-template-columns: 1fr; }

View File

@@ -111,11 +111,11 @@ import { Component, EventEmitter, Input, Output, signal, computed } from '@angul
gap: 0.5rem;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
color: var(--color-success-text);
background: var(--color-success-bg);
border: 1px solid var(--color-success-border);
border-radius: 0.375rem;
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.15s ease;
}
@@ -164,7 +164,7 @@ import { Component, EventEmitter, Input, Output, signal, computed } from '@angul
height: 1rem;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
border-radius: var(--radius-full);
animation: spin 0.75s linear infinite;
}
@@ -213,8 +213,8 @@ import { Component, EventEmitter, Input, Output, signal, computed } from '@angul
list-style: none;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 0.375rem;
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
}
.dropdown-menu li button {
@@ -228,7 +228,7 @@ import { Component, EventEmitter, Input, Output, signal, computed } from '@angul
color: var(--color-text-primary);
background: transparent;
border: none;
border-radius: 0.25rem;
border-radius: var(--radius-sm);
cursor: pointer;
}

View File

@@ -93,14 +93,14 @@ type TrackerPullRequest = PullRequestInfo & {
.subtitle {
margin: 0.25rem 0 0;
color: #4b5563;
color: var(--color-text-secondary);
}
.status-line {
margin: 0.5rem 0 0;
font-size: 0.875rem;
font-weight: 600;
color: #1d4ed8;
font-weight: var(--font-weight-semibold);
color: var(--color-status-info-text);
}
.actions {
@@ -115,9 +115,9 @@ type TrackerPullRequest = PullRequestInfo & {
}
.panel {
background: #ffffff;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-lg);
padding: 1rem;
}
`],

View File

@@ -132,9 +132,9 @@ import { ProposedAction, ActionType, ACTION_TYPE_METADATA } from './chat.models'
gap: 6px;
padding: 8px 16px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
border-radius: var(--radius-md);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
cursor: pointer;
transition: all 0.15s ease;
}
@@ -170,11 +170,11 @@ import { ProposedAction, ActionType, ACTION_TYPE_METADATA } from './chat.models'
}
.action-button.variant--secondary {
background: var(--bg-secondary);
color: var(--text-primary);
background: var(--color-surface-secondary);
color: var(--color-text-primary);
}
.action-button.variant--secondary:hover:not(:disabled) {
background: var(--bg-secondary-hover);
background: var(--color-nav-hover);
}
.button-icon {
@@ -193,8 +193,8 @@ import { ProposedAction, ActionType, ACTION_TYPE_METADATA } from './chat.models'
}
.disabled-reason {
font-size: 12px;
color: var(--text-muted);
font-size: var(--font-size-sm);
color: var(--color-text-muted);
}
/* Confirmation dialog */
@@ -215,32 +215,32 @@ import { ProposedAction, ActionType, ACTION_TYPE_METADATA } from './chat.models'
.confirmation-content {
position: relative;
background: var(--bg-elevated);
border: 1px solid var(--border-subtle);
border-radius: 12px;
background: var(--color-surface-elevated);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-xl);
padding: 24px;
min-width: 320px;
max-width: 400px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
box-shadow: var(--shadow-xl);
}
.confirmation-title {
margin: 0 0 12px;
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.confirmation-message {
margin: 0 0 8px;
font-size: 14px;
color: var(--text-secondary);
font-size: var(--font-size-base);
color: var(--color-text-secondary);
}
.confirmation-description {
margin: 0 0 16px;
font-size: 13px;
color: var(--text-muted);
font-size: var(--font-size-base);
color: var(--color-text-muted);
}
.confirmation-actions {
@@ -253,19 +253,19 @@ import { ProposedAction, ActionType, ACTION_TYPE_METADATA } from './chat.models'
.confirm-btn {
padding: 8px 16px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
border-radius: var(--radius-md);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
cursor: pointer;
transition: all 0.15s ease;
}
.confirm-btn.cancel {
background: var(--bg-secondary);
color: var(--text-primary);
background: var(--color-surface-secondary);
color: var(--color-text-primary);
}
.confirm-btn.cancel:hover {
background: var(--bg-secondary-hover);
background: var(--color-nav-hover);
}
.confirm-btn.confirm {

View File

@@ -156,22 +156,22 @@ interface MessageSegment {
display: flex;
gap: 12px;
padding: 16px;
border-radius: 12px;
border-radius: var(--radius-xl);
position: relative;
}
.chat-message.user {
background: var(--bg-user-message);
background: var(--color-surface-tertiary);
}
.chat-message.assistant {
background: var(--bg-assistant-message);
background: var(--color-surface-secondary);
}
.message-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
@@ -205,23 +205,23 @@ interface MessageSegment {
}
.message-role {
font-weight: 600;
font-size: 14px;
color: var(--text-primary);
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-base);
color: var(--color-text-primary);
}
.message-time {
font-size: 12px;
color: var(--text-muted);
font-size: var(--font-size-sm);
color: var(--color-text-muted);
}
.grounding-score {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
font-size: var(--font-size-sm);
padding: 2px 6px;
border-radius: 4px;
border-radius: var(--radius-sm);
margin-left: auto;
}
@@ -232,43 +232,43 @@ interface MessageSegment {
.grounding-score.high {
background: rgba(34, 197, 94, 0.2);
color: #22c55e;
color: var(--color-status-success);
}
.grounding-score.medium {
background: rgba(245, 158, 11, 0.2);
color: #f59e0b;
color: var(--color-status-warning);
}
.grounding-score.low {
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
color: var(--color-status-error);
}
.message-body {
font-size: 14px;
font-size: var(--font-size-base);
line-height: 1.6;
color: var(--text-secondary);
color: var(--color-text-secondary);
word-wrap: break-word;
}
.message-body :global(strong) {
color: var(--text-primary);
font-weight: 600;
color: var(--color-text-primary);
font-weight: var(--font-weight-semibold);
}
.message-body :global(code) {
font-family: var(--font-mono, monospace);
background: var(--bg-code);
background: var(--color-surface-tertiary);
padding: 2px 6px;
border-radius: 4px;
font-size: 13px;
border-radius: var(--radius-sm);
font-size: var(--font-size-base);
}
.message-body :global(pre) {
background: var(--bg-code-block);
background: var(--color-surface-tertiary);
padding: 12px;
border-radius: 8px;
border-radius: var(--radius-lg);
overflow-x: auto;
margin: 12px 0;
}
@@ -285,16 +285,16 @@ interface MessageSegment {
.message-citations {
margin-top: 12px;
padding: 8px;
background: var(--bg-citations);
border-radius: 8px;
background: var(--color-surface-tertiary);
border-radius: var(--radius-lg);
}
.message-citations summary {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--text-muted);
font-size: var(--font-size-base);
color: var(--color-text-muted);
cursor: pointer;
list-style: none;
}
@@ -327,7 +327,7 @@ interface MessageSegment {
gap: 8px;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--border-subtle);
border-top: 1px solid var(--color-border-primary);
}
.copy-btn {
@@ -338,9 +338,9 @@ interface MessageSegment {
height: 28px;
border: none;
background: transparent;
color: var(--text-muted);
color: var(--color-text-muted);
cursor: pointer;
border-radius: 4px;
border-radius: var(--radius-sm);
opacity: 0;
transition: opacity 0.15s ease, background 0.15s ease;
}
@@ -350,8 +350,8 @@ interface MessageSegment {
}
.copy-btn:hover {
background: var(--bg-hover);
color: var(--text-secondary);
background: var(--color-nav-hover);
color: var(--color-text-secondary);
}
.copy-btn svg {

View File

@@ -205,8 +205,8 @@ import {
display: flex;
flex-direction: column;
height: 100%;
background: var(--bg-surface);
border-radius: 12px;
background: var(--color-surface-primary);
border-radius: var(--radius-xl);
overflow: hidden;
}
@@ -216,8 +216,8 @@ import {
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--bg-elevated);
border-bottom: 1px solid var(--border-subtle);
background: var(--color-surface-elevated);
border-bottom: 1px solid var(--color-border-primary);
}
.header-left {
@@ -234,18 +234,18 @@ import {
.header-title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
font-size: var(--font-size-md);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.conversation-id {
font-size: 12px;
font-size: var(--font-size-sm);
font-family: var(--font-mono, monospace);
color: var(--text-muted);
color: var(--color-text-muted);
padding: 2px 6px;
background: var(--bg-code);
border-radius: 4px;
background: var(--color-surface-tertiary);
border-radius: var(--radius-sm);
}
.header-right {
@@ -259,9 +259,9 @@ import {
height: 32px;
border: none;
background: transparent;
color: var(--text-muted);
color: var(--color-text-muted);
cursor: pointer;
border-radius: 6px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
@@ -269,8 +269,8 @@ import {
}
.header-btn:hover {
background: var(--bg-hover);
color: var(--text-secondary);
background: var(--color-nav-hover);
color: var(--color-text-secondary);
}
.header-btn svg {
@@ -295,22 +295,22 @@ import {
justify-content: center;
text-align: center;
padding: 40px;
color: var(--text-muted);
color: var(--color-text-muted);
}
.loading-state svg, .error-state svg, .empty-state svg {
width: 48px;
height: 48px;
margin-bottom: 16px;
color: var(--text-muted);
color: var(--color-text-muted);
}
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid var(--border-subtle);
border: 3px solid var(--color-border-primary);
border-top-color: var(--color-primary);
border-radius: 50%;
border-radius: var(--radius-full);
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@@ -329,14 +329,14 @@ import {
background: var(--color-primary);
color: white;
border: none;
border-radius: 6px;
border-radius: var(--radius-md);
cursor: pointer;
}
.empty-state h3 {
margin: 0 0 8px;
font-size: 18px;
color: var(--text-primary);
font-size: var(--font-size-lg);
color: var(--color-text-primary);
}
.empty-state p {
@@ -353,25 +353,25 @@ import {
.suggestion-btn {
padding: 8px 12px;
background: var(--bg-secondary);
color: var(--text-secondary);
border: 1px solid var(--border-subtle);
border-radius: 20px;
font-size: 13px;
background: var(--color-surface-secondary);
color: var(--color-text-secondary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-2xl);
font-size: var(--font-size-base);
cursor: pointer;
transition: all 0.15s ease;
}
.suggestion-btn:hover {
background: var(--bg-secondary-hover);
color: var(--text-primary);
background: var(--color-nav-hover);
color: var(--color-text-primary);
}
/* Streaming message */
.chat-message.streaming {
padding: 16px;
border-radius: 12px;
background: var(--bg-assistant-message);
border-radius: var(--radius-xl);
background: var(--color-surface-secondary);
display: flex;
gap: 12px;
}
@@ -379,7 +379,7 @@ import {
.streaming .message-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
border-radius: var(--radius-full);
background: var(--color-assistant);
display: flex;
align-items: center;
@@ -406,14 +406,14 @@ import {
}
.streaming .message-role {
font-weight: 600;
font-size: 14px;
color: var(--text-primary);
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-base);
color: var(--color-text-primary);
}
.typing-indicator {
font-size: 12px;
color: var(--text-muted);
font-size: var(--font-size-sm);
color: var(--color-text-muted);
}
.dots span {
@@ -428,16 +428,16 @@ import {
}
.streaming .message-body {
font-size: 14px;
font-size: var(--font-size-base);
line-height: 1.6;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.cursor {
display: inline-block;
width: 2px;
height: 16px;
background: var(--text-primary);
background: var(--color-text-primary);
animation: blink-cursor 1s infinite;
vertical-align: text-bottom;
margin-left: 2px;
@@ -451,8 +451,8 @@ import {
/* Input area */
.chat-input-area {
padding: 12px 16px;
background: var(--bg-elevated);
border-top: 1px solid var(--border-subtle);
background: var(--color-surface-elevated);
border-top: 1px solid var(--color-border-primary);
}
.input-container {
@@ -464,11 +464,11 @@ import {
.chat-input {
flex: 1;
padding: 12px;
background: var(--bg-input);
border: 1px solid var(--border-subtle);
border-radius: 8px;
color: var(--text-primary);
font-size: 14px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
color: var(--color-text-primary);
font-size: var(--font-size-base);
font-family: inherit;
resize: none;
min-height: 44px;
@@ -486,7 +486,7 @@ import {
}
.chat-input::placeholder {
color: var(--text-muted);
color: var(--color-text-muted);
}
.send-btn {
@@ -495,7 +495,7 @@ import {
border: none;
background: var(--color-primary);
color: white;
border-radius: 8px;
border-radius: var(--radius-lg);
cursor: pointer;
display: flex;
align-items: center;
@@ -520,8 +520,8 @@ import {
.input-hint {
margin: 8px 0 0;
font-size: 11px;
color: var(--text-muted);
font-size: var(--font-size-xs);
color: var(--color-text-muted);
text-align: center;
}
`],

View File

@@ -210,16 +210,16 @@ export interface ParsedObjectLink {
* Object link type metadata for display.
*/
export const OBJECT_LINK_METADATA: Record<ObjectLinkType, { icon: string; color: string; label: string }> = {
sbom: { icon: 'package', color: '#3b82f6', label: 'SBOM' },
reach: { icon: 'git-branch', color: '#8b5cf6', label: 'Reachability' },
runtime: { icon: 'activity', color: '#f59e0b', label: 'Runtime' },
vex: { icon: 'shield', color: '#10b981', label: 'VEX' },
attest: { icon: 'file-signature', color: '#D4920A', label: 'Attestation' },
auth: { icon: 'key', color: '#ef4444', label: 'Auth' },
docs: { icon: 'book', color: '#6B5A2E', label: 'Docs' },
finding: { icon: 'alert-triangle', color: '#f97316', label: 'Finding' },
scan: { icon: 'search', color: '#0ea5e9', label: 'Scan' },
policy: { icon: 'shield-check', color: '#22c55e', label: 'Policy' },
sbom: { icon: 'package', color: 'var(--color-status-info)', label: 'SBOM' },
reach: { icon: 'git-branch', color: 'var(--color-status-excepted)', label: 'Reachability' },
runtime: { icon: 'activity', color: 'var(--color-status-warning)', label: 'Runtime' },
vex: { icon: 'shield', color: 'var(--color-status-success)', label: 'VEX' },
attest: { icon: 'file-signature', color: 'var(--color-brand-secondary)', label: 'Attestation' },
auth: { icon: 'key', color: 'var(--color-status-error)', label: 'Auth' },
docs: { icon: 'book', color: 'var(--color-text-secondary)', label: 'Docs' },
finding: { icon: 'alert-triangle', color: 'var(--color-severity-high)', label: 'Finding' },
scan: { icon: 'search', color: 'var(--color-status-info)', label: 'Scan' },
policy: { icon: 'shield-check', color: 'var(--color-status-success)', label: 'Policy' },
};
/**

View File

@@ -121,38 +121,38 @@ import {
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
font-family: var(--font-mono, monospace);
text-decoration: none;
background: var(--chip-bg);
color: var(--chip-color);
border: 1px solid var(--chip-border);
background: var(--color-surface-secondary);
color: var(--color-text-secondary);
border: 1px solid var(--color-border-primary);
transition: all 0.15s ease;
cursor: pointer;
}
.object-link-chip:hover {
background: var(--chip-bg-hover);
border-color: var(--chip-border-hover);
background: var(--color-nav-hover);
border-color: var(--color-border-secondary);
}
.object-link-chip:focus-visible {
outline: 2px solid var(--chip-color);
outline: 2px solid var(--color-text-secondary);
outline-offset: 2px;
}
/* Type-specific colors */
.chip--sbom { --chip-color: #3b82f6; --chip-bg: rgba(59, 130, 246, 0.1); --chip-border: rgba(59, 130, 246, 0.2); }
.chip--reach { --chip-color: #8b5cf6; --chip-bg: rgba(139, 92, 246, 0.1); --chip-border: rgba(139, 92, 246, 0.2); }
.chip--runtime { --chip-color: #f59e0b; --chip-bg: rgba(245, 158, 11, 0.1); --chip-border: rgba(245, 158, 11, 0.2); }
.chip--vex { --chip-color: #10b981; --chip-bg: rgba(16, 185, 129, 0.1); --chip-border: rgba(16, 185, 129, 0.2); }
.chip--sbom { --chip-color: var(--color-status-info); --chip-bg: rgba(59, 130, 246, 0.1); --chip-border: rgba(59, 130, 246, 0.2); }
.chip--reach { --chip-color: var(--color-status-excepted); --chip-bg: rgba(139, 92, 246, 0.1); --chip-border: rgba(139, 92, 246, 0.2); }
.chip--runtime { --chip-color: var(--color-status-warning); --chip-bg: rgba(245, 158, 11, 0.1); --chip-border: rgba(245, 158, 11, 0.2); }
.chip--vex { --chip-color: var(--color-status-success); --chip-bg: rgba(16, 185, 129, 0.1); --chip-border: rgba(16, 185, 129, 0.2); }
.chip--attest { --chip-color: var(--color-brand-secondary); --chip-bg: var(--color-brand-primary-10); --chip-border: var(--color-brand-primary-20); }
.chip--auth { --chip-color: #ef4444; --chip-bg: rgba(239, 68, 68, 0.1); --chip-border: rgba(239, 68, 68, 0.2); }
.chip--auth { --chip-color: var(--color-status-error); --chip-bg: rgba(239, 68, 68, 0.1); --chip-border: rgba(239, 68, 68, 0.2); }
.chip--docs { --chip-color: var(--color-text-secondary); --chip-bg: rgba(100, 116, 139, 0.1); --chip-border: rgba(100, 116, 139, 0.2); }
.chip--finding { --chip-color: #f97316; --chip-bg: rgba(249, 115, 22, 0.1); --chip-border: rgba(249, 115, 22, 0.2); }
.chip--scan { --chip-color: #0ea5e9; --chip-bg: rgba(14, 165, 233, 0.1); --chip-border: rgba(14, 165, 233, 0.2); }
.chip--policy { --chip-color: #22c55e; --chip-bg: rgba(34, 197, 94, 0.1); --chip-border: rgba(34, 197, 94, 0.2); }
.chip--finding { --chip-color: var(--color-severity-high); --chip-bg: rgba(249, 115, 22, 0.1); --chip-border: rgba(249, 115, 22, 0.2); }
.chip--scan { --chip-color: var(--color-status-info); --chip-bg: rgba(14, 165, 233, 0.1); --chip-border: rgba(14, 165, 233, 0.2); }
.chip--policy { --chip-color: var(--color-status-success); --chip-bg: rgba(34, 197, 94, 0.1); --chip-border: rgba(34, 197, 94, 0.2); }
.chip-icon {
width: 14px;
@@ -161,7 +161,7 @@ import {
}
.chip-label {
font-weight: 500;
font-weight: var(--font-weight-medium);
opacity: 0.8;
}
@@ -187,10 +187,10 @@ import {
transform: translateX(-50%);
margin-bottom: 8px;
padding: 12px;
background: var(--bg-elevated);
border: 1px solid var(--border-subtle);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
background: var(--color-surface-elevated);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
min-width: 200px;
z-index: 100;
}
@@ -202,7 +202,7 @@ import {
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: var(--border-subtle);
border-top-color: var(--color-border-primary);
}
.preview-header {
@@ -213,34 +213,34 @@ import {
}
.preview-type {
font-weight: 600;
color: var(--preview-color);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
}
.preview-verified {
font-size: 11px;
font-size: var(--font-size-xs);
padding: 2px 6px;
border-radius: 4px;
border-radius: var(--radius-sm);
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
color: var(--color-status-error);
}
.preview-verified.verified {
background: rgba(34, 197, 94, 0.2);
color: #22c55e;
color: var(--color-status-success);
}
.preview-path {
font-family: var(--font-mono, monospace);
font-size: 12px;
color: var(--text-secondary);
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
word-break: break-all;
}
.preview-hint {
margin-top: 8px;
font-size: 11px;
color: var(--text-muted);
font-size: var(--font-size-xs);
color: var(--color-text-muted);
}
`],
})

View File

@@ -95,14 +95,14 @@ import {
.status-line {
margin: 0.5rem 0 0;
font-size: 0.875rem;
color: #1d4ed8;
font-weight: 600;
color: var(--color-status-info-text);
font-weight: var(--font-weight-semibold);
}
.panel {
background: #ffffff;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-lg);
padding: 1rem;
display: grid;
gap: 0.75rem;
@@ -123,8 +123,8 @@ import {
.event-line {
margin: 0;
font-size: 0.875rem;
color: #374151;
font-weight: 500;
color: var(--color-text-primary);
font-weight: var(--font-weight-medium);
}
`],
})

View File

@@ -137,8 +137,8 @@ import type { ExplanationCitation, EvidenceType } from '../../core/api/advisory-
position: relative;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 0.5rem;
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
overflow: hidden;
animation: slideIn 0.2s ease-out;
}
@@ -175,46 +175,46 @@ import type { ExplanationCitation, EvidenceType } from '../../core/api/advisory-
align-self: flex-start;
padding: 0.25rem 0.5rem;
font-size: 0.6875rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
letter-spacing: 0.025em;
border-radius: 0.25rem;
border-radius: var(--radius-sm);
}
.evidence-type-badge.type-advisory {
color: #7c3aed;
background: #ede9fe;
color: var(--color-status-excepted);
background: var(--color-status-excepted-bg);
}
.evidence-type-badge.type-sbom {
color: #0891b2;
background: #cffafe;
color: var(--color-status-info-text);
background: var(--color-status-info-bg);
}
.evidence-type-badge.type-reachability {
color: #ca8a04;
background: #fef9c3;
color: var(--color-severity-medium);
background: var(--color-status-warning-bg);
}
.evidence-type-badge.type-runtime {
color: #ea580c;
background: #ffedd5;
color: var(--color-severity-high);
background: var(--color-severity-high-bg);
}
.evidence-type-badge.type-vex {
color: #059669;
background: #d1fae5;
color: var(--color-status-success-text);
background: var(--color-status-success-bg);
}
.evidence-type-badge.type-patch {
color: var(--color-brand-primary);
background: #e0e7ff;
background: var(--color-status-excepted-bg);
}
.claim-title {
margin: 0;
font-size: 0.9375rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
line-height: 1.4;
}
@@ -229,7 +229,7 @@ import type { ExplanationCitation, EvidenceType } from '../../core/api/advisory-
flex-shrink: 0;
background: transparent;
border: none;
border-radius: 0.375rem;
border-radius: var(--radius-md);
cursor: pointer;
color: var(--color-text-secondary);
transition: background 0.15s;
@@ -255,8 +255,8 @@ import type { ExplanationCitation, EvidenceType } from '../../core/api/advisory-
padding: 0.625rem 0.75rem;
margin-bottom: 1rem;
font-size: 0.8125rem;
font-weight: 500;
border-radius: 0.375rem;
font-weight: var(--font-weight-medium);
border-radius: var(--radius-md);
color: var(--color-warning-text);
background: var(--color-warning-bg);
}
@@ -275,7 +275,7 @@ import type { ExplanationCitation, EvidenceType } from '../../core/api/advisory-
.section-label {
margin: 0 0 0.5rem;
font-size: 0.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-text-secondary);
@@ -288,7 +288,7 @@ import type { ExplanationCitation, EvidenceType } from '../../core/api/advisory-
.excerpt-content {
padding: 0.75rem;
background: var(--color-code-bg);
border-radius: 0.375rem;
border-radius: var(--radius-md);
overflow-x: auto;
}
@@ -311,7 +311,7 @@ import type { ExplanationCitation, EvidenceType } from '../../core/api/advisory-
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: var(--color-code-bg);
border-radius: 0.375rem;
border-radius: var(--radius-md);
}
.reference-id code {
@@ -334,7 +334,7 @@ import type { ExplanationCitation, EvidenceType } from '../../core/api/advisory-
flex-shrink: 0;
background: transparent;
border: none;
border-radius: 0.25rem;
border-radius: var(--radius-sm);
cursor: pointer;
color: var(--color-text-secondary);
}
@@ -372,8 +372,8 @@ import type { ExplanationCitation, EvidenceType } from '../../core/api/advisory-
gap: 0.375rem;
padding: 0.5rem 0.75rem;
font-size: 0.8125rem;
font-weight: 500;
border-radius: 0.375rem;
font-weight: var(--font-weight-medium);
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.15s;
}

View File

@@ -51,11 +51,11 @@ import { Component, EventEmitter, Input, Output, signal, computed } from '@angul
gap: 0.5rem;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
color: var(--color-primary-text);
background: var(--color-primary-bg);
border: 1px solid var(--color-primary-border);
border-radius: 0.375rem;
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.15s ease;
}
@@ -104,7 +104,7 @@ import { Component, EventEmitter, Input, Output, signal, computed } from '@angul
height: 1rem;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
border-radius: var(--radius-full);
animation: spin 0.75s linear infinite;
}

View File

@@ -171,7 +171,7 @@ import type {
.explanation-panel {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 0.5rem;
border-radius: var(--radius-lg);
overflow: hidden;
}
@@ -206,7 +206,7 @@ import type {
gap: 0.5rem;
margin: 0;
font-size: 0.9375rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
@@ -219,8 +219,8 @@ import type {
.authority-badge {
padding: 0.125rem 0.5rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: 9999px;
font-weight: var(--font-weight-medium);
border-radius: var(--radius-full);
}
.authority-badge.evidence-backed {
@@ -256,7 +256,7 @@ import type {
padding: 0;
background: transparent;
border: none;
border-radius: 0.25rem;
border-radius: var(--radius-sm);
cursor: pointer;
color: var(--color-text-secondary);
}
@@ -291,7 +291,7 @@ import type {
height: 2rem;
border: 3px solid var(--color-border);
border-top-color: var(--color-primary);
border-radius: 50%;
border-radius: var(--radius-full);
animation: spin 0.75s linear infinite;
margin-bottom: 0.75rem;
}
@@ -318,7 +318,7 @@ import type {
color: var(--color-primary-text);
background: var(--color-primary-bg);
border: 1px solid var(--color-primary-border);
border-radius: 0.375rem;
border-radius: var(--radius-md);
cursor: pointer;
}
@@ -326,7 +326,7 @@ import type {
margin-bottom: 1rem;
padding: 0.75rem;
background: var(--color-surface-alt);
border-radius: 0.375rem;
border-radius: var(--radius-md);
}
.summary-line {
@@ -338,7 +338,7 @@ import type {
.summary-label {
flex-shrink: 0;
font-size: 0.8125rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
min-width: 3.5rem;
}
@@ -374,7 +374,7 @@ import type {
.content-text :deep(h2) {
margin: 1rem 0 0.5rem;
font-size: 1rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.content-text :deep(p) {
@@ -385,7 +385,7 @@ import type {
padding: 0.125rem 0.25rem;
font-size: 0.8125rem;
background: var(--color-code-bg);
border-radius: 0.25rem;
border-radius: var(--radius-sm);
}
.citations-section {
@@ -399,12 +399,12 @@ import type {
gap: 0.5rem;
margin: 0 0 0.75rem;
font-size: 0.875rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.citation-rate {
font-weight: 400;
font-weight: var(--font-weight-normal);
color: var(--color-text-secondary);
}
@@ -427,7 +427,7 @@ import type {
text-align: left;
background: transparent;
border: 1px solid var(--color-border);
border-radius: 0.375rem;
border-radius: var(--radius-md);
cursor: pointer;
transition: background 0.15s;
}
@@ -440,39 +440,39 @@ import type {
flex-shrink: 0;
padding: 0.125rem 0.375rem;
font-size: 0.6875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
border-radius: 0.25rem;
border-radius: var(--radius-sm);
}
.citation-type.type-advisory {
color: #7c3aed;
background: #ede9fe;
color: var(--color-status-excepted);
background: var(--color-status-excepted-bg);
}
.citation-type.type-sbom {
color: #0891b2;
background: #cffafe;
color: var(--color-status-info-text);
background: var(--color-status-info-bg);
}
.citation-type.type-reachability {
color: #ca8a04;
background: #fef9c3;
color: var(--color-severity-medium);
background: var(--color-status-warning-bg);
}
.citation-type.type-runtime {
color: #ea580c;
background: #ffedd5;
color: var(--color-severity-high);
background: var(--color-severity-high-bg);
}
.citation-type.type-vex {
color: #059669;
background: #d1fae5;
color: var(--color-status-success-text);
background: var(--color-status-success-bg);
}
.citation-type.type-patch {
color: var(--color-brand-primary);
background: #e0e7ff;
background: var(--color-status-excepted-bg);
}
.citation-claim {

View File

@@ -94,7 +94,7 @@ import { Component, EventEmitter, Input, Output, signal } from '@angular/core';
height: 1.375rem;
padding: 0.125rem;
background: var(--color-toggle-off);
border-radius: 9999px;
border-radius: var(--radius-full);
transition: background 0.2s ease;
}
@@ -106,8 +106,8 @@ import { Component, EventEmitter, Input, Output, signal } from '@angular/core';
width: 1.125rem;
height: 1.125rem;
background: var(--color-surface);
border-radius: 50%;
box-shadow: 0 1px 3px rgb(0 0 0 / 0.1);
border-radius: var(--radius-full);
box-shadow: var(--shadow-sm);
transition: transform 0.2s ease;
}
@@ -138,7 +138,7 @@ import { Component, EventEmitter, Input, Output, signal } from '@angular/core';
.label-text {
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
}
@@ -157,10 +157,10 @@ import { Component, EventEmitter, Input, Output, signal } from '@angular/core';
gap: 0.375rem;
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
color: var(--color-info-text);
background: var(--color-info-bg);
border-radius: 9999px;
border-radius: var(--radius-full);
animation: fadeIn 0.2s ease;
}

View File

@@ -212,7 +212,7 @@ import type {
.pr-tracker {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 0.5rem;
border-radius: var(--radius-lg);
overflow: hidden;
}
@@ -239,7 +239,7 @@ import type {
.pr-number {
font-size: 0.875rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
}
@@ -247,7 +247,7 @@ import type {
flex: 1;
margin: 0;
font-size: 1rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
overflow: hidden;
text-overflow: ellipsis;
@@ -257,8 +257,8 @@ import type {
.pr-status-badge {
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 600;
border-radius: 9999px;
font-weight: var(--font-weight-semibold);
border-radius: var(--radius-full);
text-transform: capitalize;
}
@@ -304,8 +304,8 @@ import type {
.meta-item.scm-provider {
padding: 0.125rem 0.5rem;
background: var(--color-surface);
border-radius: 0.25rem;
font-weight: 500;
border-radius: var(--radius-sm);
font-weight: var(--font-weight-medium);
text-transform: capitalize;
}
@@ -319,15 +319,15 @@ import type {
justify-content: space-between;
margin: 0 0 0.75rem;
font-size: 0.8125rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.check-summary,
.review-summary {
font-weight: 500;
font-weight: var(--font-weight-medium);
padding: 0.125rem 0.5rem;
border-radius: 9999px;
border-radius: var(--radius-full);
font-size: 0.75rem;
}
@@ -408,7 +408,7 @@ import type {
height: 1rem;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
border-radius: var(--radius-full);
animation: spin 0.75s linear infinite;
}
@@ -448,7 +448,7 @@ import type {
gap: 0.625rem;
padding: 0.5rem;
background: var(--color-surface-alt);
border-radius: 0.375rem;
border-radius: var(--radius-md);
}
.reviewer-avatar {
@@ -458,16 +458,16 @@ import type {
width: 2rem;
height: 2rem;
font-size: 0.875rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-primary-contrast);
background: var(--color-primary);
border-radius: 50%;
border-radius: var(--radius-full);
}
.reviewer-name {
flex: 1;
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
}
@@ -511,7 +511,7 @@ import type {
.timeline-label {
font-size: 0.6875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
color: var(--color-text-secondary);
}
@@ -536,9 +536,9 @@ import type {
gap: 0.375rem;
padding: 0.5rem 0.875rem;
font-size: 0.8125rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-decoration: none;
border-radius: 0.375rem;
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.15s;
}

View File

@@ -227,7 +227,7 @@ import type {
.remediation-plan {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 0.5rem;
border-radius: var(--radius-lg);
overflow: hidden;
}
@@ -252,7 +252,7 @@ import type {
gap: 0.5rem;
margin: 0;
font-size: 0.9375rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
@@ -265,8 +265,8 @@ import type {
.status-badge {
padding: 0.125rem 0.5rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: 9999px;
font-weight: var(--font-weight-medium);
border-radius: var(--radius-full);
}
.status-badge.draft {
@@ -297,11 +297,11 @@ import type {
.strategy-badge {
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 0.25rem;
border-radius: var(--radius-sm);
text-transform: capitalize;
}
@@ -326,7 +326,7 @@ import type {
height: 2rem;
border: 3px solid var(--color-border);
border-top-color: var(--color-success);
border-radius: 50%;
border-radius: var(--radius-full);
animation: spin 0.75s linear infinite;
margin-bottom: 0.75rem;
}
@@ -353,7 +353,7 @@ import type {
color: var(--color-primary-text);
background: var(--color-primary-bg);
border: 1px solid var(--color-primary-border);
border-radius: 0.375rem;
border-radius: var(--radius-md);
cursor: pointer;
}
@@ -361,7 +361,7 @@ import type {
margin-bottom: 1rem;
padding: 0.75rem;
background: var(--color-surface-alt);
border-radius: 0.375rem;
border-radius: var(--radius-md);
}
.summary-line {
@@ -373,7 +373,7 @@ import type {
.summary-label {
flex-shrink: 0;
font-size: 0.8125rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
min-width: 3.5rem;
}
@@ -386,7 +386,7 @@ import type {
.section-title {
margin: 0 0 0.75rem;
font-size: 0.875rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
@@ -394,7 +394,7 @@ import type {
margin-bottom: 1.5rem;
padding: 1rem;
background: var(--color-surface-alt);
border-radius: 0.375rem;
border-radius: var(--radius-md);
}
.impact-grid {
@@ -413,7 +413,7 @@ import type {
.impact-value {
font-size: 1.25rem;
font-weight: 700;
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
}
@@ -438,7 +438,7 @@ import type {
.risk-label {
font-size: 0.8125rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
}
@@ -446,13 +446,13 @@ import type {
flex: 1;
height: 0.5rem;
background: var(--color-border);
border-radius: 9999px;
border-radius: var(--radius-full);
overflow: hidden;
}
.risk-fill {
height: 100%;
border-radius: 9999px;
border-radius: var(--radius-full);
transition: width 0.3s ease;
}
@@ -470,7 +470,7 @@ import type {
.risk-value {
font-size: 0.8125rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
@@ -487,7 +487,7 @@ import type {
.step-item {
margin-bottom: 0.5rem;
border: 1px solid var(--color-border);
border-radius: 0.375rem;
border-radius: var(--radius-md);
overflow: hidden;
}
@@ -514,82 +514,82 @@ import type {
width: 1.5rem;
height: 1.5rem;
font-size: 0.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-primary-text);
background: var(--color-primary-bg);
border-radius: 50%;
border-radius: var(--radius-full);
}
.step-type {
padding: 0.125rem 0.375rem;
font-size: 0.6875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
border-radius: 0.25rem;
border-radius: var(--radius-sm);
}
.step-type.type-upgrade {
color: #059669;
background: #d1fae5;
color: var(--color-status-success-text);
background: var(--color-status-success-bg);
}
.step-type.type-patch {
color: #7c3aed;
background: #ede9fe;
color: var(--color-status-excepted);
background: var(--color-status-excepted-bg);
}
.step-type.type-config {
color: #0891b2;
background: #cffafe;
color: var(--color-status-info-text);
background: var(--color-status-info-bg);
}
.step-type.type-workaround {
color: #ca8a04;
background: #fef9c3;
color: var(--color-severity-medium);
background: var(--color-status-warning-bg);
}
.step-type.type-vex_document {
color: var(--color-brand-primary);
background: #e0e7ff;
background: var(--color-status-excepted-bg);
}
.step-title {
flex: 1;
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
}
.breaking-badge {
padding: 0.125rem 0.375rem;
font-size: 0.6875rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-error-text);
background: var(--color-error-bg);
border-radius: 0.25rem;
border-radius: var(--radius-sm);
}
.step-risk {
padding: 0.125rem 0.375rem;
font-size: 0.6875rem;
font-weight: 500;
border-radius: 0.25rem;
font-weight: var(--font-weight-medium);
border-radius: var(--radius-sm);
text-transform: capitalize;
}
.step-risk.risk-low {
color: #065f46;
background: #d1fae5;
color: var(--color-status-success-text);
background: var(--color-status-success-bg);
}
.step-risk.risk-medium {
color: #92400e;
background: #fef3c7;
color: var(--color-status-warning-text);
background: var(--color-status-warning-bg);
}
.step-risk.risk-high {
color: #991b1b;
background: #fee2e2;
color: var(--color-status-error-text);
background: var(--color-status-error-bg);
}
.expand-icon {
@@ -620,7 +620,7 @@ import type {
display: block;
margin-bottom: 0.375rem;
font-size: 0.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
}
@@ -632,7 +632,7 @@ import type {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
background: var(--color-code-bg);
color: var(--color-code-text);
border-radius: 0.375rem;
border-radius: var(--radius-md);
overflow-x: auto;
}
@@ -649,7 +649,7 @@ import type {
color: var(--color-code-text);
background: var(--color-code-btn);
border: none;
border-radius: 0.25rem;
border-radius: var(--radius-sm);
cursor: pointer;
}
@@ -675,8 +675,8 @@ import type {
gap: 0.375rem;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
border-radius: 0.375rem;
font-weight: var(--font-weight-medium);
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.15s;
}

View File

@@ -85,7 +85,7 @@ interface ActionFeedback {
(click)="toggleActionsMenu()"
>
Actions
<span aria-hidden="true">&#9662;</span>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
</button>
@if (showActionsMenu()) {
<div class="actions-dropdown__menu">
@@ -334,8 +334,11 @@ interface ActionFeedback {
role="alert"
>
<span class="action-toast__icon" aria-hidden="true">
@if (feedback.type === 'success') { &#10003; }
@else { &#10007; }
@if (feedback.type === 'success') {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
} @else {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
}
</span>
<span class="action-toast__message">{{ feedback.message }}</span>
<button
@@ -368,7 +371,7 @@ interface ActionFeedback {
}
.breadcrumb__link {
color: var(--primary);
color: var(--color-brand-primary);
text-decoration: none;
&:hover {
@@ -377,11 +380,11 @@ interface ActionFeedback {
}
.breadcrumb__separator {
color: var(--text-muted);
color: var(--color-text-muted);
}
.breadcrumb__current {
color: var(--text-secondary);
color: var(--color-text-secondary);
}
/* Header */
@@ -402,20 +405,20 @@ interface ActionFeedback {
display: block;
width: 16px;
height: 16px;
border-radius: 50%;
border-radius: var(--radius-full);
margin-top: 0.5rem;
}
.detail-header__title h1 {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
color: var(--text-primary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.detail-header__id {
font-size: 0.8125rem;
color: var(--text-muted);
color: var(--color-text-muted);
}
.detail-header__actions {
@@ -432,27 +435,27 @@ interface ActionFeedback {
.tag {
padding: 0.25rem 0.75rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
background: var(--surface-secondary);
color: var(--text-secondary);
font-weight: var(--font-weight-medium);
background: var(--color-surface-secondary);
color: var(--color-text-secondary);
}
.tag--env {
background: var(--tag-env-bg);
color: var(--tag-env-text);
background: var(--color-surface-tertiary);
color: var(--color-text-secondary);
}
.tag--version {
background: var(--tag-version-bg);
color: var(--tag-version-text);
background: var(--color-surface-tertiary);
color: var(--color-text-secondary);
}
/* Tabs */
.tabs {
display: flex;
border-bottom: 1px solid var(--border-default);
border-bottom: 1px solid var(--color-border-primary);
margin-bottom: 1.5rem;
}
@@ -462,18 +465,18 @@ interface ActionFeedback {
border: none;
border-bottom: 2px solid transparent;
font-size: 0.875rem;
font-weight: 500;
color: var(--text-secondary);
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
cursor: pointer;
transition: all 0.15s;
&:hover {
color: var(--text-primary);
color: var(--color-text-primary);
}
&--active {
color: var(--primary);
border-bottom-color: var(--primary);
color: var(--color-brand-primary);
border-bottom-color: var(--color-brand-primary);
}
}
@@ -487,44 +490,44 @@ interface ActionFeedback {
.stat-card {
padding: 1rem;
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
text-align: center;
}
.stat-card__label {
display: block;
font-size: 0.75rem;
color: var(--text-muted);
color: var(--color-text-muted);
text-transform: uppercase;
margin-bottom: 0.25rem;
}
.stat-card__value {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
/* Section Card */
.section-card {
padding: 1.25rem;
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
margin-bottom: 1.5rem;
&--warning {
border-left: 3px solid var(--status-warning);
border-left: 3px solid var(--color-status-warning);
}
}
.section-card__title {
margin: 0 0 1rem;
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
/* Resource Meters */
@@ -542,14 +545,14 @@ interface ActionFeedback {
.resource-meter__bar {
height: 8px;
background: var(--surface-secondary);
border-radius: 4px;
background: var(--color-surface-secondary);
border-radius: var(--radius-sm);
overflow: hidden;
}
.resource-meter__fill {
height: 100%;
border-radius: 4px;
border-radius: var(--radius-sm);
transition: width 0.3s;
}
@@ -562,31 +565,31 @@ interface ActionFeedback {
}
.detail-list dt {
font-weight: 500;
color: var(--text-secondary);
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
font-size: 0.8125rem;
}
.detail-list dd {
margin: 0;
font-size: 0.8125rem;
color: var(--text-primary);
color: var(--color-text-primary);
}
.detail-list code {
font-size: 0.75rem;
background: var(--surface-secondary);
background: var(--color-surface-secondary);
padding: 0.125rem 0.375rem;
border-radius: 3px;
border-radius: var(--radius-sm);
}
.warning-badge {
display: inline-block;
margin-left: 0.5rem;
padding: 0.125rem 0.5rem;
background: var(--warning-bg);
color: var(--warning-text);
border-radius: 4px;
background: var(--color-status-warning-bg);
color: var(--color-status-warning-text);
border-radius: var(--radius-sm);
font-size: 0.75rem;
}
@@ -601,10 +604,10 @@ interface ActionFeedback {
right: 0;
margin-top: 0.25rem;
min-width: 180px;
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
z-index: 100;
overflow: hidden;
@@ -619,18 +622,18 @@ interface ActionFeedback {
cursor: pointer;
&:hover {
background: var(--surface-hover);
background: var(--color-nav-hover);
}
&.danger {
color: var(--status-error);
color: var(--color-status-error);
}
}
hr {
margin: 0.25rem 0;
border: none;
border-top: 1px solid var(--border-default);
border-top: 1px solid var(--color-border-primary);
}
}
@@ -640,20 +643,20 @@ interface ActionFeedback {
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
border: 1px solid transparent;
}
.btn--secondary {
background: var(--surface-primary);
border-color: var(--border-default);
color: var(--text-primary);
background: var(--color-surface-primary);
border-color: var(--color-border-primary);
color: var(--color-text-primary);
&:hover {
background: var(--surface-hover);
background: var(--color-nav-hover);
}
}
@@ -669,9 +672,9 @@ interface ActionFeedback {
.spinner {
width: 40px;
height: 40px;
border: 3px solid var(--border-default);
border-top-color: var(--primary);
border-radius: 50%;
border: 3px solid var(--color-border-primary);
border-top-color: var(--color-brand-primary);
border-radius: var(--radius-full);
animation: spin 0.8s linear infinite;
margin-bottom: 1rem;
}
@@ -681,12 +684,12 @@ interface ActionFeedback {
}
.error-state__message {
color: var(--status-error);
color: var(--color-status-error);
margin-bottom: 1rem;
}
.placeholder {
color: var(--text-muted);
color: var(--color-text-muted);
font-style: italic;
text-align: center;
padding: 2rem;
@@ -701,10 +704,10 @@ interface ActionFeedback {
align-items: center;
gap: 0.75rem;
padding: 0.875rem 1rem;
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
z-index: 1000;
animation: slide-in-toast 0.3s ease-out;
}
@@ -721,11 +724,11 @@ interface ActionFeedback {
}
.action-toast--success {
border-left: 3px solid var(--status-success);
border-left: 3px solid var(--color-status-success);
}
.action-toast--error {
border-left: 3px solid var(--status-error);
border-left: 3px solid var(--color-status-error);
}
.action-toast__icon {
@@ -734,25 +737,23 @@ interface ActionFeedback {
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 0.875rem;
font-weight: 600;
border-radius: var(--radius-full);
}
.action-toast--success .action-toast__icon {
background: rgba(16, 185, 129, 0.1);
color: var(--status-success);
color: var(--color-status-success);
}
.action-toast--error .action-toast__icon {
background: rgba(239, 68, 68, 0.1);
color: var(--status-error);
color: var(--color-status-error);
}
.action-toast__message {
font-size: 0.875rem;
font-weight: 500;
color: var(--text-primary);
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
}
.action-toast__close {
@@ -761,11 +762,11 @@ interface ActionFeedback {
background: none;
border: none;
font-size: 1.25rem;
color: var(--text-muted);
color: var(--color-text-muted);
cursor: pointer;
&:hover {
color: var(--text-primary);
color: var(--color-text-primary);
}
}
`]

View File

@@ -55,7 +55,7 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
}
</div>
<button type="button" class="btn btn--secondary" (click)="refresh()">
<span class="btn__icon" aria-hidden="true">&#8635;</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="btn__icon" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
Refresh
</button>
<button type="button" class="btn btn--primary" (click)="openOnboardingWizard()">
@@ -191,7 +191,7 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
aria-label="Grid view"
title="Card grid"
>
<span aria-hidden="true">&#9638;&#9638;</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
</button>
<button
type="button"
@@ -201,7 +201,7 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
aria-label="Heatmap view"
title="Capacity heatmap"
>
<span aria-hidden="true">&#9632;</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/></svg>
</button>
<button
type="button"
@@ -211,7 +211,7 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
aria-label="Table view"
title="Comparison table"
>
<span aria-hidden="true">&#9776;</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
</button>
</div>
</div>
@@ -307,14 +307,14 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
.page-header__title {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
color: var(--text-primary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.page-header__subtitle {
margin: 0.25rem 0 0;
font-size: 0.875rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.page-header__actions {
@@ -329,36 +329,36 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.75rem;
background: var(--surface-secondary);
border-radius: 9999px;
background: var(--color-surface-secondary);
border-radius: var(--radius-full);
font-size: 0.75rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.realtime-status--connected {
background: rgba(16, 185, 129, 0.1);
color: var(--status-success);
color: var(--color-status-success);
}
.realtime-status__indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--text-muted);
border-radius: var(--radius-full);
background: var(--color-text-muted);
}
.realtime-status__indicator--connected {
background: var(--status-success);
background: var(--color-status-success);
animation: pulse-connected 2s infinite;
}
.realtime-status__indicator--connecting {
background: var(--status-warning);
background: var(--color-status-warning);
animation: pulse-connecting 1s infinite;
}
.realtime-status__indicator--error {
background: var(--status-error);
background: var(--color-status-error);
}
@keyframes pulse-connected {
@@ -372,7 +372,7 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
}
.realtime-status__label {
font-weight: 500;
font-weight: var(--font-weight-medium);
}
/* Buttons */
@@ -381,50 +381,51 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
transition: all 0.15s;
border: 1px solid transparent;
}
.btn--primary {
background: var(--primary);
background: var(--color-brand-primary);
color: var(--color-text-heading);
&:hover {
background: var(--primary-hover);
background: var(--color-brand-primary-hover);
}
}
.btn--secondary {
background: var(--surface-primary);
border-color: var(--border-default);
color: var(--text-primary);
background: var(--color-surface-primary);
border-color: var(--color-border-primary);
color: var(--color-text-primary);
&:hover {
background: var(--surface-hover);
background: var(--color-nav-hover);
}
}
.btn--text {
background: transparent;
color: var(--primary);
color: var(--color-brand-primary);
padding: 0.5rem;
&:hover {
background: var(--surface-hover);
background: var(--color-nav-hover);
}
&:disabled {
color: var(--text-muted);
color: var(--color-text-muted);
cursor: not-allowed;
}
}
.btn__icon {
font-size: 1rem;
display: inline-flex;
align-items: center;
}
/* KPI Strip */
@@ -440,34 +441,34 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
flex: 1;
min-width: 120px;
padding: 1rem;
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
text-align: center;
&--success {
border-left: 3px solid var(--status-success);
border-left: 3px solid var(--color-status-success);
}
&--warning {
border-left: 3px solid var(--status-warning);
border-left: 3px solid var(--color-status-warning);
}
&--danger {
border-left: 3px solid var(--status-error);
border-left: 3px solid var(--color-status-error);
}
}
.kpi-card__value {
display: block;
font-size: 1.5rem;
font-weight: 700;
color: var(--text-primary);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
}
.kpi-card__label {
font-size: 0.75rem;
color: var(--text-muted);
color: var(--color-text-muted);
text-transform: uppercase;
letter-spacing: 0.02em;
}
@@ -478,8 +479,8 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
flex-wrap: wrap;
gap: 1rem;
padding: 1rem;
background: var(--surface-secondary);
border-radius: 8px;
background: var(--color-surface-secondary);
border-radius: var(--radius-lg);
margin-bottom: 1rem;
}
@@ -491,14 +492,14 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
.search-input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-default);
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
font-size: 0.875rem;
&:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
border-color: var(--color-brand-primary);
box-shadow: 0 0 0 2px var(--color-focus-ring);
}
}
@@ -517,8 +518,8 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
.filter-group__label {
font-size: 0.75rem;
font-weight: 500;
color: var(--text-secondary);
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
}
.filter-chips {
@@ -528,36 +529,36 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
.filter-chip {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
border: 1px solid var(--border-default);
background: var(--surface-primary);
color: var(--text-secondary);
font-weight: var(--font-weight-medium);
border: 1px solid var(--color-border-primary);
background: var(--color-surface-primary);
color: var(--color-text-secondary);
cursor: pointer;
transition: all 0.15s;
&:hover {
border-color: var(--chip-color, var(--primary));
border-color: var(--chip-color, var(--color-brand-primary));
}
&--active {
background: var(--chip-color, var(--primary));
border-color: var(--chip-color, var(--primary));
background: var(--chip-color, var(--color-brand-primary));
border-color: var(--chip-color, var(--color-brand-primary));
color: white;
}
}
.filter-select {
padding: 0.375rem 0.75rem;
border: 1px solid var(--border-default);
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
font-size: 0.8125rem;
background: var(--surface-primary);
background: var(--color-surface-primary);
&:focus {
outline: none;
border-color: var(--primary);
border-color: var(--color-brand-primary);
}
}
@@ -571,34 +572,36 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
.view-controls__count {
font-size: 0.8125rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.view-controls__toggle {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
background: var(--surface-secondary);
border-radius: 6px;
background: var(--color-surface-secondary);
border-radius: var(--radius-md);
}
.view-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.375rem 0.5rem;
border: none;
background: transparent;
border-radius: 4px;
border-radius: var(--radius-sm);
cursor: pointer;
color: var(--text-muted);
font-size: 1rem;
color: var(--color-text-muted);
&:hover {
color: var(--text-primary);
color: var(--color-text-primary);
}
&--active {
background: var(--surface-primary);
color: var(--text-primary);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
background: var(--color-surface-primary);
color: var(--color-text-primary);
box-shadow: var(--shadow-sm);
}
}
@@ -624,9 +627,9 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
.spinner {
width: 40px;
height: 40px;
border: 3px solid var(--border-default);
border-top-color: var(--primary);
border-radius: 50%;
border: 3px solid var(--color-border-primary);
border-top-color: var(--color-brand-primary);
border-radius: var(--radius-full);
animation: spin 0.8s linear infinite;
margin-bottom: 1rem;
}
@@ -636,12 +639,12 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
}
.error-state__message {
color: var(--status-error);
color: var(--color-status-error);
margin-bottom: 1rem;
}
.empty-state__message {
color: var(--text-secondary);
color: var(--color-text-secondary);
margin-bottom: 1rem;
}
@@ -649,13 +652,13 @@ type ViewMode = 'grid' | 'heatmap' | 'table';
.page-footer {
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 1px solid var(--border-default);
border-top: 1px solid var(--color-border-primary);
text-align: center;
}
.page-footer__refresh {
font-size: 0.75rem;
color: var(--text-muted);
color: var(--color-text-muted);
}
/* Responsive */

View File

@@ -22,7 +22,7 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
<!-- Header -->
<header class="wizard-header">
<a routerLink="/ops/agents" class="wizard-header__back">
&larr; Back to Fleet
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg> Back to Fleet
</a>
<h1 class="wizard-header__title">Add New Agent</h1>
</header>
@@ -131,10 +131,10 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
<div class="spinner"></div>
<p>Listening for agent heartbeat...</p>
} @else if (isVerified()) {
<div class="success-icon">&#10003;</div>
<div class="success-icon"><svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg></div>
<p>Agent connected successfully!</p>
} @else {
<div class="pending-icon">&#8987;</div>
<div class="pending-icon"><svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></div>
<p>Agent not yet connected</p>
<button type="button" class="btn btn--secondary" (click)="startVerification()">
Retry
@@ -154,7 +154,7 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
}
@case ('complete') {
<section class="step-content step-content--center">
<div class="complete-icon">&#10003;</div>
<div class="complete-icon"><svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg></div>
<h2>Agent Onboarded!</h2>
<p>Your agent is now ready to receive tasks.</p>
@@ -208,7 +208,7 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
.wizard-header__back {
display: inline-block;
margin-bottom: 0.5rem;
color: var(--primary);
color: var(--color-brand-primary);
text-decoration: none;
font-size: 0.875rem;
@@ -220,7 +220,7 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
.wizard-header__title {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
/* Progress */
@@ -237,7 +237,7 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
left: 40px;
right: 40px;
height: 2px;
background: var(--border-default);
background: var(--color-border-primary);
}
}
@@ -252,43 +252,43 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
.progress-step__number {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--surface-primary);
border: 2px solid var(--border-default);
border-radius: var(--radius-full);
background: var(--color-surface-primary);
border: 2px solid var(--color-border-primary);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-weight: var(--font-weight-semibold);
font-size: 0.875rem;
margin-bottom: 0.5rem;
}
.progress-step__label {
font-size: 0.75rem;
color: var(--text-muted);
color: var(--color-text-muted);
}
.progress-step--active .progress-step__number {
border-color: var(--primary);
color: var(--primary);
border-color: var(--color-brand-primary);
color: var(--color-brand-primary);
}
.progress-step--active .progress-step__label {
color: var(--primary);
font-weight: 500;
color: var(--color-brand-primary);
font-weight: var(--font-weight-medium);
}
.progress-step--completed .progress-step__number {
background: var(--primary);
border-color: var(--primary);
background: var(--color-brand-primary);
border-color: var(--color-brand-primary);
color: white;
}
/* Content */
.wizard-content {
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
padding: 2rem;
min-height: 300px;
}
@@ -299,7 +299,7 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
}
.step-content > p {
color: var(--text-secondary);
color: var(--color-text-secondary);
margin-bottom: 1.5rem;
}
@@ -318,31 +318,31 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
flex-direction: column;
align-items: flex-start;
padding: 1rem;
border: 2px solid var(--border-default);
border-radius: 8px;
background: var(--surface-primary);
border: 2px solid var(--color-border-primary);
border-radius: var(--radius-lg);
background: var(--color-surface-primary);
cursor: pointer;
text-align: left;
transition: all 0.15s;
&:hover {
border-color: var(--primary);
border-color: var(--color-brand-primary);
}
&--selected {
border-color: var(--primary);
border-color: var(--color-brand-primary);
background: rgba(59, 130, 246, 0.05);
}
}
.env-option__name {
font-weight: 600;
font-weight: var(--font-weight-semibold);
margin-bottom: 0.25rem;
}
.env-option__desc {
font-size: 0.8125rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
/* Form */
@@ -351,7 +351,7 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
label {
display: block;
font-weight: 500;
font-weight: var(--font-weight-medium);
margin-bottom: 0.5rem;
}
}
@@ -359,28 +359,28 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
.form-input {
width: 100%;
padding: 0.625rem 0.75rem;
border: 1px solid var(--border-default);
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
font-size: 0.875rem;
&:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
border-color: var(--color-brand-primary);
box-shadow: 0 0 0 2px var(--color-focus-ring);
}
}
/* Install Command */
.install-command {
position: relative;
background: var(--surface-code);
border-radius: 8px;
background: var(--color-surface-tertiary);
border-radius: var(--radius-lg);
margin-bottom: 1.5rem;
pre {
margin: 0;
padding: 1rem;
color: #e5e7eb;
color: var(--color-border-primary);
font-size: 0.8125rem;
overflow-x: auto;
}
@@ -392,8 +392,8 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
padding: 0.375rem 0.75rem;
background: rgba(255, 255, 255, 0.1);
border: none;
border-radius: 4px;
color: #e5e7eb;
border-radius: var(--radius-sm);
color: var(--color-border-primary);
font-size: 0.75rem;
cursor: pointer;
@@ -413,7 +413,7 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
margin: 0;
padding-left: 1.25rem;
font-size: 0.8125rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
}
@@ -428,9 +428,9 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
.spinner {
width: 48px;
height: 48px;
border: 3px solid var(--border-default);
border-top-color: var(--primary);
border-radius: 50%;
border: 3px solid var(--color-border-primary);
border-top-color: var(--color-brand-primary);
border-radius: var(--radius-full);
animation: spin 0.8s linear infinite;
margin-bottom: 1rem;
}
@@ -443,22 +443,21 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
.pending-icon {
width: 48px;
height: 48px;
border-radius: 50%;
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-bottom: 1rem;
}
.success-icon {
background: var(--status-success);
background: var(--color-status-success);
color: white;
}
.pending-icon {
background: var(--surface-secondary);
color: var(--text-muted);
background: var(--color-surface-secondary);
color: var(--color-text-muted);
}
.troubleshooting {
@@ -467,19 +466,19 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
summary {
cursor: pointer;
color: var(--primary);
color: var(--color-brand-primary);
}
ul {
margin-top: 0.5rem;
padding-left: 1.25rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
code {
background: var(--surface-secondary);
background: var(--color-surface-secondary);
padding: 0.125rem 0.375rem;
border-radius: 3px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
}
}
@@ -488,13 +487,12 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
.complete-icon {
width: 64px;
height: 64px;
border-radius: 50%;
background: var(--status-success);
border-radius: var(--radius-full);
background: var(--color-status-success);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
margin: 0 auto 1rem;
}
@@ -515,9 +513,9 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
/* Buttons */
.btn {
padding: 0.625rem 1.25rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
border: 1px solid transparent;
text-decoration: none;
@@ -529,21 +527,21 @@ type WizardStep = 'environment' | 'configure' | 'install' | 'verify' | 'complete
}
.btn--primary {
background: var(--primary);
background: var(--color-brand-primary);
color: var(--color-text-heading);
&:hover:not(:disabled) {
background: var(--primary-hover);
background: var(--color-brand-primary-hover);
}
}
.btn--secondary {
background: var(--surface-primary);
border-color: var(--border-default);
color: var(--text-primary);
background: var(--color-surface-primary);
border-color: var(--color-border-primary);
color: var(--color-text-primary);
&:hover:not(:disabled) {
background: var(--surface-hover);
background: var(--color-nav-hover);
}
}
`]

View File

@@ -81,13 +81,13 @@ const ACTION_CONFIGS: Record<AgentAction, ActionConfig> = {
<h2 [id]="'modal-title-' + config().action" class="modal__title">
@switch (config().confirmVariant) {
@case ('danger') {
<span class="modal__icon modal__icon--danger" aria-hidden="true">&#9888;</span>
<span class="modal__icon modal__icon--danger" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span>
}
@case ('warning') {
<span class="modal__icon modal__icon--warning" aria-hidden="true">&#9888;</span>
<span class="modal__icon modal__icon--warning" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span>
}
@default {
<span class="modal__icon modal__icon--info" aria-hidden="true">&#9432;</span>
<span class="modal__icon modal__icon--info" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></span>
}
}
{{ config().title }}
@@ -185,9 +185,9 @@ const ACTION_CONFIGS: Record<AgentAction, ActionConfig> = {
.modal {
width: 100%;
max-width: 480px;
background: var(--surface-primary);
border-radius: 12px;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.2);
background: var(--color-surface-primary);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-xl);
animation: slide-up 0.2s ease-out;
}
@@ -207,7 +207,7 @@ const ACTION_CONFIGS: Record<AgentAction, ActionConfig> = {
align-items: center;
justify-content: space-between;
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--border-default);
border-bottom: 1px solid var(--color-border-primary);
}
.modal__title {
@@ -216,23 +216,24 @@ const ACTION_CONFIGS: Record<AgentAction, ActionConfig> = {
gap: 0.75rem;
margin: 0;
font-size: 1.125rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.modal__icon {
font-size: 1.25rem;
display: inline-flex;
align-items: center;
}
.modal__icon--danger {
color: var(--status-error);
color: var(--color-status-error);
}
.modal__icon--warning {
color: var(--status-warning);
color: var(--color-status-warning);
}
.modal__icon--info {
color: var(--primary);
color: var(--color-brand-primary);
}
.modal__close {
@@ -243,14 +244,14 @@ const ACTION_CONFIGS: Record<AgentAction, ActionConfig> = {
justify-content: center;
background: none;
border: none;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 1.5rem;
color: var(--text-muted);
color: var(--color-text-muted);
cursor: pointer;
&:hover {
background: var(--surface-hover);
color: var(--text-primary);
background: var(--color-nav-hover);
color: var(--color-text-primary);
}
}
@@ -262,7 +263,7 @@ const ACTION_CONFIGS: Record<AgentAction, ActionConfig> = {
margin: 0 0 1rem;
font-size: 0.9375rem;
line-height: 1.6;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.modal__agent-info {
@@ -270,18 +271,18 @@ const ACTION_CONFIGS: Record<AgentAction, ActionConfig> = {
flex-direction: column;
gap: 0.25rem;
padding: 0.75rem 1rem;
background: var(--surface-secondary);
border-radius: 6px;
background: var(--color-surface-secondary);
border-radius: var(--radius-md);
margin-bottom: 1rem;
strong {
font-size: 0.9375rem;
color: var(--text-primary);
color: var(--color-text-primary);
}
code {
font-size: 0.75rem;
color: var(--text-muted);
color: var(--color-text-muted);
}
}
@@ -293,28 +294,28 @@ const ACTION_CONFIGS: Record<AgentAction, ActionConfig> = {
display: block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--text-primary);
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
}
.modal__input {
width: 100%;
padding: 0.625rem 0.875rem;
border: 1px solid var(--border-default);
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
font-size: 0.9375rem;
&:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
border-color: var(--color-brand-primary);
box-shadow: 0 0 0 3px var(--color-focus-ring);
}
}
.modal__input-error {
margin: 0.5rem 0 0;
font-size: 0.8125rem;
color: var(--status-error);
color: var(--color-status-error);
}
.modal__footer {
@@ -322,8 +323,8 @@ const ACTION_CONFIGS: Record<AgentAction, ActionConfig> = {
justify-content: flex-end;
gap: 0.75rem;
padding: 1rem 1.5rem;
background: var(--surface-secondary);
border-top: 1px solid var(--border-default);
background: var(--color-surface-secondary);
border-top: 1px solid var(--color-border-primary);
border-radius: 0 0 12px 12px;
}
@@ -334,9 +335,9 @@ const ACTION_CONFIGS: Record<AgentAction, ActionConfig> = {
justify-content: center;
gap: 0.5rem;
padding: 0.625rem 1.25rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
border: 1px solid transparent;
transition: all 0.15s;
@@ -348,39 +349,39 @@ const ACTION_CONFIGS: Record<AgentAction, ActionConfig> = {
}
.btn--secondary {
background: var(--surface-primary);
border-color: var(--border-default);
color: var(--text-primary);
background: var(--color-surface-primary);
border-color: var(--color-border-primary);
color: var(--color-text-primary);
&:hover:not(:disabled) {
background: var(--surface-hover);
background: var(--color-nav-hover);
}
}
.btn--primary {
background: var(--primary);
background: var(--color-brand-primary);
color: var(--color-text-heading);
&:hover:not(:disabled) {
background: var(--primary-hover);
background: var(--color-brand-primary-hover);
}
}
.btn--warning {
background: var(--status-warning);
background: var(--color-status-warning);
color: white;
&:hover:not(:disabled) {
background: #d97706;
background: var(--color-status-warning-text);
}
}
.btn--danger {
background: var(--status-error);
background: var(--color-status-error);
color: white;
&:hover:not(:disabled) {
background: #dc2626;
background: var(--color-status-error);
}
}
@@ -389,7 +390,7 @@ const ACTION_CONFIGS: Record<AgentAction, ActionConfig> = {
height: 16px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
border-radius: var(--radius-full);
animation: spin 0.6s linear infinite;
}

View File

@@ -50,7 +50,7 @@ import {
(click)="onMenuClick($event)"
aria-label="Agent actions"
>
<span aria-hidden="true">&#8942;</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
</button>
</header>
@@ -94,7 +94,7 @@ import {
<!-- Certificate Warning -->
@if (hasCertificateWarning()) {
<div class="agent-card__warning">
<span class="warning-icon" aria-hidden="true">&#9888;</span>
<span class="warning-icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span>
<span>Certificate expires in {{ agent().certificate?.daysUntilExpiry }} days</span>
</div>
}
@@ -102,32 +102,32 @@ import {
`,
styles: [`
.agent-card {
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
padding: 1rem;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
border-color: var(--border-hover);
border-color: var(--color-border-secondary);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
transform: translateY(-2px);
}
&:focus-visible {
outline: 2px solid var(--focus-ring);
outline: 2px solid var(--color-brand-primary);
outline-offset: 2px;
}
&--selected {
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
border-color: var(--color-brand-primary);
box-shadow: 0 0 0 2px var(--color-focus-ring);
}
&--offline {
opacity: 0.8;
background: var(--surface-muted);
background: var(--color-surface-tertiary);
}
}
@@ -147,7 +147,7 @@ import {
display: block;
width: 10px;
height: 10px;
border-radius: 50%;
border-radius: var(--radius-full);
animation: pulse 2s infinite;
}
@@ -169,8 +169,8 @@ import {
.agent-card__name {
margin: 0;
font-size: 0.9375rem;
font-weight: 600;
color: var(--text-primary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -178,24 +178,25 @@ import {
.agent-card__id {
font-size: 0.75rem;
color: var(--text-muted);
color: var(--color-text-muted);
font-family: var(--font-mono, monospace);
}
.agent-card__menu-btn {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.25rem;
background: none;
border: none;
border-radius: 4px;
border-radius: var(--radius-sm);
cursor: pointer;
color: var(--text-muted);
font-size: 1.25rem;
line-height: 1;
color: var(--color-text-muted);
&:hover {
background: var(--surface-hover);
color: var(--text-primary);
background: var(--color-nav-hover);
color: var(--color-text-primary);
}
}
@@ -209,21 +210,21 @@ import {
display: inline-flex;
align-items: center;
padding: 0.125rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.6875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
letter-spacing: 0.02em;
}
.agent-card__tag--env {
background: var(--tag-env-bg);
color: var(--tag-env-text);
background: var(--color-surface-tertiary);
color: var(--color-text-secondary);
}
.agent-card__tag--version {
background: var(--tag-version-bg);
color: var(--tag-version-text);
background: var(--color-surface-tertiary);
color: var(--color-text-secondary);
}
.agent-card__capacity {
@@ -234,24 +235,24 @@ import {
display: flex;
justify-content: space-between;
font-size: 0.75rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
margin-bottom: 0.25rem;
}
.capacity-value {
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.capacity-bar {
height: 6px;
background: var(--surface-secondary);
border-radius: 3px;
background: var(--color-surface-secondary);
border-radius: var(--radius-sm);
overflow: hidden;
}
.capacity-bar__fill {
height: 100%;
border-radius: 3px;
border-radius: var(--radius-sm);
transition: width 0.3s ease;
}
@@ -260,7 +261,7 @@ import {
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
padding-top: 0.75rem;
border-top: 1px solid var(--border-default);
border-top: 1px solid var(--color-border-primary);
}
.metric {
@@ -272,14 +273,14 @@ import {
font-size: 0.625rem;
text-transform: uppercase;
letter-spacing: 0.03em;
color: var(--text-muted);
color: var(--color-text-muted);
margin-bottom: 0.125rem;
}
.metric__value {
font-size: 0.875rem;
font-weight: 600;
color: var(--text-primary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.agent-card__warning {
@@ -288,14 +289,14 @@ import {
gap: 0.5rem;
margin-top: 0.75rem;
padding: 0.5rem;
background: var(--warning-bg);
border-radius: 4px;
background: var(--color-status-warning-bg);
border-radius: var(--radius-sm);
font-size: 0.75rem;
color: var(--warning-text);
color: var(--color-status-warning-text);
}
.warning-icon {
color: var(--warning);
color: var(--color-status-warning);
}
`]
})

View File

@@ -68,13 +68,13 @@ import { AgentHealthResult } from '../../models/agent.models';
<div class="check-item__status">
@switch (check.status) {
@case ('pass') {
<span class="status-icon status-icon--pass">&#10003;</span>
<span class="status-icon status-icon--pass"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg></span>
}
@case ('warn') {
<span class="status-icon status-icon--warn">&#9888;</span>
<span class="status-icon status-icon--warn"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span>
}
@case ('fail') {
<span class="status-icon status-icon--fail">&#10007;</span>
<span class="status-icon status-icon--fail"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></span>
}
}
</div>
@@ -94,7 +94,7 @@ import { AgentHealthResult } from '../../models/agent.models';
(click)="rerunCheck.emit(check.checkId)"
title="Re-run this check"
>
&#8635;
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
</button>
</div>
</article>
@@ -133,7 +133,7 @@ import { AgentHealthResult } from '../../models/agent.models';
.tab-header__title {
margin: 0;
font-size: 1.125rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
/* Summary */
@@ -146,33 +146,33 @@ import { AgentHealthResult } from '../../models/agent.models';
.summary-item {
flex: 1;
padding: 1rem;
border-radius: 8px;
border-radius: var(--radius-lg);
text-align: center;
background: var(--surface-secondary);
border: 1px solid var(--border-default);
background: var(--color-surface-secondary);
border: 1px solid var(--color-border-primary);
&--pass {
border-left: 3px solid var(--status-success);
border-left: 3px solid var(--color-status-success);
}
&--warn {
border-left: 3px solid var(--status-warning);
border-left: 3px solid var(--color-status-warning);
}
&--fail {
border-left: 3px solid var(--status-error);
border-left: 3px solid var(--color-status-error);
}
}
.summary-item__count {
display: block;
font-size: 1.5rem;
font-weight: 700;
font-weight: var(--font-weight-bold);
}
.summary-item__label {
font-size: 0.75rem;
color: var(--text-muted);
color: var(--color-text-muted);
text-transform: uppercase;
}
@@ -188,9 +188,9 @@ import { AgentHealthResult } from '../../models/agent.models';
align-items: flex-start;
gap: 1rem;
padding: 1rem;
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
transition: box-shadow 0.15s;
&:hover {
@@ -198,15 +198,15 @@ import { AgentHealthResult } from '../../models/agent.models';
}
&--pass {
border-left: 3px solid var(--status-success);
border-left: 3px solid var(--color-status-success);
}
&--warn {
border-left: 3px solid var(--status-warning);
border-left: 3px solid var(--color-status-warning);
}
&--fail {
border-left: 3px solid var(--status-error);
border-left: 3px solid var(--color-status-error);
}
}
@@ -220,22 +220,21 @@ import { AgentHealthResult } from '../../models/agent.models';
justify-content: center;
width: 24px;
height: 24px;
border-radius: 50%;
font-size: 0.875rem;
border-radius: var(--radius-full);
&--pass {
background: rgba(16, 185, 129, 0.1);
color: var(--status-success);
color: var(--color-status-success);
}
&--warn {
background: rgba(245, 158, 11, 0.1);
color: var(--status-warning);
color: var(--color-status-warning);
}
&--fail {
background: rgba(239, 68, 68, 0.1);
color: var(--status-error);
color: var(--color-status-error);
}
}
@@ -247,20 +246,20 @@ import { AgentHealthResult } from '../../models/agent.models';
.check-item__name {
margin: 0;
font-size: 0.9375rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.check-item__message {
margin: 0.25rem 0 0;
font-size: 0.8125rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.check-item__time {
display: block;
margin-top: 0.5rem;
font-size: 0.75rem;
color: var(--text-muted);
color: var(--color-text-muted);
}
.check-item__actions {
@@ -271,13 +270,13 @@ import { AgentHealthResult } from '../../models/agent.models';
.history-section {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border-default);
border-top: 1px solid var(--color-border-primary);
summary {
cursor: pointer;
font-size: 0.875rem;
color: var(--primary);
font-weight: 500;
color: var(--color-brand-primary);
font-weight: var(--font-weight-medium);
&:hover {
text-decoration: underline;
@@ -291,9 +290,9 @@ import { AgentHealthResult } from '../../models/agent.models';
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
border: 1px solid transparent;
@@ -304,22 +303,22 @@ import { AgentHealthResult } from '../../models/agent.models';
}
.btn--primary {
background: var(--primary);
background: var(--color-brand-primary);
color: var(--color-text-heading);
&:hover:not(:disabled) {
background: var(--primary-hover);
background: var(--color-brand-primary-hover);
}
}
.btn--text {
background: transparent;
color: var(--text-muted);
color: var(--color-text-muted);
padding: 0.25rem 0.5rem;
&:hover {
color: var(--text-primary);
background: var(--surface-hover);
color: var(--color-text-primary);
background: var(--color-nav-hover);
}
}
@@ -328,7 +327,7 @@ import { AgentHealthResult } from '../../models/agent.models';
height: 16px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
border-radius: var(--radius-full);
animation: spin 0.6s linear infinite;
}
@@ -340,11 +339,11 @@ import { AgentHealthResult } from '../../models/agent.models';
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.placeholder {
color: var(--text-muted);
color: var(--color-text-muted);
font-style: italic;
padding: 1rem 0;
}

View File

@@ -87,10 +87,10 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
<span class="status-badge status-badge--pending">Pending</span>
}
@case ('completed') {
<span class="status-badge status-badge--completed">&#10003; Completed</span>
<span class="status-badge status-badge--completed"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg> Completed</span>
}
@case ('failed') {
<span class="status-badge status-badge--failed">&#10007; Failed</span>
<span class="status-badge status-badge--failed"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> Failed</span>
}
@case ('cancelled') {
<span class="status-badge status-badge--cancelled">Cancelled</span>
@@ -133,7 +133,7 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
<!-- Error Message -->
@if (task.status === 'failed' && task.errorMessage) {
<div class="task-item__error">
<span class="error-icon">&#9888;</span>
<span class="error-icon"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span>
{{ task.errorMessage }}
</div>
}
@@ -158,7 +158,7 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
title="View details"
(click)="viewDetails.emit(task)"
>
&#8594;
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
</button>
</div>
</article>
@@ -204,7 +204,7 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
.tab-header__title {
margin: 0;
font-size: 1.125rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.tab-header__filters {
@@ -217,20 +217,20 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.75rem;
border: 1px solid var(--border-default);
border-radius: 6px;
background: var(--surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
background: var(--color-surface-primary);
font-size: 0.8125rem;
cursor: pointer;
transition: all 0.15s;
&:hover {
border-color: var(--primary);
border-color: var(--color-brand-primary);
}
&--active {
background: var(--primary);
border-color: var(--primary);
background: var(--color-brand-primary);
border-color: var(--color-brand-primary);
color: white;
}
}
@@ -243,9 +243,9 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
height: 18px;
padding: 0 0.25rem;
background: rgba(0, 0, 0, 0.1);
border-radius: 9px;
border-radius: var(--radius-lg);
font-size: 0.6875rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.filter-btn--active .filter-btn__count {
@@ -256,15 +256,15 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
.queue-viz {
margin-bottom: 1.5rem;
padding: 1rem;
background: var(--surface-secondary);
border-radius: 8px;
background: var(--color-surface-secondary);
border-radius: var(--radius-lg);
}
.queue-viz__title {
margin: 0 0 0.75rem;
font-size: 0.8125rem;
font-weight: 600;
color: var(--text-secondary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
}
.queue-items {
@@ -278,31 +278,31 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
flex-direction: column;
gap: 0.375rem;
padding: 0.5rem 0.75rem;
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 6px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
min-width: 120px;
&--running {
border-color: var(--primary);
border-color: var(--color-brand-primary);
}
}
.queue-item__type {
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.queue-item__progress {
height: 4px;
background: var(--surface-secondary);
border-radius: 2px;
background: var(--color-surface-secondary);
border-radius: var(--radius-sm);
overflow: hidden;
}
.queue-item__progress-fill {
height: 100%;
background: var(--primary);
background: var(--color-brand-primary);
transition: width 0.3s;
}
@@ -318,9 +318,9 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
align-items: flex-start;
gap: 1rem;
padding: 1rem;
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
transition: box-shadow 0.15s;
&:hover {
@@ -328,15 +328,15 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
}
&--running {
border-left: 3px solid var(--primary);
border-left: 3px solid var(--color-brand-primary);
}
&--completed {
border-left: 3px solid var(--status-success);
border-left: 3px solid var(--color-status-success);
}
&--failed {
border-left: 3px solid var(--status-error);
border-left: 3px solid var(--color-status-error);
}
&--cancelled {
@@ -353,34 +353,34 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.6875rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
&--running {
background: rgba(59, 130, 246, 0.1);
color: var(--primary);
color: var(--color-brand-primary);
}
&--pending {
background: var(--surface-secondary);
color: var(--text-secondary);
background: var(--color-surface-secondary);
color: var(--color-text-secondary);
}
&--completed {
background: rgba(16, 185, 129, 0.1);
color: var(--status-success);
color: var(--color-status-success);
}
&--failed {
background: rgba(239, 68, 68, 0.1);
color: var(--status-error);
color: var(--color-status-error);
}
&--cancelled {
background: var(--surface-secondary);
color: var(--text-muted);
background: var(--color-surface-secondary);
color: var(--color-text-muted);
}
}
@@ -388,8 +388,8 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
width: 10px;
height: 10px;
border: 2px solid rgba(59, 130, 246, 0.3);
border-top-color: var(--primary);
border-radius: 50%;
border-top-color: var(--color-brand-primary);
border-radius: var(--radius-full);
animation: spin 0.6s linear infinite;
}
@@ -412,24 +412,24 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
.task-item__type {
margin: 0;
font-size: 0.9375rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.task-item__id {
font-size: 0.6875rem;
color: var(--text-muted);
background: var(--surface-secondary);
color: var(--color-text-muted);
background: var(--color-surface-secondary);
padding: 0.125rem 0.375rem;
border-radius: 3px;
border-radius: var(--radius-sm);
}
.task-item__ref {
margin: 0.25rem 0;
font-size: 0.8125rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
a {
color: var(--primary);
color: var(--color-brand-primary);
text-decoration: none;
&:hover {
@@ -441,16 +441,16 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
.task-item__progress-bar {
position: relative;
height: 6px;
background: var(--surface-secondary);
border-radius: 3px;
background: var(--color-surface-secondary);
border-radius: var(--radius-sm);
margin: 0.75rem 0;
overflow: hidden;
}
.task-item__progress-fill {
height: 100%;
background: var(--primary);
border-radius: 3px;
background: var(--color-brand-primary);
border-radius: var(--radius-sm);
transition: width 0.3s;
}
@@ -459,7 +459,7 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
right: 0;
top: -1.25rem;
font-size: 0.6875rem;
color: var(--text-muted);
color: var(--color-text-muted);
}
.task-item__error {
@@ -469,9 +469,9 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
margin: 0.5rem 0;
padding: 0.5rem;
background: rgba(239, 68, 68, 0.1);
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.8125rem;
color: var(--status-error);
color: var(--color-status-error);
}
.error-icon {
@@ -483,7 +483,7 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
gap: 1rem;
margin-top: 0.5rem;
font-size: 0.75rem;
color: var(--text-muted);
color: var(--color-text-muted);
}
.task-item__actions {
@@ -496,9 +496,9 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
border: 1px solid transparent;
}
@@ -507,27 +507,27 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
padding: 0.375rem 0.5rem;
background: transparent;
border: none;
color: var(--text-muted);
color: var(--color-text-muted);
&:hover {
color: var(--text-primary);
background: var(--surface-hover);
color: var(--color-text-primary);
background: var(--color-nav-hover);
}
}
.btn--secondary {
background: var(--surface-primary);
border-color: var(--border-default);
color: var(--text-primary);
background: var(--color-surface-primary);
border-color: var(--color-border-primary);
color: var(--color-text-primary);
&:hover {
background: var(--surface-hover);
background: var(--color-nav-hover);
}
}
.btn--text {
background: transparent;
color: var(--primary);
color: var(--color-brand-primary);
&:hover {
text-decoration: underline;
@@ -538,7 +538,7 @@ type TaskFilter = 'all' | 'active' | 'completed' | 'failed';
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
/* Pagination */

View File

@@ -41,16 +41,16 @@ type GroupBy = 'none' | 'environment' | 'status';
<div class="heatmap-legend">
<span class="legend-label">Utilization:</span>
<div class="legend-scale">
<span class="legend-item" style="--item-color: var(--capacity-low)">
<span class="legend-item" style="--item-color: var(--color-severity-low)">
&lt;50%
</span>
<span class="legend-item" style="--item-color: var(--capacity-medium)">
<span class="legend-item" style="--item-color: var(--color-severity-medium)">
50-80%
</span>
<span class="legend-item" style="--item-color: var(--capacity-high)">
<span class="legend-item" style="--item-color: var(--color-severity-high)">
80-95%
</span>
<span class="legend-item" style="--item-color: var(--capacity-critical)">
<span class="legend-item" style="--item-color: var(--color-severity-critical)">
&gt;95%
</span>
</div>
@@ -145,9 +145,9 @@ type GroupBy = 'none' | 'environment' | 'status';
`,
styles: [`
.capacity-heatmap {
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
padding: 1.25rem;
position: relative;
}
@@ -162,7 +162,7 @@ type GroupBy = 'none' | 'environment' | 'status';
.heatmap-header__title {
margin: 0;
font-size: 1rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.group-label {
@@ -170,19 +170,19 @@ type GroupBy = 'none' | 'environment' | 'status';
align-items: center;
gap: 0.5rem;
font-size: 0.8125rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.group-select {
padding: 0.25rem 0.5rem;
border: 1px solid var(--border-default);
border-radius: 4px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-sm);
font-size: 0.8125rem;
background: var(--surface-primary);
background: var(--color-surface-primary);
&:focus {
outline: none;
border-color: var(--primary);
border-color: var(--color-brand-primary);
}
}
@@ -193,13 +193,13 @@ type GroupBy = 'none' | 'environment' | 'status';
gap: 1rem;
margin-bottom: 1rem;
padding: 0.5rem 0.75rem;
background: var(--surface-secondary);
border-radius: 6px;
background: var(--color-surface-secondary);
border-radius: var(--radius-md);
}
.legend-label {
font-size: 0.75rem;
color: var(--text-muted);
color: var(--color-text-muted);
}
.legend-scale {
@@ -212,14 +212,14 @@ type GroupBy = 'none' | 'environment' | 'status';
align-items: center;
gap: 0.25rem;
font-size: 0.6875rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
&::before {
content: '';
width: 12px;
height: 12px;
border-radius: 2px;
background: var(--item-color);
border-radius: var(--radius-sm);
background: var(--color-text-secondary);
}
}
@@ -235,39 +235,39 @@ type GroupBy = 'none' | 'environment' | 'status';
display: flex;
align-items: center;
justify-content: center;
background: var(--cell-color);
background: var(--color-text-secondary);
border: none;
border-radius: 4px;
border-radius: var(--radius-sm);
cursor: pointer;
transition: transform 0.15s, box-shadow 0.15s;
min-width: 48px;
&:hover {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
box-shadow: var(--shadow-lg);
z-index: 10;
}
&:focus-visible {
outline: 2px solid var(--focus-ring);
outline: 2px solid var(--color-brand-primary);
outline-offset: 2px;
}
&--offline {
opacity: 0.4;
background: var(--surface-secondary) !important;
background: var(--color-surface-secondary) !important;
}
}
.heatmap-cell__value {
font-size: 0.625rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: white;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.heatmap-cell--offline .heatmap-cell__value {
color: var(--text-muted);
color: var(--color-text-muted);
text-shadow: none;
}
@@ -283,13 +283,13 @@ type GroupBy = 'none' | 'environment' | 'status';
.heatmap-group__title {
margin: 0 0 0.5rem;
font-size: 0.8125rem;
font-weight: 600;
color: var(--text-secondary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
}
.heatmap-group__count {
font-weight: 400;
color: var(--text-muted);
font-weight: var(--font-weight-normal);
color: var(--color-text-muted);
}
/* Tooltip */
@@ -300,9 +300,9 @@ type GroupBy = 'none' | 'environment' | 'status';
transform: translateY(-50%);
width: 180px;
padding: 0.75rem;
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
z-index: 20;
pointer-events: none;
@@ -319,7 +319,7 @@ type GroupBy = 'none' | 'environment' | 'status';
.tooltip-status {
width: 8px;
height: 8px;
border-radius: 50%;
border-radius: var(--radius-full);
}
.tooltip-details {
@@ -330,12 +330,12 @@ type GroupBy = 'none' | 'environment' | 'status';
font-size: 0.75rem;
dt {
color: var(--text-muted);
color: var(--color-text-muted);
}
dd {
margin: 0;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
}
@@ -343,9 +343,9 @@ type GroupBy = 'none' | 'environment' | 'status';
display: block;
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid var(--border-default);
border-top: 1px solid var(--color-border-primary);
font-size: 0.625rem;
color: var(--text-muted);
color: var(--color-text-muted);
text-align: center;
}
@@ -356,7 +356,7 @@ type GroupBy = 'none' | 'environment' | 'status';
gap: 2rem;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border-default);
border-top: 1px solid var(--color-border-primary);
}
.summary-stat {
@@ -366,13 +366,13 @@ type GroupBy = 'none' | 'environment' | 'status';
.summary-stat__value {
display: block;
font-size: 1.25rem;
font-weight: 700;
color: var(--text-primary);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
}
.summary-stat__label {
font-size: 0.6875rem;
color: var(--text-muted);
color: var(--color-text-muted);
text-transform: uppercase;
}
@@ -462,13 +462,13 @@ export class CapacityHeatmapComponent {
getStatusColor(status: string): string {
switch (status) {
case 'online':
return 'var(--status-success)';
return 'var(--color-status-success)';
case 'degraded':
return 'var(--status-warning)';
return 'var(--color-status-warning)';
case 'offline':
return 'var(--status-error)';
return 'var(--color-status-error)';
default:
return 'var(--status-unknown)';
return 'var(--color-text-muted)';
}
}

View File

@@ -40,7 +40,7 @@ interface ColumnConfig {
(click)="toggleColumnMenu()"
title="Select columns"
>
<span aria-hidden="true">&#9881;</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
</button>
@if (showColumnMenu()) {
<div class="column-menu">
@@ -72,7 +72,7 @@ interface ColumnConfig {
<!-- Alerts -->
@if (versionMismatchCount() > 0) {
<div class="alert alert--warning">
<span class="alert-icon">&#9888;</span>
<span class="alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span>
{{ versionMismatchCount() }} agents have version mismatches.
Latest version: {{ latestVersion() }}
</div>
@@ -93,7 +93,11 @@ interface ColumnConfig {
{{ col.label }}
@if (sortColumn() === col.key) {
<span class="sort-indicator">
{{ sortDirection() === 'asc' ? '&#9650;' : '&#9660;' }}
@if (sortDirection() === 'asc') {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="18 15 12 9 6 15"/></svg>
} @else {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
}
</span>
}
</th>
@@ -138,7 +142,7 @@ interface ColumnConfig {
>
v{{ agent.version }}
@if (agent.version !== latestVersion()) {
<span class="mismatch-icon" title="Not latest version">&#9888;</span>
<span class="mismatch-icon" title="Not latest version"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span>
}
</span>
}
@@ -187,7 +191,7 @@ interface ColumnConfig {
title="View details"
(click)="viewAgent.emit(agent)"
>
&#8594;
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
</button>
</td>
</tr>
@@ -209,9 +213,9 @@ interface ColumnConfig {
`,
styles: [`
.fleet-comparison {
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
overflow: hidden;
}
@@ -221,8 +225,8 @@ interface ColumnConfig {
justify-content: space-between;
align-items: center;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--border-default);
background: var(--surface-secondary);
border-bottom: 1px solid var(--color-border-primary);
background: var(--color-surface-secondary);
}
.toolbar-left {
@@ -234,12 +238,12 @@ interface ColumnConfig {
.toolbar-title {
margin: 0;
font-size: 1rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.toolbar-count {
font-size: 0.8125rem;
color: var(--text-muted);
color: var(--color-text-muted);
}
.toolbar-right {
@@ -258,18 +262,18 @@ interface ColumnConfig {
right: 0;
margin-top: 0.25rem;
padding: 0.75rem;
background: var(--surface-primary);
border: 1px solid var(--border-default);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
z-index: 100;
min-width: 160px;
h4 {
margin: 0 0 0.5rem;
font-size: 0.75rem;
font-weight: 600;
color: var(--text-muted);
font-weight: var(--font-weight-semibold);
color: var(--color-text-muted);
text-transform: uppercase;
}
}
@@ -294,17 +298,18 @@ interface ColumnConfig {
gap: 0.5rem;
margin: 0.75rem 1rem;
padding: 0.75rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.8125rem;
&--warning {
background: var(--warning-bg);
color: var(--warning-text);
background: var(--color-status-warning-bg);
color: var(--color-status-warning-text);
}
}
.alert-icon {
font-size: 1rem;
display: inline-flex;
align-items: center;
}
/* Table */
@@ -322,13 +327,13 @@ interface ColumnConfig {
.comparison-table td {
padding: 0.75rem 1rem;
text-align: left;
border-bottom: 1px solid var(--border-default);
border-bottom: 1px solid var(--color-border-primary);
}
.comparison-table th {
background: var(--surface-secondary);
font-weight: 600;
color: var(--text-secondary);
background: var(--color-surface-secondary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
white-space: nowrap;
&.sortable {
@@ -336,25 +341,27 @@ interface ColumnConfig {
user-select: none;
&:hover {
color: var(--text-primary);
color: var(--color-text-primary);
}
}
&.sorted {
color: var(--primary);
color: var(--color-brand-primary);
}
}
.sort-indicator {
margin-left: 0.25rem;
font-size: 0.625rem;
display: inline-flex;
align-items: center;
vertical-align: middle;
}
.comparison-table tbody tr {
transition: background 0.15s;
&:hover {
background: var(--surface-hover);
background: var(--color-nav-hover);
}
&.row--offline {
@@ -381,43 +388,43 @@ interface ColumnConfig {
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
border-radius: var(--radius-full);
flex-shrink: 0;
}
.agent-name {
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.agent-id {
font-size: 0.6875rem;
color: var(--text-muted);
background: var(--surface-secondary);
color: var(--color-text-muted);
background: var(--color-surface-secondary);
padding: 0.125rem 0.25rem;
border-radius: 3px;
border-radius: var(--radius-sm);
}
.tag {
display: inline-block;
padding: 0.125rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.6875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
&--env {
background: var(--tag-env-bg);
color: var(--tag-env-text);
background: var(--color-surface-tertiary);
color: var(--color-text-secondary);
}
}
.status-badge {
display: inline-block;
padding: 0.125rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.6875rem;
font-weight: 600;
background: color-mix(in srgb, var(--badge-color) 15%, transparent);
color: var(--badge-color);
font-weight: var(--font-weight-semibold);
background: color-mix(in srgb, var(--color-text-secondary) 15%, transparent);
color: var(--color-text-secondary);
}
.version {
@@ -426,12 +433,14 @@ interface ColumnConfig {
gap: 0.25rem;
&--mismatch {
color: var(--status-warning);
color: var(--color-status-warning);
}
}
.mismatch-icon {
font-size: 0.75rem;
display: inline-flex;
align-items: center;
vertical-align: middle;
}
.capacity-cell {
@@ -443,14 +452,14 @@ interface ColumnConfig {
.capacity-bar {
width: 60px;
height: 6px;
background: var(--surface-secondary);
border-radius: 3px;
background: var(--color-surface-secondary);
border-radius: var(--radius-sm);
overflow: hidden;
}
.capacity-bar__fill {
height: 100%;
border-radius: 3px;
border-radius: var(--radius-sm);
}
.capacity-value {
@@ -463,22 +472,22 @@ interface ColumnConfig {
}
.heartbeat {
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.cert-expiry {
&--warning {
color: var(--status-warning);
font-weight: 600;
color: var(--color-status-warning);
font-weight: var(--font-weight-semibold);
}
&--critical {
color: var(--status-error);
font-weight: 600;
color: var(--color-status-error);
font-weight: var(--font-weight-semibold);
}
&--na {
color: var(--text-muted);
color: var(--color-text-muted);
}
}
@@ -488,20 +497,20 @@ interface ColumnConfig {
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.8125rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
border: 1px solid transparent;
}
.btn--secondary {
background: var(--surface-primary);
border-color: var(--border-default);
color: var(--text-primary);
background: var(--color-surface-primary);
border-color: var(--color-border-primary);
color: var(--color-text-primary);
&:hover {
background: var(--surface-hover);
background: var(--color-nav-hover);
}
}
@@ -509,24 +518,24 @@ interface ColumnConfig {
padding: 0.375rem 0.5rem;
background: transparent;
border: none;
color: var(--text-muted);
color: var(--color-text-muted);
&:hover {
color: var(--text-primary);
background: var(--surface-hover);
color: var(--color-text-primary);
background: var(--color-nav-hover);
}
}
/* Footer */
.comparison-footer {
padding: 0.75rem 1rem;
background: var(--surface-secondary);
border-top: 1px solid var(--border-default);
background: var(--color-surface-secondary);
border-top: 1px solid var(--color-border-primary);
}
.footer-info {
font-size: 0.75rem;
color: var(--text-muted);
color: var(--color-text-muted);
}
/* Responsive */

View File

@@ -152,14 +152,14 @@ export interface AgentActionResult {
export function getStatusColor(status: AgentStatus): string {
switch (status) {
case 'online':
return 'var(--status-success)';
return 'var(--color-status-success)';
case 'degraded':
return 'var(--status-warning)';
return 'var(--color-status-warning)';
case 'offline':
return 'var(--status-error)';
return 'var(--color-status-error)';
case 'unknown':
default:
return 'var(--status-unknown)';
return 'var(--color-text-muted)';
}
}
@@ -184,10 +184,10 @@ export function getStatusLabel(status: AgentStatus): string {
* Get capacity color based on utilization percentage.
*/
export function getCapacityColor(percent: number): string {
if (percent < 50) return 'var(--capacity-low)';
if (percent < 80) return 'var(--capacity-medium)';
if (percent < 95) return 'var(--capacity-high)';
return 'var(--capacity-critical)';
if (percent < 50) return 'var(--color-severity-low)';
if (percent < 80) return 'var(--color-severity-medium)';
if (percent < 95) return 'var(--color-severity-high)';
return 'var(--color-severity-critical)';
}
/**

View File

@@ -304,9 +304,9 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
display: flex;
flex-direction: column;
height: 100%;
background: var(--bg-primary);
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
background: var(--color-surface-primary);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
}
.loading-state, .error-state, .empty-state {
@@ -315,15 +315,15 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
align-items: center;
justify-content: center;
padding: 2rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid var(--border-color);
border-top-color: var(--primary-color);
border-radius: 50%;
border: 3px solid var(--color-border-primary);
border-top-color: var(--color-brand-primary);
border-radius: var(--radius-full);
animation: spin 0.8s linear infinite;
}
@@ -334,15 +334,15 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.error-icon {
width: 48px;
height: 48px;
color: var(--error-color);
color: var(--color-status-error);
margin-bottom: 1rem;
}
.retry-btn {
padding: 0.5rem 1rem;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--bg-secondary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-sm);
background: var(--color-surface-secondary);
cursor: pointer;
font-size: 0.875rem;
}
@@ -352,7 +352,7 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-color);
border-bottom: 1px solid var(--color-border-primary);
}
.header-left {
@@ -363,16 +363,16 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.run-title {
font-size: 1.125rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
margin: 0;
}
.run-id {
font-size: 0.75rem;
color: var(--text-secondary);
background: var(--bg-secondary);
color: var(--color-text-secondary);
background: var(--color-surface-secondary);
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-family: monospace;
}
@@ -384,30 +384,30 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.status-badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
}
.status-created { background: #e0e7ff; color: #3730a3; }
.status-active { background: #dbeafe; color: #1e40af; }
.status-pending_approval { background: #fef3c7; color: #92400e; }
.status-approved { background: #dcfce7; color: #166534; }
.status-rejected { background: #fee2e2; color: #991b1b; }
.status-complete { background: #d1fae5; color: #065f46; }
.status-cancelled { background: #f3f4f6; color: #4b5563; }
.status-created { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.status-active { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.status-pending_approval { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.status-approved { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-rejected { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.status-complete { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-cancelled { background: var(--color-surface-secondary); color: var(--color-text-secondary); }
.attested-badge {
display: flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
background: #dcfce7;
color: #166534;
border-radius: 4px;
background: var(--color-status-success-bg);
color: var(--color-status-success-text);
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.attested-badge svg {
@@ -417,7 +417,7 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.run-section {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-color);
border-bottom: 1px solid var(--color-border-primary);
}
.section-title {
@@ -425,8 +425,8 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
font-weight: 600;
color: var(--text-primary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
margin: 0 0 0.75rem 0;
text-transform: uppercase;
letter-spacing: 0.05em;
@@ -435,10 +435,10 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.count-badge {
font-size: 0.75rem;
padding: 0.125rem 0.375rem;
border-radius: 9999px;
background: var(--bg-secondary);
color: var(--text-secondary);
font-weight: 500;
border-radius: var(--radius-full);
background: var(--color-surface-secondary);
color: var(--color-text-secondary);
font-weight: var(--font-weight-medium);
}
.info-grid {
@@ -450,7 +450,7 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.info-item dt {
font-size: 0.75rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
margin-bottom: 0.25rem;
}
@@ -494,16 +494,16 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.marker-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--primary-color);
border: 2px solid var(--bg-primary);
box-shadow: 0 0 0 2px var(--primary-color);
border-radius: var(--radius-full);
background: var(--color-brand-primary);
border: 2px solid var(--color-surface-primary);
box-shadow: 0 0 0 2px var(--color-brand-primary);
}
.marker-line {
flex: 1;
width: 2px;
background: var(--border-color);
background: var(--color-border-primary);
margin-top: 4px;
}
@@ -521,21 +521,21 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.event-type {
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
color: var(--primary-color);
color: var(--color-brand-primary);
}
.event-time {
font-size: 0.75rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.event-body {
padding: 0.75rem;
background: var(--bg-secondary);
border-radius: 6px;
border: 1px solid var(--border-color);
background: var(--color-surface-secondary);
border-radius: var(--radius-md);
border: 1px solid var(--color-border-primary);
}
.turn-content {
@@ -548,25 +548,25 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
}
.user-turn {
border-left: 3px solid var(--info-color);
border-left: 3px solid var(--color-status-info);
}
.assistant-turn {
border-left: 3px solid var(--primary-color);
border-left: 3px solid var(--color-brand-primary);
}
.grounding-score {
display: inline-block;
font-size: 0.75rem;
padding: 0.125rem 0.375rem;
background: var(--bg-tertiary);
border-radius: 4px;
background: var(--color-surface-tertiary);
border-radius: var(--radius-sm);
margin-top: 0.5rem;
}
.grounding-score.acceptable {
background: #dcfce7;
color: #166534;
background: var(--color-status-success-bg);
color: var(--color-status-success-text);
}
.grounding-content, .evidence-pack-content, .action-content,
@@ -578,7 +578,7 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
background: none;
border: none;
padding: 0;
color: var(--primary-color);
color: var(--color-brand-primary);
cursor: pointer;
font-family: monospace;
font-size: 0.8125rem;
@@ -591,17 +591,17 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.pack-stats {
display: block;
font-size: 0.75rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
margin-top: 0.25rem;
}
.action-type {
display: inline-block;
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
padding: 0.125rem 0.375rem;
background: var(--bg-tertiary);
border-radius: 4px;
background: var(--color-surface-tertiary);
border-radius: var(--radius-sm);
text-transform: uppercase;
}
@@ -612,7 +612,7 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.action-target {
display: block;
font-size: 0.75rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
font-family: monospace;
}
@@ -620,9 +620,9 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
display: inline-block;
font-size: 0.75rem;
padding: 0.125rem 0.375rem;
background: #fef3c7;
color: #92400e;
border-radius: 4px;
background: var(--color-status-warning-bg);
color: var(--color-status-warning-text);
border-radius: var(--radius-sm);
margin-top: 0.5rem;
}
@@ -634,26 +634,26 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
}
.approval-content.approved .approval-decision {
background: #dcfce7;
color: #166534;
background: var(--color-status-success-bg);
color: var(--color-status-success-text);
}
.approval-content.denied .approval-decision {
background: #fee2e2;
color: #991b1b;
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
}
.approval-decision {
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
padding: 0.125rem 0.375rem;
border-radius: 4px;
border-radius: var(--radius-sm);
text-transform: uppercase;
}
.approval-by {
font-size: 0.75rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.approval-reason {
@@ -672,8 +672,8 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.attestation-type, .attestation-id {
font-size: 0.75rem;
padding: 0.125rem 0.375rem;
background: var(--bg-tertiary);
border-radius: 4px;
background: var(--color-surface-tertiary);
border-radius: var(--radius-sm);
}
.attestation-id {
@@ -683,9 +683,9 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.signed-indicator {
font-size: 0.75rem;
padding: 0.125rem 0.375rem;
background: #dcfce7;
color: #166534;
border-radius: 4px;
background: var(--color-status-success-bg);
color: var(--color-status-success-text);
border-radius: var(--radius-sm);
}
/* Artifacts */
@@ -697,9 +697,9 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.artifact-card {
padding: 0.75rem;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--bg-secondary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
background: var(--color-surface-secondary);
}
.artifact-header {
@@ -711,38 +711,38 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.artifact-type {
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
color: var(--primary-color);
color: var(--color-brand-primary);
}
.artifact-date {
font-size: 0.75rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.artifact-name {
display: block;
font-weight: 500;
font-weight: var(--font-weight-medium);
margin-bottom: 0.25rem;
}
.artifact-uri {
display: block;
font-size: 0.75rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
word-break: break-all;
}
.artifact-footer {
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid var(--border-color);
border-top: 1px solid var(--color-border-primary);
}
.artifact-digest {
font-size: 0.6875rem;
color: var(--text-tertiary);
color: var(--color-text-muted);
word-break: break-all;
}
@@ -755,27 +755,27 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.approve-btn, .reject-btn {
padding: 0.5rem 1.5rem;
border: none;
border-radius: 4px;
font-weight: 500;
border-radius: var(--radius-sm);
font-weight: var(--font-weight-medium);
cursor: pointer;
}
.approve-btn {
background: #16a34a;
color: #fff;
background: var(--color-status-success);
color: var(--color-surface-primary);
}
.approve-btn:hover:not(:disabled) {
background: #15803d;
background: var(--color-status-success-text);
}
.reject-btn {
background: #dc2626;
color: #fff;
background: var(--color-status-error);
color: var(--color-surface-primary);
}
.reject-btn:hover:not(:disabled) {
background: #b91c1c;
background: var(--color-status-error-text);
}
.approve-btn:disabled, .reject-btn:disabled {
@@ -788,10 +788,10 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
flex-wrap: wrap;
gap: 1rem;
padding: 0.75rem 1.5rem;
background: var(--bg-secondary);
border-top: 1px solid var(--border-color);
background: var(--color-surface-secondary);
border-top: 1px solid var(--color-border-primary);
font-size: 0.75rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
`]
})

View File

@@ -154,7 +154,7 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
display: flex;
flex-direction: column;
height: 100%;
background: var(--bg-primary);
background: var(--color-surface-primary);
}
.list-header {
@@ -162,12 +162,12 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-color);
border-bottom: 1px solid var(--color-border-primary);
}
.list-title {
font-size: 1.125rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
margin: 0;
}
@@ -179,14 +179,14 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.filter-select, .filter-input {
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-color);
border-radius: 4px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-sm);
font-size: 0.875rem;
}
.filter-select:focus, .filter-input:focus {
outline: none;
border-color: var(--primary-color);
border-color: var(--color-brand-primary);
}
.list-content {
@@ -200,15 +200,15 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
align-items: center;
justify-content: center;
padding: 2rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid var(--border-color);
border-top-color: var(--primary-color);
border-radius: 50%;
border: 3px solid var(--color-border-primary);
border-top-color: var(--color-brand-primary);
border-radius: var(--radius-full);
animation: spin 0.8s linear infinite;
}
@@ -219,15 +219,15 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.empty-icon {
width: 48px;
height: 48px;
color: var(--text-tertiary);
color: var(--color-text-muted);
margin-bottom: 1rem;
}
.retry-btn {
padding: 0.5rem 1rem;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--bg-secondary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-sm);
background: var(--color-surface-secondary);
cursor: pointer;
}
@@ -244,16 +244,16 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
padding: 0.75rem 1rem;
text-align: left;
font-size: 0.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
color: var(--text-secondary);
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
color: var(--color-text-secondary);
background: var(--color-surface-secondary);
border-bottom: 1px solid var(--color-border-primary);
}
.runs-table td {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border-color);
border-bottom: 1px solid var(--color-border-primary);
font-size: 0.875rem;
}
@@ -263,7 +263,7 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
}
.run-row:hover {
background: var(--bg-hover);
background: var(--color-nav-hover);
}
.run-row.selected {
@@ -272,25 +272,25 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.run-id-cell code {
font-size: 0.75rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.status-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
}
.status-created { background: #e0e7ff; color: #3730a3; }
.status-active { background: #dbeafe; color: #1e40af; }
.status-pending_approval { background: #fef3c7; color: #92400e; }
.status-approved { background: #dcfce7; color: #166534; }
.status-rejected { background: #fee2e2; color: #991b1b; }
.status-complete { background: #d1fae5; color: #065f46; }
.status-cancelled { background: #f3f4f6; color: #4b5563; }
.status-created { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.status-active { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.status-pending_approval { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.status-approved { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-rejected { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.status-complete { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-cancelled { background: var(--color-surface-secondary); color: var(--color-text-secondary); }
.user-cell {
max-width: 150px;
@@ -301,7 +301,7 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.count-cell {
text-align: center;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.attested-cell {
@@ -311,15 +311,15 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
.check-icon {
width: 18px;
height: 18px;
color: #16a34a;
color: var(--color-status-success);
}
.no-attestation {
color: var(--text-tertiary);
color: var(--color-text-muted);
}
.date-cell {
color: var(--text-secondary);
color: var(--color-text-secondary);
white-space: nowrap;
}
@@ -329,14 +329,14 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
align-items: center;
gap: 1rem;
padding: 1rem;
border-top: 1px solid var(--border-color);
border-top: 1px solid var(--color-border-primary);
}
.page-btn {
padding: 0.5rem 1rem;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--bg-secondary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-sm);
background: var(--color-surface-secondary);
cursor: pointer;
font-size: 0.875rem;
}
@@ -347,12 +347,12 @@ import { AI_RUNS_API } from '../../core/api/ai-runs.client';
}
.page-btn:not(:disabled):hover {
background: var(--bg-hover);
background: var(--color-nav-hover);
}
.page-info {
font-size: 0.875rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
`]
})

View File

@@ -546,16 +546,16 @@ const SEVERITY_RANK: Record<string, number> = {
.page-title {
margin: 0 0 0.25rem;
font-size: 1.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.page-subtitle {
margin: 0;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.page-meta {
margin: 0.5rem 0 0;
font-size: 0.85rem;
color: var(--text-color-muted);
color: var(--color-text-muted);
}
.page-actions {
display: flex;
@@ -569,9 +569,9 @@ const SEVERITY_RANK: Record<string, number> = {
align-items: flex-end;
flex-wrap: wrap;
padding: 1rem;
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: 10px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-xl);
}
.filter-group {
display: flex;
@@ -583,28 +583,28 @@ const SEVERITY_RANK: Record<string, number> = {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.filter-group select {
padding: 0.5rem 0.75rem;
border-radius: 6px;
border: 1px solid var(--surface-border);
background: var(--surface-ground);
border-radius: var(--radius-md);
border: 1px solid var(--color-border-primary);
background: var(--color-surface-secondary);
font-size: 0.875rem;
}
.btn {
padding: 0.45rem 0.9rem;
border-radius: 6px;
border: 1px solid var(--surface-border);
background: var(--surface-ground);
color: var(--text-color);
border-radius: var(--radius-md);
border: 1px solid var(--color-border-primary);
background: var(--color-surface-secondary);
color: var(--color-text-primary);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
}
.btn--secondary {
background: var(--surface-ground);
background: var(--color-surface-secondary);
}
.btn--ghost {
background: transparent;
@@ -620,10 +620,10 @@ const SEVERITY_RANK: Record<string, number> = {
align-items: center;
gap: 1rem;
padding: 0.75rem 1rem;
border-radius: 8px;
background: var(--red-50);
border: 1px solid var(--red-200);
color: var(--red-700);
border-radius: var(--radius-lg);
background: var(--color-severity-critical-bg);
border: 1px solid var(--color-severity-critical-border);
color: var(--color-status-error-text);
}
.panel-grid {
@@ -638,9 +638,9 @@ const SEVERITY_RANK: Record<string, number> = {
gap: 1rem;
}
.panel {
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: 12px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-xl);
padding: 1rem;
display: flex;
flex-direction: column;
@@ -655,16 +655,16 @@ const SEVERITY_RANK: Record<string, number> = {
.panel-title {
margin: 0;
font-size: 1rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.panel-subtitle {
margin: 0.2rem 0 0;
font-size: 0.85rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.panel-meta {
font-size: 0.75rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
white-space: nowrap;
}
.panel-body {
@@ -699,30 +699,30 @@ const SEVERITY_RANK: Record<string, number> = {
gap: 0.15rem;
}
.metric-row__name {
font-weight: 600;
font-weight: var(--font-weight-semibold);
font-size: 0.9rem;
}
.metric-row__meta {
font-size: 0.75rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.metric-row__bar {
height: 6px;
background: var(--surface-ground);
border-radius: 999px;
background: var(--color-surface-secondary);
border-radius: var(--radius-full);
overflow: hidden;
}
.metric-row__fill {
height: 100%;
background: var(--primary-color);
background: var(--color-brand-primary);
}
.metric-row__fill--accent {
background: var(--emerald-500);
background: var(--color-status-success);
}
.metric-row__value {
font-size: 0.85rem;
font-weight: 600;
color: var(--text-color);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.metric-row__chips {
display: flex;
@@ -734,30 +734,30 @@ const SEVERITY_RANK: Record<string, number> = {
display: inline-flex;
align-items: center;
padding: 0.15rem 0.45rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.65rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
}
.severity-badge--critical { background: var(--red-100); color: var(--red-700); }
.severity-badge--high { background: var(--orange-100); color: var(--orange-700); }
.severity-badge--medium { background: var(--yellow-100); color: var(--yellow-700); }
.severity-badge--low { background: var(--blue-100); color: var(--blue-700); }
.severity-badge--unknown { background: var(--gray-100); color: var(--gray-600); }
.severity-badge--critical { background: var(--color-severity-critical-bg); color: var(--color-status-error-text); }
.severity-badge--high { background: var(--color-severity-high-bg); color: var(--color-severity-high); }
.severity-badge--medium { background: var(--color-severity-medium-bg); color: var(--color-status-warning-text); }
.severity-badge--low { background: var(--color-severity-info-bg); color: var(--color-status-info-text); }
.severity-badge--unknown { background: var(--color-severity-none-bg); color: var(--color-text-secondary); }
.flag {
display: inline-flex;
align-items: center;
padding: 0.15rem 0.4rem;
border-radius: 999px;
background: var(--surface-ground);
color: var(--text-color-secondary);
border-radius: var(--radius-full);
background: var(--color-surface-secondary);
color: var(--color-text-secondary);
font-size: 0.65rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
}
.flag--warning { background: var(--yellow-100); color: var(--yellow-700); }
.flag--success { background: var(--green-100); color: var(--green-700); }
.flag--warning { background: var(--color-severity-medium-bg); color: var(--color-status-warning-text); }
.flag--success { background: var(--color-severity-low-bg); color: var(--color-status-success-text); }
.coverage-summary {
display: grid;
@@ -765,8 +765,8 @@ const SEVERITY_RANK: Record<string, number> = {
gap: 0.75rem;
}
.coverage-stat {
background: var(--surface-ground);
border-radius: 8px;
background: var(--color-surface-secondary);
border-radius: var(--radius-lg);
padding: 0.75rem;
display: flex;
flex-direction: column;
@@ -774,11 +774,11 @@ const SEVERITY_RANK: Record<string, number> = {
}
.coverage-stat__value {
font-size: 1.2rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.coverage-stat__label {
font-size: 0.75rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.coverage-list {
display: flex;
@@ -792,26 +792,26 @@ const SEVERITY_RANK: Record<string, number> = {
align-items: center;
}
.coverage-row__title {
font-weight: 600;
font-weight: var(--font-weight-semibold);
font-size: 0.9rem;
}
.coverage-row__meta {
font-size: 0.75rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.coverage-row__bar {
height: 6px;
background: var(--surface-ground);
border-radius: 999px;
background: var(--color-surface-secondary);
border-radius: var(--radius-full);
overflow: hidden;
}
.coverage-row__fill {
height: 100%;
background: var(--emerald-500);
background: var(--color-status-success);
}
.coverage-row__value {
font-size: 0.8rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.trend-chart {
@@ -825,19 +825,19 @@ const SEVERITY_RANK: Record<string, number> = {
}
.trend-bar {
width: 100%;
background: var(--primary-color);
border-radius: 4px 4px 0 0;
background: var(--color-brand-primary);
border-radius: var(--radius-sm) 4px 0 0;
min-height: 6px;
}
.trend-bar--accent {
background: var(--emerald-500);
background: var(--color-status-success);
}
.trend-table {
display: flex;
flex-direction: column;
gap: 0.4rem;
font-size: 0.75rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.trend-row {
display: flex;
@@ -847,8 +847,8 @@ const SEVERITY_RANK: Record<string, number> = {
.table-container {
overflow-x: auto;
border-radius: 8px;
border: 1px solid var(--surface-border);
border-radius: var(--radius-lg);
border: 1px solid var(--color-border-primary);
}
.data-table {
width: 100%;
@@ -859,45 +859,45 @@ const SEVERITY_RANK: Record<string, number> = {
.data-table td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid var(--surface-border);
border-bottom: 1px solid var(--color-border-primary);
font-size: 0.85rem;
}
.data-table th {
background: var(--surface-ground);
background: var(--color-surface-secondary);
text-transform: uppercase;
font-size: 0.7rem;
letter-spacing: 0.04em;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.table-primary {
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.table-secondary {
font-size: 0.75rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.empty-state {
font-size: 0.85rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
margin: 0;
}
.empty-callout {
border-radius: 12px;
border: 1px dashed var(--surface-border);
border-radius: var(--radius-xl);
border: 1px dashed var(--color-border-primary);
padding: 1.5rem;
text-align: center;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.empty-callout h3 {
margin: 0 0 0.5rem;
color: var(--text-color);
color: var(--color-text-primary);
}
.empty-callout--error {
border-style: solid;
border-color: var(--red-200);
background: var(--red-50);
color: var(--red-700);
border-color: var(--color-severity-critical-border);
background: var(--color-severity-critical-bg);
color: var(--color-status-error-text);
}
@media (max-width: 900px) {

View File

@@ -27,7 +27,7 @@ import {
<p class="subtitle">Aggregation-Only Contract ingestion monitoring and compliance metrics</p>
<div class="header-actions">
<button class="btn btn-secondary" (click)="refresh()">
<span class="icon">&#8635;</span> Refresh
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg> Refresh
</button>
<a routerLink="report" class="btn btn-primary">Export Report</a>
</div>
@@ -42,7 +42,7 @@ import {
@if (error()) {
<div class="error-banner">
<span class="icon">&#9888;</span>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
{{ error() }}
<button (click)="refresh()">Retry</button>
</div>
@@ -56,7 +56,13 @@ import {
<div class="kpi-label">Guard Violations</div>
<div class="kpi-detail">{{ (metrics()?.guardViolations?.percentage || 0).toFixed(2) }}% of ingested</div>
<div class="kpi-trend" [class.down]="metrics()?.guardViolations?.trend === 'down'">
{{ getTrendIcon(metrics()?.guardViolations?.trend) }}
@if (metrics()?.guardViolations?.trend === 'up') {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="18 15 12 9 6 15"/></svg>
} @else if (metrics()?.guardViolations?.trend === 'down') {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
} @else {
&#8212;
}
</div>
</div>
@@ -65,7 +71,13 @@ import {
<div class="kpi-label">Provenance Completeness</div>
<div class="kpi-detail">{{ metrics()?.provenanceCompleteness?.recordsWithValidHash?.toLocaleString() }} records</div>
<div class="kpi-trend">
{{ getTrendIcon(metrics()?.provenanceCompleteness?.trend) }}
@if (metrics()?.provenanceCompleteness?.trend === 'up') {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="18 15 12 9 6 15"/></svg>
} @else if (metrics()?.provenanceCompleteness?.trend === 'down') {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
} @else {
&#8212;
}
</div>
</div>
@@ -74,7 +86,13 @@ import {
<div class="kpi-label">Deduplication Rate</div>
<div class="kpi-detail">{{ metrics()?.deduplicationRate?.duplicatesDetected?.toLocaleString() }} duplicates</div>
<div class="kpi-trend" [class.up]="metrics()?.deduplicationRate?.trend === 'up'">
{{ getTrendIcon(metrics()?.deduplicationRate?.trend) }}
@if (metrics()?.deduplicationRate?.trend === 'up') {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="18 15 12 9 6 15"/></svg>
} @else if (metrics()?.deduplicationRate?.trend === 'down') {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
} @else {
&#8212;
}
</div>
</div>
@@ -82,7 +100,13 @@ import {
<div class="kpi-value">{{ formatLatency(metrics()?.ingestionLatency?.p95Ms) }}</div>
<div class="kpi-label">Ingestion Latency (P95)</div>
<div class="kpi-detail">SLA: {{ formatLatency(metrics()?.ingestionLatency?.slaTargetP95Ms) }}</div>
<div class="kpi-status">{{ metrics()?.ingestionLatency?.meetsSla ? '&#10004; Meets SLA' : '&#10006; SLA Breach' }}</div>
<div class="kpi-status">
@if (metrics()?.ingestionLatency?.meetsSla) {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg> Meets SLA
} @else {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> SLA Breach
}
</div>
</div>
</section>
@@ -92,7 +116,7 @@ import {
<section class="panel violations-panel">
<header class="panel-header">
<h2>Guard Violations (Last 24h)</h2>
<a routerLink="violations" class="view-all">View All &#8594;</a>
<a routerLink="violations" class="view-all">View All <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg></a>
</header>
<div class="violations-table">
<table>
@@ -122,10 +146,10 @@ import {
</td>
<td class="actions">
@if (violation.payloadSample) {
<button class="btn-icon" title="View Payload" (click)="viewPayload(violation)">&#128269;</button>
<button class="btn-icon" title="View Payload" (click)="viewPayload(violation)"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></button>
}
@if (violation.canRetry) {
<button class="btn-icon" title="Retry" (click)="retryIngestion(violation)">&#8635;</button>
<button class="btn-icon" title="Retry" (click)="retryIngestion(violation)"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg></button>
}
</td>
</tr>
@@ -139,7 +163,7 @@ import {
<section class="panel ingestion-panel">
<header class="panel-header">
<h2>Ingestion Flow</h2>
<a routerLink="ingestion" class="view-all">Details &#8594;</a>
<a routerLink="ingestion" class="view-all">Details <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg></a>
</header>
<div class="ingestion-flow">
<div class="flow-module">
@@ -150,7 +174,13 @@ import {
<span class="source-rate">{{ source.throughputPerMinute }}/min</span>
<span class="source-latency">P95: {{ formatLatency(source.latencyP95Ms) }}</span>
<span class="source-status" [class]="source.status">
{{ source.status === 'healthy' ? '&#9679;' : source.status === 'degraded' ? '&#9675;' : '&#10006;' }}
@if (source.status === 'healthy') {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="6" fill="currentColor" stroke="none"/></svg>
} @else if (source.status === 'degraded') {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="6"/></svg>
} @else {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
}
</span>
</div>
}
@@ -163,7 +193,13 @@ import {
<span class="source-rate">{{ source.throughputPerMinute }}/min</span>
<span class="source-latency">P95: {{ formatLatency(source.latencyP95Ms) }}</span>
<span class="source-status" [class]="source.status">
{{ source.status === 'healthy' ? '&#9679;' : source.status === 'degraded' ? '&#9675;' : '&#10006;' }}
@if (source.status === 'healthy') {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="6" fill="currentColor" stroke="none"/></svg>
} @else if (source.status === 'degraded') {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="6"/></svg>
} @else {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
}
</span>
</div>
}
@@ -180,7 +216,7 @@ import {
<section class="panel provenance-panel">
<header class="panel-header">
<h2>Provenance Chain Validator</h2>
<a routerLink="provenance" class="view-all">Full Validator &#8594;</a>
<a routerLink="provenance" class="view-all">Full Validator <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg></a>
</header>
<div class="provenance-input">
<select [(ngModel)]="provenanceInputType">
@@ -273,7 +309,7 @@ import {
}
.subtitle {
color: var(--text-secondary);
color: var(--color-text-secondary);
margin: 0.25rem 0 0;
font-size: 0.9rem;
width: 100%;
@@ -286,7 +322,7 @@ import {
.btn {
padding: 0.5rem 1rem;
border-radius: 4px;
border-radius: var(--radius-sm);
border: none;
cursor: pointer;
font-size: 0.9rem;
@@ -297,14 +333,14 @@ import {
}
.btn-primary {
background: var(--primary);
background: var(--color-brand-primary);
color: var(--color-text-heading);
}
.btn-secondary {
background: var(--surface-elevated);
color: var(--text-primary);
border: 1px solid var(--border);
background: var(--color-surface-elevated);
color: var(--color-text-primary);
border: 1px solid var(--color-border-primary);
}
.kpi-strip {
@@ -315,31 +351,31 @@ import {
}
.kpi-card {
background: var(--surface-card);
border-radius: 8px;
background: var(--color-surface-primary);
border-radius: var(--radius-lg);
padding: 1.25rem;
border: 1px solid var(--border);
border: 1px solid var(--color-border-primary);
position: relative;
}
.kpi-card.success { border-left: 4px solid var(--success); }
.kpi-card.warning { border-left: 4px solid var(--warning); }
.kpi-card.success { border-left: 4px solid var(--color-status-success); }
.kpi-card.warning { border-left: 4px solid var(--color-status-warning); }
.kpi-value {
font-size: 2rem;
font-weight: 700;
color: var(--text-primary);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
}
.kpi-label {
font-size: 0.85rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
margin-top: 0.25rem;
}
.kpi-detail {
font-size: 0.75rem;
color: var(--text-tertiary);
color: var(--color-text-muted);
margin-top: 0.5rem;
}
@@ -350,8 +386,8 @@ import {
font-size: 0.8rem;
}
.kpi-trend.up { color: var(--success); }
.kpi-trend.down { color: var(--success); }
.kpi-trend.up { color: var(--color-status-success); }
.kpi-trend.down { color: var(--color-status-success); }
.dashboard-grid {
display: grid;
@@ -360,9 +396,9 @@ import {
}
.panel {
background: var(--surface-card);
border-radius: 8px;
border: 1px solid var(--border);
background: var(--color-surface-primary);
border-radius: var(--radius-lg);
border: 1px solid var(--color-border-primary);
overflow: hidden;
}
@@ -371,19 +407,22 @@ import {
justify-content: space-between;
align-items: center;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--border);
background: var(--surface-elevated);
border-bottom: 1px solid var(--color-border-primary);
background: var(--color-surface-elevated);
}
.panel-header h2 {
margin: 0;
font-size: 1rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.view-all {
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: 0.85rem;
color: var(--primary);
color: var(--color-brand-primary);
text-decoration: none;
}
@@ -401,12 +440,12 @@ import {
.violations-table td {
padding: 0.75rem 1rem;
text-align: left;
border-bottom: 1px solid var(--border);
border-bottom: 1px solid var(--color-border-primary);
}
.violations-table th {
background: var(--surface-elevated);
font-weight: 600;
background: var(--color-surface-elevated);
font-weight: var(--font-weight-semibold);
}
.timestamp { font-family: monospace; font-size: 0.8rem; }
@@ -414,19 +453,19 @@ import {
.reason-badge, .module-badge {
display: inline-block;
padding: 0.2rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.reason-badge.schema_invalid { background: #fef3c7; color: #92400e; }
.reason-badge.untrusted_source { background: #fee2e2; color: #991b1b; }
.reason-badge.duplicate { background: #e0e7ff; color: #3730a3; }
.reason-badge.malformed_timestamp { background: #fef3c7; color: #92400e; }
.reason-badge.missing_required_fields { background: #fee2e2; color: #991b1b; }
.reason-badge.schema_invalid { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.reason-badge.untrusted_source { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.reason-badge.duplicate { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.reason-badge.malformed_timestamp { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.reason-badge.missing_required_fields { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.module-badge.concelier { background: #dbeafe; color: #1e40af; }
.module-badge.excititor { background: #dcfce7; color: #166534; }
.module-badge.concelier { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.module-badge.excititor { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.btn-icon {
background: none;
@@ -446,7 +485,7 @@ import {
.flow-module h3 {
font-size: 0.85rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
margin: 0 0 0.75rem;
}
@@ -455,21 +494,21 @@ import {
grid-template-columns: 1fr auto auto auto;
gap: 1rem;
padding: 0.5rem 0;
border-bottom: 1px solid var(--border);
border-bottom: 1px solid var(--color-border-primary);
font-size: 0.85rem;
}
.source-status.healthy { color: var(--success); }
.source-status.degraded { color: var(--warning); }
.source-status.unhealthy { color: var(--error); }
.source-status.healthy { color: var(--color-status-success); }
.source-status.degraded { color: var(--color-status-warning); }
.source-status.unhealthy { color: var(--color-status-error); }
.flow-summary {
display: flex;
justify-content: space-between;
padding: 1rem 1.25rem;
background: var(--surface-elevated);
background: var(--color-surface-elevated);
font-size: 0.85rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.provenance-input {
@@ -481,8 +520,8 @@ import {
.provenance-input select,
.provenance-input input {
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 4px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-sm);
font-size: 0.9rem;
}
@@ -491,7 +530,7 @@ import {
.hint {
padding: 0 1.25rem 1rem;
font-size: 0.8rem;
color: var(--text-tertiary);
color: var(--color-text-muted);
margin: 0;
}
@@ -509,15 +548,15 @@ import {
}
.bar-container {
background: var(--surface-elevated);
background: var(--color-surface-elevated);
height: 20px;
border-radius: 4px;
border-radius: var(--radius-sm);
overflow: hidden;
}
.bar {
height: 100%;
background: var(--primary);
background: var(--color-brand-primary);
transition: width 0.3s ease;
}
@@ -525,9 +564,9 @@ import {
display: flex;
justify-content: space-between;
padding: 0.75rem 1.25rem;
background: var(--surface-elevated);
background: var(--color-surface-elevated);
font-size: 0.85rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.dashboard-footer {
@@ -535,9 +574,9 @@ import {
justify-content: space-between;
padding: 1rem 0;
font-size: 0.8rem;
color: var(--text-tertiary);
color: var(--color-text-muted);
margin-top: 1.5rem;
border-top: 1px solid var(--border);
border-top: 1px solid var(--color-border-primary);
}
.loading-overlay {
@@ -546,15 +585,15 @@ import {
align-items: center;
justify-content: center;
padding: 4rem;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid var(--border);
border-top-color: var(--primary);
border-radius: 50%;
border: 3px solid var(--color-border-primary);
border-top-color: var(--color-brand-primary);
border-radius: var(--radius-full);
animation: spin 1s linear infinite;
}
@@ -563,10 +602,10 @@ import {
}
.error-banner {
background: #fee2e2;
color: #991b1b;
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
padding: 1rem;
border-radius: 8px;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
gap: 0.75rem;
@@ -584,8 +623,8 @@ import {
}
.modal {
background: var(--surface-card);
border-radius: 8px;
background: var(--color-surface-primary);
border-radius: var(--radius-lg);
width: 90%;
max-width: 600px;
max-height: 80vh;
@@ -597,7 +636,7 @@ import {
justify-content: space-between;
align-items: center;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--border);
border-bottom: 1px solid var(--color-border-primary);
}
.modal-header h3 { margin: 0; }
@@ -607,7 +646,7 @@ import {
border: none;
font-size: 1.5rem;
cursor: pointer;
color: var(--text-secondary);
color: var(--color-text-secondary);
}
.modal-body {
@@ -615,9 +654,9 @@ import {
}
.payload-sample {
background: var(--surface-elevated);
background: var(--color-surface-elevated);
padding: 1rem;
border-radius: 4px;
border-radius: var(--radius-sm);
overflow-x: auto;
font-size: 0.8rem;
margin-top: 1rem;
@@ -712,9 +751,9 @@ export class AocComplianceDashboardComponent implements OnInit {
getTrendIcon(trend?: 'up' | 'down' | 'stable'): string {
switch (trend) {
case 'up': return '&#9650;';
case 'down': return '&#9660;';
default: return '&#8212;';
case 'up': return '\u25B2';
case 'down': return '\u25BC';
default: return '\u2014';
}
}

View File

@@ -95,32 +95,32 @@ import { ComplianceReportSummary, ComplianceReportRequest, ComplianceReportForma
styles: [`
.report-page { padding: 1.5rem; max-width: 900px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
h1 { margin: 0 0 0.25rem; }
.description { color: var(--text-secondary); font-size: 0.9rem; margin: 0; }
.report-config { background: var(--surface-card); border-radius: 8px; border: 1px solid var(--border); padding: 1.5rem; margin-bottom: 2rem; }
.description { color: var(--color-text-secondary); font-size: 0.9rem; margin: 0; }
.report-config { background: var(--color-surface-primary); border-radius: var(--radius-lg); border: 1px solid var(--color-border-primary); padding: 1.5rem; margin-bottom: 2rem; }
.config-section { margin-bottom: 1.5rem; }
.config-section h3 { margin: 0 0 0.75rem; font-size: 0.95rem; }
.date-range { display: flex; gap: 1.5rem; }
.date-range label { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; color: var(--text-secondary); }
.date-range input { padding: 0.5rem; border: 1px solid var(--border); border-radius: 4px; }
.date-range label { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; color: var(--color-text-secondary); }
.date-range input { padding: 0.5rem; border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); }
.checkbox, .radio { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; }
.format-options { display: flex; gap: 1.5rem; }
.btn-primary { background: var(--primary); color: var(--color-text-heading); border: none; padding: 0.75rem 1.5rem; border-radius: 4px; cursor: pointer; font-size: 1rem; }
.btn-primary { background: var(--color-brand-primary); color: var(--color-text-heading); border: none; padding: 0.75rem 1.5rem; border-radius: var(--radius-sm); cursor: pointer; font-size: 1rem; }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.report-preview { background: var(--surface-card); border-radius: 8px; border: 1px solid var(--border); overflow: hidden; }
.preview-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.25rem; background: var(--surface-elevated); border-bottom: 1px solid var(--border); }
.report-preview { background: var(--color-surface-primary); border-radius: var(--radius-lg); border: 1px solid var(--color-border-primary); overflow: hidden; }
.preview-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.25rem; background: var(--color-surface-elevated); border-bottom: 1px solid var(--color-border-primary); }
.preview-header h2 { margin: 0; font-size: 1.1rem; }
.report-id { font-size: 0.8rem; color: var(--text-tertiary); font-family: monospace; }
.report-id { font-size: 0.8rem; color: var(--color-text-muted); font-family: monospace; }
.preview-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; padding: 1.25rem; }
.preview-section { padding: 1rem; background: var(--surface-elevated); border-radius: 4px; }
.preview-section h4 { margin: 0 0 0.5rem; font-size: 0.85rem; color: var(--text-secondary); }
.stat { font-size: 1.5rem; font-weight: 700; }
.breakdown { margin-top: 0.5rem; font-size: 0.8rem; color: var(--text-secondary); }
.preview-section { padding: 1rem; background: var(--color-surface-elevated); border-radius: var(--radius-sm); }
.preview-section h4 { margin: 0 0 0.5rem; font-size: 0.85rem; color: var(--color-text-secondary); }
.stat { font-size: 1.5rem; font-weight: var(--font-weight-bold); }
.breakdown { margin-top: 0.5rem; font-size: 0.8rem; color: var(--color-text-secondary); }
.breakdown-item { margin-bottom: 0.25rem; }
.preview-footer { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.25rem; background: var(--surface-elevated); border-top: 1px solid var(--border); }
.generated-at { font-size: 0.8rem; color: var(--text-tertiary); }
.preview-footer { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.25rem; background: var(--color-surface-elevated); border-top: 1px solid var(--color-border-primary); }
.generated-at { font-size: 0.8rem; color: var(--color-text-muted); }
`]
})
export class ComplianceReportComponent {

View File

@@ -76,25 +76,25 @@ import { GuardViolation, GuardViolationsPagedResponse, GuardViolationReason } fr
styles: [`
.violations-page { padding: 1.5rem; max-width: 1400px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
h1 { margin: 0; }
.filters { display: flex; gap: 1rem; margin-bottom: 1rem; }
.filters select { padding: 0.5rem; border: 1px solid var(--border); border-radius: 4px; }
.violations-table { width: 100%; border-collapse: collapse; background: var(--surface-card); border-radius: 8px; overflow: hidden; }
.violations-table th, .violations-table td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid var(--border); }
.violations-table th { background: var(--surface-elevated); font-weight: 600; }
.filters select { padding: 0.5rem; border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); }
.violations-table { width: 100%; border-collapse: collapse; background: var(--color-surface-primary); border-radius: var(--radius-lg); overflow: hidden; }
.violations-table th, .violations-table td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid var(--color-border-primary); }
.violations-table th { background: var(--color-surface-elevated); font-weight: var(--font-weight-semibold); }
.mono { font-family: monospace; font-size: 0.8rem; }
.message { max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.badge { display: inline-block; padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.75rem; }
.badge.schema_invalid, .badge.malformed_timestamp { background: #fef3c7; color: #92400e; }
.badge.untrusted_source, .badge.missing_required_fields { background: #fee2e2; color: #991b1b; }
.badge.duplicate { background: #e0e7ff; color: #3730a3; }
.badge.module.concelier { background: #dbeafe; color: #1e40af; }
.badge.module.excititor { background: #dcfce7; color: #166534; }
.badge { display: inline-block; padding: 0.2rem 0.5rem; border-radius: var(--radius-sm); font-size: 0.75rem; }
.badge.schema_invalid, .badge.malformed_timestamp { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.badge.untrusted_source, .badge.missing_required_fields { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.duplicate { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.badge.module.concelier { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.module.excititor { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.btn-sm { padding: 0.25rem 0.5rem; font-size: 0.8rem; cursor: pointer; }
.pagination { display: flex; justify-content: center; gap: 1rem; align-items: center; margin-top: 1rem; }
.loading { text-align: center; padding: 2rem; color: var(--text-secondary); }
.loading { text-align: center; padding: 2rem; color: var(--color-text-secondary); }
`]
})
export class GuardViolationsListComponent implements OnInit {

View File

@@ -16,7 +16,7 @@ import { IngestionFlowSummary, IngestionSourceMetrics } from '../../core/api/aoc
<a routerLink="/ops/aoc">AOC Compliance</a> / Ingestion Flow
</div>
<h1>Ingestion Flow Metrics</h1>
<button class="btn-secondary" (click)="refresh()">&#8635; Refresh</button>
<button class="btn-secondary" (click)="refresh()"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg> Refresh</button>
</header>
@if (flow()) {
@@ -87,32 +87,32 @@ import { IngestionFlowSummary, IngestionSourceMetrics } from '../../core/api/aoc
.ingestion-page { padding: 1.5rem; max-width: 1200px; margin: 0 auto; }
.page-header { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; margin-bottom: 1.5rem; }
.page-header h1 { margin: 0; flex: 1; }
.breadcrumb { width: 100%; font-size: 0.85rem; color: var(--text-secondary); }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.btn-secondary { padding: 0.5rem 1rem; background: var(--surface-elevated); border: 1px solid var(--border); border-radius: 4px; cursor: pointer; }
.summary-strip { display: flex; gap: 2rem; justify-content: center; padding: 1.5rem; background: var(--surface-card); border-radius: 8px; margin-bottom: 2rem; }
.breadcrumb { width: 100%; font-size: 0.85rem; color: var(--color-text-secondary); }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
.btn-secondary { padding: 0.5rem 1rem; background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); cursor: pointer; }
.summary-strip { display: flex; gap: 2rem; justify-content: center; padding: 1.5rem; background: var(--color-surface-primary); border-radius: var(--radius-lg); margin-bottom: 2rem; }
.summary-item { text-align: center; }
.summary-item .value { display: block; font-size: 2rem; font-weight: 700; }
.summary-item .label { font-size: 0.85rem; color: var(--text-secondary); }
.summary-item .value { display: block; font-size: 2rem; font-weight: var(--font-weight-bold); }
.summary-item .label { font-size: 0.85rem; color: var(--color-text-secondary); }
.module-section { margin-bottom: 2rem; }
.module-section h2 { font-size: 1.1rem; margin-bottom: 1rem; }
.sources-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; }
.source-card { background: var(--surface-card); border-radius: 8px; border: 1px solid var(--border); padding: 1rem; }
.source-card.healthy { border-left: 4px solid var(--success); }
.source-card.degraded { border-left: 4px solid var(--warning); }
.source-card.unhealthy { border-left: 4px solid var(--error); }
.source-card { background: var(--color-surface-primary); border-radius: var(--radius-lg); border: 1px solid var(--color-border-primary); padding: 1rem; }
.source-card.healthy { border-left: 4px solid var(--color-status-success); }
.source-card.degraded { border-left: 4px solid var(--color-status-warning); }
.source-card.unhealthy { border-left: 4px solid var(--color-status-error); }
.source-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; }
.source-name { font-weight: 600; }
.status-badge { font-size: 0.75rem; padding: 0.2rem 0.5rem; border-radius: 4px; }
.status-badge.healthy { background: #dcfce7; color: #166534; }
.status-badge.degraded { background: #fef3c7; color: #92400e; }
.status-badge.unhealthy { background: #fee2e2; color: #991b1b; }
.source-name { font-weight: var(--font-weight-semibold); }
.status-badge { font-size: 0.75rem; padding: 0.2rem 0.5rem; border-radius: var(--radius-sm); }
.status-badge.healthy { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.status-badge.degraded { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.status-badge.unhealthy { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.metrics-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.5rem; }
.metric { text-align: center; }
.metric .value { display: block; font-size: 1.1rem; font-weight: 600; }
.metric .label { font-size: 0.7rem; color: var(--text-tertiary); }
.source-footer { font-size: 0.75rem; color: var(--text-tertiary); margin-top: 1rem; padding-top: 0.75rem; border-top: 1px solid var(--border); }
.page-footer { text-align: center; font-size: 0.8rem; color: var(--text-tertiary); margin-top: 2rem; }
.metric .value { display: block; font-size: 1.1rem; font-weight: var(--font-weight-semibold); }
.metric .label { font-size: 0.7rem; color: var(--color-text-muted); }
.source-footer { font-size: 0.75rem; color: var(--color-text-muted); margin-top: 1rem; padding-top: 0.75rem; border-top: 1px solid var(--color-border-primary); }
.page-footer { text-align: center; font-size: 0.8rem; color: var(--color-text-muted); margin-top: 2rem; }
`]
})
export class IngestionFlowComponent implements OnInit {

View File

@@ -37,14 +37,18 @@ import { ProvenanceChain, ProvenanceStep } from '../../core/api/aoc.models';
<header class="result-header">
<h2>{{ chain()?.inputValue }}</h2>
<span class="status" [class.valid]="chain()?.isComplete">
{{ chain()?.isComplete ? '&#10004; Complete Chain' : '&#10006; Incomplete Chain' }}
@if (chain()?.isComplete) {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg> Complete Chain
} @else {
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> Incomplete Chain
}
</span>
</header>
@if (chain()?.validationErrors?.length) {
<div class="errors">
@for (err of chain()?.validationErrors; track err) {
<div class="error">&#9888; {{ err }}</div>
<div class="error"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg> {{ err }}</div>
}
</div>
}
@@ -54,7 +58,13 @@ import { ProvenanceChain, ProvenanceStep } from '../../core/api/aoc.models';
<div class="step" [class]="step.status">
<div class="step-marker">
<div class="marker-icon" [class]="step.status">
{{ step.status === 'valid' ? '&#10004;' : step.status === 'warning' ? '&#9888;' : '&#10006;' }}
@if (step.status === 'valid') {
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>
} @else if (step.status === 'warning') {
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
} @else {
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
}
</div>
@if (!last) {
<div class="connector"></div>
@@ -70,7 +80,7 @@ import { ProvenanceChain, ProvenanceStep } from '../../core/api/aoc.models';
<div class="step-hash mono">{{ truncateHash(step.hash) }}</div>
}
@if (step.linkedFromHash) {
<div class="step-link mono">&#8592; {{ truncateHash(step.linkedFromHash) }}</div>
<div class="step-link mono"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg> {{ truncateHash(step.linkedFromHash) }}</div>
}
@if (step.errorMessage) {
<div class="step-error">{{ step.errorMessage }}</div>
@@ -90,41 +100,41 @@ import { ProvenanceChain, ProvenanceStep } from '../../core/api/aoc.models';
styles: [`
.provenance-page { padding: 1.5rem; max-width: 900px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
h1 { margin: 0 0 0.25rem; }
.description { color: var(--text-secondary); font-size: 0.9rem; margin: 0; }
.description { color: var(--color-text-secondary); font-size: 0.9rem; margin: 0; }
.validator-input { display: flex; gap: 0.75rem; margin-bottom: 2rem; }
.validator-input select, .validator-input input { padding: 0.75rem; border: 1px solid var(--border); border-radius: 4px; font-size: 1rem; }
.validator-input select, .validator-input input { padding: 0.75rem; border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); font-size: 1rem; }
.validator-input input { flex: 1; }
.btn-primary { background: var(--primary); color: var(--color-text-heading); border: none; padding: 0.75rem 1.5rem; border-radius: 4px; cursor: pointer; }
.btn-primary { background: var(--color-brand-primary); color: var(--color-text-heading); border: none; padding: 0.75rem 1.5rem; border-radius: var(--radius-sm); cursor: pointer; }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.chain-result { background: var(--surface-card); border-radius: 8px; border: 1px solid var(--border); overflow: hidden; }
.chain-result.complete { border-color: var(--success); }
.chain-result.incomplete { border-color: var(--error); }
.result-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.25rem; background: var(--surface-elevated); border-bottom: 1px solid var(--border); }
.chain-result { background: var(--color-surface-primary); border-radius: var(--radius-lg); border: 1px solid var(--color-border-primary); overflow: hidden; }
.chain-result.complete { border-color: var(--color-status-success); }
.chain-result.incomplete { border-color: var(--color-status-error); }
.result-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.25rem; background: var(--color-surface-elevated); border-bottom: 1px solid var(--color-border-primary); }
.result-header h2 { margin: 0; font-size: 1.1rem; }
.status { font-size: 0.85rem; font-weight: 600; }
.status.valid { color: var(--success); }
.errors { padding: 1rem 1.25rem; background: #fef2f2; }
.error { color: #991b1b; font-size: 0.85rem; margin-bottom: 0.5rem; }
.status { font-size: 0.85rem; font-weight: var(--font-weight-semibold); }
.status.valid { color: var(--color-status-success); }
.errors { padding: 1rem 1.25rem; background: var(--color-status-error-bg); }
.error { color: var(--color-status-error-text); font-size: 0.85rem; margin-bottom: 0.5rem; }
.chain-visualization { padding: 1.5rem 1.25rem; }
.step { display: flex; gap: 1rem; margin-bottom: 0.5rem; }
.step-marker { display: flex; flex-direction: column; align-items: center; width: 32px; }
.marker-icon { width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1rem; background: var(--surface-elevated); border: 2px solid var(--border); }
.marker-icon.valid { border-color: var(--success); color: var(--success); }
.marker-icon.warning { border-color: var(--warning); color: var(--warning); }
.marker-icon.error { border-color: var(--error); color: var(--error); }
.connector { width: 2px; flex: 1; background: var(--border); margin: 4px 0; min-height: 40px; }
.marker-icon { width: 32px; height: 32px; border-radius: var(--radius-full); display: flex; align-items: center; justify-content: center; background: var(--color-surface-elevated); border: 2px solid var(--color-border-primary); }
.marker-icon.valid { border-color: var(--color-status-success); color: var(--color-status-success); }
.marker-icon.warning { border-color: var(--color-status-warning); color: var(--color-status-warning); }
.marker-icon.error { border-color: var(--color-status-error); color: var(--color-status-error); }
.connector { width: 2px; flex: 1; background: var(--color-border-primary); margin: 4px 0; min-height: 40px; }
.step-content { flex: 1; padding-bottom: 1.5rem; }
.step-header { display: flex; justify-content: space-between; margin-bottom: 0.25rem; }
.step-type { font-size: 0.75rem; color: var(--text-tertiary); text-transform: uppercase; }
.step-time { font-size: 0.75rem; color: var(--text-tertiary); }
.step-label { font-weight: 600; margin-bottom: 0.25rem; }
.step-hash, .step-link { font-size: 0.75rem; color: var(--text-secondary); }
.step-type { font-size: 0.75rem; color: var(--color-text-muted); text-transform: uppercase; }
.step-time { font-size: 0.75rem; color: var(--color-text-muted); }
.step-label { font-weight: var(--font-weight-semibold); margin-bottom: 0.25rem; }
.step-hash, .step-link { font-size: 0.75rem; color: var(--color-text-secondary); }
.mono { font-family: monospace; }
.step-error { color: var(--error); font-size: 0.8rem; margin-top: 0.25rem; }
.result-footer { padding: 0.75rem 1.25rem; background: var(--surface-elevated); font-size: 0.8rem; color: var(--text-tertiary); border-top: 1px solid var(--border); }
.step-error { color: var(--color-status-error); font-size: 0.8rem; margin-top: 0.25rem; }
.result-footer { padding: 0.75rem 1.25rem; background: var(--color-surface-elevated); font-size: 0.8rem; color: var(--color-text-muted); border-top: 1px solid var(--color-border-primary); }
`]
})
export class ProvenanceValidatorComponent implements OnInit {

View File

@@ -60,22 +60,22 @@ import { ViolationDrilldownComponent } from './violation-drilldown.component';
.status-line {
margin: 0.5rem 0 0;
font-size: 0.875rem;
color: #1d4ed8;
font-weight: 600;
color: var(--color-status-info-text);
font-weight: var(--font-weight-semibold);
}
.selection-line {
margin: 0.25rem 0 0;
font-size: 0.875rem;
color: #0f766e;
font-weight: 600;
color: var(--color-status-success-text);
font-weight: var(--font-weight-semibold);
}
.event-line {
margin: 0;
font-size: 0.875rem;
color: #374151;
font-weight: 500;
color: var(--color-text-primary);
font-weight: var(--font-weight-medium);
}
`],
})

View File

@@ -142,7 +142,7 @@ interface ApprovalDetailState {
<p class="panel-subtitle">{{ selectedWitness()!.findingId }} in {{ selectedWitness()!.component }}&#64;{{ selectedWitness()!.version }}</p>
</div>
<button type="button" class="btn btn--sm btn--secondary" (click)="closeWitness()">
Close
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> Close
</button>
</div>
@@ -170,10 +170,10 @@ interface ApprovalDetailState {
<div class="callpath-node" [class]="'callpath-node--' + node.type">
<div class="node-icon">
@switch (node.type) {
@case ('entry') { }
@case ('call') { }
@case ('guard') { 🛡 }
@case ('sink') { }
@case ('entry') { <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="5 3 19 12 5 21 5 3"/></svg> }
@case ('call') { <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> }
@case ('guard') { <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> }
@case ('sink') { <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg> }
}
</div>
<div class="node-info">
@@ -182,7 +182,7 @@ interface ApprovalDetailState {
</div>
</div>
@if (!last) {
<div class="callpath-arrow"></div>
<div class="callpath-arrow"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg></div>
}
}
</div>
@@ -221,7 +221,7 @@ interface ApprovalDetailState {
<span class="analysis-label">Guards Detected</span>
<div class="guards-list">
@for (guard of selectedWitness()!.analysisDetails.guards; track guard) {
<span class="guard-badge">🛡 {{ guard }}</span>
<span class="guard-badge"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> {{ guard }}</span>
}
</div>
</div>
@@ -307,14 +307,14 @@ interface ApprovalDetailState {
class="btn btn--success btn--lg"
(click)="approve()"
>
Approve
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg> Approve
</button>
<button
type="button"
class="btn btn--danger btn--lg"
(click)="reject()"
>
Reject
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> Reject
</button>
</div>
@@ -336,7 +336,7 @@ interface ApprovalDetailState {
<div class="evidence-link">
<h4>Evidence</h4>
<a [routerLink]="['/evidence', approval().evidenceId]" class="btn btn--secondary btn--block">
📋 Open Evidence Packet
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><line x1="12" y1="11" x2="12" y2="17"/><line x1="9" y1="14" x2="15" y2="14"/></svg> Open Evidence Packet
</a>
</div>
</div>
@@ -352,27 +352,27 @@ interface ApprovalDetailState {
display: inline-block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
color: var(--primary-color);
color: var(--color-brand-primary);
text-decoration: none;
}
.header-main { display: flex; align-items: center; gap: 1rem; margin-bottom: 0.5rem; }
.page-title { margin: 0; font-size: 1.5rem; font-weight: 600; }
.page-title { margin: 0; font-size: 1.5rem; font-weight: var(--font-weight-semibold); }
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.status-badge--pending { background: var(--yellow-100); color: var(--yellow-700); }
.status-badge--approved { background: var(--green-100); color: var(--green-700); }
.status-badge--rejected { background: var(--red-100); color: var(--red-700); }
.page-subtitle { margin: 0; color: var(--text-color-secondary); font-size: 0.875rem; }
.status-badge--pending { background: var(--color-severity-medium-bg); color: var(--color-status-warning-text); }
.status-badge--approved { background: var(--color-severity-low-bg); color: var(--color-status-success-text); }
.status-badge--rejected { background: var(--color-severity-critical-bg); color: var(--color-status-error-text); }
.page-subtitle { margin: 0; color: var(--color-text-secondary); font-size: 0.875rem; }
.digest {
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
font-size: 0.75rem;
padding: 0.125rem 0.375rem;
background: var(--surface-ground);
border-radius: 4px;
background: var(--color-surface-secondary);
border-radius: var(--radius-sm);
}
.content-layout { display: grid; grid-template-columns: 1fr 320px; gap: 1.5rem; }
@@ -381,12 +381,12 @@ interface ApprovalDetailState {
.panel {
padding: 1.25rem;
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
}
.panel-title { margin: 0 0 0.25rem; font-size: 1rem; font-weight: 600; }
.panel-subtitle { margin: 0 0 1rem; font-size: 0.875rem; color: var(--text-color-secondary); }
.panel-title { margin: 0 0 0.25rem; font-size: 1rem; font-weight: var(--font-weight-semibold); }
.panel-subtitle { margin: 0 0 1rem; font-size: 0.875rem; color: var(--color-text-secondary); }
.diff-summary { display: flex; gap: 1.5rem; margin-bottom: 1rem; }
.diff-item { display: flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; }
@@ -396,96 +396,96 @@ interface ApprovalDetailState {
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-weight: 600;
border-radius: var(--radius-full);
font-weight: var(--font-weight-semibold);
font-size: 0.875rem;
}
.diff-item--added .diff-icon { background: var(--red-100); color: var(--red-600); }
.diff-item--removed .diff-icon { background: var(--green-100); color: var(--green-600); }
.diff-item--changed .diff-icon { background: var(--blue-100); color: var(--blue-600); }
.diff-item--added .diff-icon { background: var(--color-severity-critical-bg); color: var(--color-status-error-text); }
.diff-item--removed .diff-icon { background: var(--color-severity-low-bg); color: var(--color-status-success-text); }
.diff-item--changed .diff-icon { background: var(--color-severity-info-bg); color: var(--color-status-info-text); }
.diff-table { width: 100%; border-collapse: collapse; }
.diff-table th, .diff-table td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid var(--surface-border);
border-bottom: 1px solid var(--color-border-primary);
}
.diff-table th {
font-size: 0.75rem;
font-weight: 600;
color: var(--text-color-secondary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
text-transform: uppercase;
}
.diff-row--added { background: var(--red-50); }
.diff-row--removed { background: var(--green-50); }
.diff-row--added { background: var(--color-severity-critical-bg); }
.diff-row--removed { background: var(--color-severity-low-bg); }
.change-badge {
padding: 0.125rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.625rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.change-badge--new { background: var(--red-100); color: var(--red-700); }
.change-badge--fixed { background: var(--green-100); color: var(--green-700); }
.change-badge--new { background: var(--color-severity-critical-bg); color: var(--color-status-error-text); }
.change-badge--fixed { background: var(--color-severity-low-bg); color: var(--color-status-success-text); }
.severity {
padding: 0.125rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.625rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.severity--critical { background: var(--purple-100); color: var(--purple-700); }
.severity--high { background: var(--red-100); color: var(--red-700); }
.severity--medium { background: var(--yellow-100); color: var(--yellow-700); }
.severity--critical { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.severity--high { background: var(--color-severity-critical-bg); color: var(--color-status-error-text); }
.severity--medium { background: var(--color-severity-medium-bg); color: var(--color-status-warning-text); }
.reachability-chip {
padding: 0.25rem 0.5rem;
border: 1px solid;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
cursor: pointer;
background: transparent;
}
.reachability-chip--reachable {
border-color: var(--red-200);
color: var(--red-700);
border-color: var(--color-severity-critical-border);
color: var(--color-status-error-text);
}
.reachability-chip--unreachable {
border-color: var(--green-200);
color: var(--green-700);
border-color: var(--color-severity-low-border);
color: var(--color-status-success-text);
}
.gate-list { display: flex; flex-direction: column; gap: 0.5rem; }
.gate-item { display: flex; align-items: center; gap: 0.5rem; }
.gate-badge {
padding: 0.125rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.625rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.gate-badge--pass { background: var(--green-100); color: var(--green-700); }
.gate-badge--warn { background: var(--yellow-100); color: var(--yellow-700); }
.gate-badge--block { background: var(--red-100); color: var(--red-700); }
.gate-badge--pass { background: var(--color-severity-low-bg); color: var(--color-status-success-text); }
.gate-badge--warn { background: var(--color-severity-medium-bg); color: var(--color-status-warning-text); }
.gate-badge--block { background: var(--color-severity-critical-bg); color: var(--color-status-error-text); }
.gate-name { flex: 1; }
.comments-list { margin-bottom: 1rem; }
.comment {
padding: 0.75rem;
background: var(--surface-ground);
border-radius: 6px;
background: var(--color-surface-secondary);
border-radius: var(--radius-md);
margin-bottom: 0.5rem;
}
.comment-header { display: flex; justify-content: space-between; margin-bottom: 0.25rem; }
.comment-author { font-weight: 500; font-size: 0.875rem; }
.comment-time { font-size: 0.75rem; color: var(--text-color-secondary); }
.comment-author { font-weight: var(--font-weight-medium); font-size: 0.875rem; }
.comment-time { font-size: 0.75rem; color: var(--color-text-secondary); }
.comment-body { margin: 0; font-size: 0.875rem; }
.no-comments { color: var(--text-color-secondary); font-size: 0.875rem; }
.no-comments { color: var(--color-text-secondary); font-size: 0.875rem; }
.comment-form { display: flex; flex-direction: column; gap: 0.5rem; }
.comment-input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid var(--surface-border);
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
font-size: 0.875rem;
resize: vertical;
}
@@ -493,12 +493,12 @@ interface ApprovalDetailState {
.decision-sidebar { position: sticky; top: 1rem; }
.decision-panel {
padding: 1.25rem;
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
}
.decision-panel h3 { margin: 0 0 1rem; font-size: 1rem; font-weight: 600; }
.decision-info { margin-bottom: 1rem; font-size: 0.875rem; color: var(--text-color-secondary); }
.decision-panel h3 { margin: 0 0 1rem; font-size: 1rem; font-weight: var(--font-weight-semibold); }
.decision-info { margin-bottom: 1rem; font-size: 0.875rem; color: var(--color-text-secondary); }
.decision-info p { margin: 0; }
.decision-actions { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1rem; }
.decision-options { margin-bottom: 1rem; }
@@ -510,14 +510,14 @@ interface ApprovalDetailState {
cursor: pointer;
}
.decision-result { margin-bottom: 1rem; }
.decision-time { font-size: 0.75rem; color: var(--text-color-secondary); }
.evidence-link h4 { margin: 0 0 0.5rem; font-size: 0.875rem; font-weight: 600; }
.decision-time { font-size: 0.75rem; color: var(--color-text-secondary); }
.evidence-link h4 { margin: 0 0 0.5rem; font-size: 0.875rem; font-weight: var(--font-weight-semibold); }
.btn {
padding: 0.5rem 1rem;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
text-decoration: none;
display: inline-flex;
@@ -528,16 +528,16 @@ interface ApprovalDetailState {
.btn--sm { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
.btn--lg { padding: 0.75rem 1.5rem; font-size: 1rem; }
.btn--block { width: 100%; }
.btn--secondary { background: var(--surface-ground); border: 1px solid var(--surface-border); color: var(--text-color); }
.btn--success { background: var(--green-600); border: none; color: white; }
.btn--danger { background: var(--red-600); border: none; color: white; }
.btn--link { background: transparent; border: none; color: var(--primary-color); padding: 0; }
.btn--primary { background: var(--primary-color); border: none; color: var(--color-text-heading); }
.btn--secondary { background: var(--color-surface-secondary); border: 1px solid var(--color-border-primary); color: var(--color-text-primary); }
.btn--success { background: var(--color-status-success-text); border: none; color: white; }
.btn--danger { background: var(--color-status-error-text); border: none; color: white; }
.btn--link { background: transparent; border: none; color: var(--color-brand-primary); padding: 0; }
.btn--primary { background: var(--color-brand-primary); border: none; color: var(--color-text-heading); }
/* Witness Panel Styles (APPR-008 - THE MOAT) */
.witness-panel {
border: 2px solid var(--primary-color);
background: linear-gradient(to bottom, var(--blue-50) 0%, var(--surface-card) 100%);
border: 2px solid var(--color-brand-primary);
background: linear-gradient(to bottom, var(--color-severity-info-bg) 0%, var(--color-surface-primary) 100%);
}
.witness-header {
display: flex;
@@ -550,10 +550,10 @@ interface ApprovalDetailState {
.witness-finding { margin-bottom: 1rem; }
.finding-description {
font-size: 0.875rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
padding: 0.75rem;
background: var(--surface-ground);
border-radius: 6px;
background: var(--color-surface-secondary);
border-radius: var(--radius-md);
}
.witness-state { margin-bottom: 1.25rem; }
@@ -562,35 +562,35 @@ interface ApprovalDetailState {
align-items: center;
gap: 0.75rem;
padding: 0.5rem 1rem;
border-radius: 8px;
border-radius: var(--radius-lg);
margin-bottom: 0.5rem;
}
.state-indicator--reachable {
background: var(--red-100);
border: 1px solid var(--red-200);
background: var(--color-severity-critical-bg);
border: 1px solid var(--color-severity-critical-border);
}
.state-indicator--unreachable {
background: var(--green-100);
border: 1px solid var(--green-200);
background: var(--color-severity-low-bg);
border: 1px solid var(--color-severity-low-border);
}
.state-indicator--uncertain {
background: var(--yellow-100);
border: 1px solid var(--yellow-200);
background: var(--color-severity-medium-bg);
border: 1px solid var(--color-severity-medium-border);
}
.state-label {
font-weight: 700;
font-weight: var(--font-weight-bold);
font-size: 0.875rem;
}
.state-indicator--reachable .state-label { color: var(--red-700); }
.state-indicator--unreachable .state-label { color: var(--green-700); }
.state-indicator--uncertain .state-label { color: var(--yellow-700); }
.state-indicator--reachable .state-label { color: var(--color-status-error-text); }
.state-indicator--unreachable .state-label { color: var(--color-status-success-text); }
.state-indicator--uncertain .state-label { color: var(--color-status-warning-text); }
.state-confidence {
font-size: 0.875rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.confidence-explanation {
font-size: 0.8125rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
font-style: italic;
}
@@ -598,16 +598,16 @@ interface ApprovalDetailState {
.section-title {
margin: 0 0 0.75rem;
font-size: 0.875rem;
font-weight: 600;
color: var(--text-color-secondary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
}
.callpath-visualization {
display: flex;
flex-direction: column;
gap: 0.25rem;
padding: 1rem;
background: var(--surface-ground);
border-radius: 8px;
background: var(--color-surface-secondary);
border-radius: var(--radius-lg);
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
}
.callpath-node {
@@ -615,23 +615,23 @@ interface ApprovalDetailState {
align-items: center;
gap: 0.75rem;
padding: 0.5rem;
border-radius: 6px;
border-radius: var(--radius-md);
}
.callpath-node--entry { background: var(--blue-100); }
.callpath-node--call { background: var(--gray-100); }
.callpath-node--guard { background: var(--yellow-100); }
.callpath-node--sink { background: var(--red-100); }
.callpath-node--entry { background: var(--color-severity-info-bg); }
.callpath-node--call { background: var(--color-severity-none-bg); }
.callpath-node--guard { background: var(--color-severity-medium-bg); }
.callpath-node--sink { background: var(--color-severity-critical-bg); }
.node-icon {
width: 1.5rem;
text-align: center;
font-size: 0.875rem;
}
.node-info { display: flex; flex-direction: column; gap: 0.125rem; }
.node-function { font-size: 0.8125rem; font-weight: 500; }
.node-location { font-size: 0.6875rem; color: var(--text-color-secondary); }
.node-function { font-size: 0.8125rem; font-weight: var(--font-weight-medium); }
.node-location { font-size: 0.6875rem; color: var(--color-text-secondary); }
.callpath-arrow {
text-align: center;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
font-size: 0.75rem;
}
@@ -644,28 +644,28 @@ interface ApprovalDetailState {
}
.analysis-item {
padding: 0.75rem;
background: var(--surface-ground);
border-radius: 6px;
background: var(--color-surface-secondary);
border-radius: var(--radius-md);
}
.analysis-item--full { grid-column: 1 / -1; }
.analysis-label {
display: block;
font-size: 0.6875rem;
font-weight: 600;
color: var(--text-color-secondary);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
text-transform: uppercase;
margin-bottom: 0.25rem;
}
.analysis-value { font-size: 0.875rem; font-weight: 500; }
.analysis-value--warning { color: var(--yellow-600); }
.analysis-value { font-size: 0.875rem; font-weight: var(--font-weight-medium); }
.analysis-value--warning { color: var(--color-status-warning-text); }
.guards-section { margin-top: 0.75rem; }
.guards-list { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; }
.guard-badge {
padding: 0.25rem 0.5rem;
background: var(--yellow-100);
border: 1px solid var(--yellow-200);
border-radius: 4px;
background: var(--color-severity-medium-bg);
border: 1px solid var(--color-severity-medium-border);
border-radius: var(--radius-sm);
font-size: 0.75rem;
}
@@ -673,7 +673,7 @@ interface ApprovalDetailState {
display: flex;
gap: 0.5rem;
padding-top: 1rem;
border-top: 1px solid var(--surface-border);
border-top: 1px solid var(--color-border-primary);
}
@media (max-width: 1024px) {

View File

@@ -34,7 +34,7 @@ import { ActivatedRoute, RouterLink } from '@angular/router';
.back-link {
display: inline-block;
margin-bottom: 1rem;
color: var(--primary-color);
color: var(--color-brand-primary);
text-decoration: none;
&:hover {
@@ -49,14 +49,14 @@ import { ActivatedRoute, RouterLink } from '@angular/router';
p {
margin: 0;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.approval-detail__content {
padding: 2rem;
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
}
`],
changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -97,8 +97,8 @@ interface ApprovalRequest {
.approvals-page { max-width: 1000px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.page-title { margin: 0 0 0.25rem; font-size: 1.5rem; font-weight: 600; }
.page-subtitle { margin: 0; color: var(--text-color-secondary); }
.page-title { margin: 0 0 0.25rem; font-size: 1.5rem; font-weight: var(--font-weight-semibold); }
.page-subtitle { margin: 0; color: var(--color-text-secondary); }
.status-filter {
display: flex;
@@ -110,13 +110,13 @@ interface ApprovalRequest {
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: 6px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
font-size: 0.875rem;
cursor: pointer;
}
.status-filter__btn:hover { background: var(--surface-hover); }
.status-filter__btn:hover { background: var(--color-nav-hover); }
.status-filter__btn--active {
background: var(--color-brand-primary);
border-color: var(--color-brand-primary);
@@ -125,7 +125,7 @@ interface ApprovalRequest {
.status-filter__count {
padding: 0.125rem 0.375rem;
background: rgba(0, 0, 0, 0.1);
border-radius: 10px;
border-radius: var(--radius-xl);
font-size: 0.75rem;
}
@@ -134,9 +134,9 @@ interface ApprovalRequest {
.approval-card {
display: block;
padding: 1rem;
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
text-decoration: none;
color: inherit;
transition: border-color 0.15s, box-shadow 0.15s;
@@ -145,10 +145,10 @@ interface ApprovalRequest {
border-color: var(--color-brand-primary);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.approval-card--pending { border-left: 4px solid var(--yellow-400); }
.approval-card--approved { border-left: 4px solid var(--green-400); }
.approval-card--rejected { border-left: 4px solid var(--red-400); }
.approval-card--expired { border-left: 4px solid var(--gray-400); }
.approval-card--pending { border-left: 4px solid var(--color-severity-medium); }
.approval-card--approved { border-left: 4px solid var(--color-status-success); }
.approval-card--rejected { border-left: 4px solid var(--color-severity-critical); }
.approval-card--expired { border-left: 4px solid var(--color-text-muted); }
.approval-card__header {
display: flex;
@@ -156,19 +156,19 @@ interface ApprovalRequest {
gap: 1rem;
margin-bottom: 0.75rem;
}
.approval-card__release { font-weight: 600; font-size: 1rem; }
.approval-card__route { color: var(--text-color-secondary); }
.approval-card__release { font-weight: var(--font-weight-semibold); font-size: 1rem; }
.approval-card__route { color: var(--color-text-secondary); }
.approval-card__status {
margin-left: auto;
padding: 0.125rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.625rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.approval-card__status--pending { background: var(--yellow-100); color: var(--yellow-700); }
.approval-card__status--approved { background: var(--green-100); color: var(--green-700); }
.approval-card__status--rejected { background: var(--red-100); color: var(--red-700); }
.approval-card__status--expired { background: var(--gray-100); color: var(--gray-600); }
.approval-card__status--pending { background: var(--color-severity-medium-bg); color: var(--color-status-warning-text); }
.approval-card__status--approved { background: var(--color-severity-low-bg); color: var(--color-status-success-text); }
.approval-card__status--rejected { background: var(--color-severity-critical-bg); color: var(--color-status-error-text); }
.approval-card__status--expired { background: var(--color-severity-none-bg); color: var(--color-text-secondary); }
.approval-card__body {
display: flex;
@@ -179,40 +179,40 @@ interface ApprovalRequest {
.approval-card__digest code {
font-size: 0.75rem;
padding: 0.125rem 0.375rem;
background: var(--surface-ground);
border-radius: 4px;
background: var(--color-surface-secondary);
border-radius: var(--radius-sm);
}
.approval-card__meta { font-size: 0.75rem; color: var(--text-color-secondary); }
.approval-card__meta { font-size: 0.75rem; color: var(--color-text-secondary); }
.approval-card__indicators { display: flex; align-items: center; gap: 0.5rem; }
.gate-badge {
padding: 0.125rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.625rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.gate-badge--pass { background: var(--green-100); color: var(--green-700); }
.gate-badge--warn { background: var(--yellow-100); color: var(--yellow-700); }
.gate-badge--block { background: var(--red-100); color: var(--red-700); }
.gate-badge--pass { background: var(--color-severity-low-bg); color: var(--color-status-success-text); }
.gate-badge--warn { background: var(--color-severity-medium-bg); color: var(--color-status-warning-text); }
.gate-badge--block { background: var(--color-severity-critical-bg); color: var(--color-status-error-text); }
.findings-badge {
padding: 0.125rem 0.5rem;
background: var(--orange-100);
color: var(--orange-700);
border-radius: 4px;
background: var(--color-severity-high-bg);
color: var(--color-severity-high);
border-radius: var(--radius-sm);
font-size: 0.625rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
}
.risk-delta { font-size: 0.75rem; color: var(--green-600); }
.risk-delta--worse { color: var(--red-600); }
.risk-delta { font-size: 0.75rem; color: var(--color-status-success-text); }
.risk-delta--worse { color: var(--color-status-error-text); }
.empty-state {
padding: 3rem;
text-align: center;
color: var(--text-color-secondary);
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: 8px;
color: var(--color-text-secondary);
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
}
`]
})

View File

@@ -96,12 +96,12 @@ import { RouterLink } from '@angular/router';
.approvals__title {
margin: 0 0 0.5rem;
font-size: 1.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.approvals__subtitle {
margin: 0;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.approvals__filters {
@@ -114,10 +114,10 @@ import { RouterLink } from '@angular/router';
.filter-select,
.filter-search {
padding: 0.5rem 0.75rem;
border: 1px solid var(--surface-border);
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
font-size: 0.875rem;
background: var(--surface-card);
background: var(--color-surface-primary);
}
.filter-search {
@@ -132,13 +132,13 @@ import { RouterLink } from '@angular/router';
.approvals__section-title {
margin: 0 0 1rem;
font-size: 1rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.approval-card {
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: 8px;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
padding: 1.25rem;
margin-bottom: 1rem;
}
@@ -152,8 +152,8 @@ import { RouterLink } from '@angular/router';
}
.approval-card__release {
font-weight: 600;
color: var(--primary-color);
font-weight: var(--font-weight-semibold);
color: var(--color-brand-primary);
text-decoration: none;
&:hover {
@@ -163,12 +163,12 @@ import { RouterLink } from '@angular/router';
.approval-card__flow {
font-size: 0.875rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
}
.approval-card__meta {
font-size: 0.75rem;
color: var(--text-color-secondary);
color: var(--color-text-secondary);
margin-left: auto;
}
@@ -176,8 +176,8 @@ import { RouterLink } from '@angular/router';
font-size: 0.875rem;
margin-bottom: 1rem;
padding: 0.75rem;
background: var(--surface-ground);
border-radius: 6px;
background: var(--color-surface-secondary);
border-radius: var(--radius-md);
}
.approval-card__gates {
@@ -200,23 +200,23 @@ import { RouterLink } from '@angular/router';
.gate-item__badge {
padding: 0.125rem 0.375rem;
font-size: 0.625rem;
font-weight: 600;
border-radius: 3px;
font-weight: var(--font-weight-semibold);
border-radius: var(--radius-sm);
}
.gate-item--pass .gate-item__badge {
background: var(--green-100);
color: var(--green-700);
background: var(--color-severity-low-bg);
color: var(--color-status-success-text);
}
.gate-item--warn .gate-item__badge {
background: var(--yellow-100);
color: var(--yellow-700);
background: var(--color-severity-medium-bg);
color: var(--color-status-warning-text);
}
.gate-item--block .gate-item__badge {
background: var(--red-100);
color: var(--red-700);
background: var(--color-severity-critical-bg);
color: var(--color-status-error-text);
}
.approval-card__actions {
@@ -231,47 +231,47 @@ import { RouterLink } from '@angular/router';
gap: 0.5rem;
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
text-decoration: none;
cursor: pointer;
transition: background-color 0.15s;
}
.btn--success {
background: var(--green-500);
background: var(--color-status-success);
color: white;
&:hover {
background: var(--green-600);
background: var(--color-status-success-text);
}
}
.btn--danger {
background: var(--red-500);
background: var(--color-severity-critical);
color: white;
&:hover {
background: var(--red-600);
background: var(--color-status-error-text);
}
}
.btn--secondary {
background: var(--surface-ground);
color: var(--text-color);
background: var(--color-surface-secondary);
color: var(--color-text-primary);
&:hover {
background: var(--surface-hover);
background: var(--color-nav-hover);
}
}
.btn--ghost {
background: transparent;
color: var(--primary-color);
color: var(--color-brand-primary);
&:hover {
background: var(--primary-50);
background: var(--color-brand-soft);
}
}
`],

View File

@@ -288,7 +288,7 @@ export interface GateContext {
.modal {
background: var(--color-surface-primary, white);
border-radius: 12px;
border-radius: var(--radius-xl);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
width: 100%;
max-width: 560px;
@@ -320,7 +320,7 @@ export interface GateContext {
.modal__title {
margin: 0;
font-size: 1.125rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
@@ -329,7 +329,7 @@ export interface GateContext {
padding: 0.5rem;
background: none;
border: none;
border-radius: 6px;
border-radius: var(--radius-md);
color: var(--color-text-secondary);
cursor: pointer;
transition: all 0.15s ease;
@@ -363,15 +363,15 @@ export interface GateContext {
gap: 0.75rem;
padding: 1rem;
background: var(--color-surface-secondary);
border-radius: 8px;
border-radius: var(--radius-lg);
margin-bottom: 1.5rem;
}
.gate-context__badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.6875rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
}
@@ -392,7 +392,7 @@ export interface GateContext {
}
.gate-context__name {
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
@@ -420,7 +420,7 @@ export interface GateContext {
.form-label {
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
}
@@ -442,7 +442,7 @@ export interface GateContext {
.form-textarea {
padding: 0.625rem 0.875rem;
border: 1px solid var(--color-border-primary);
border-radius: 6px;
border-radius: var(--radius-md);
font-size: 0.875rem;
color: var(--color-text-primary);
background: white;
@@ -475,7 +475,7 @@ export interface GateContext {
gap: 0.75rem;
padding: 0.875rem;
border: 1px solid var(--color-border-primary);
border-radius: 8px;
border-radius: var(--radius-lg);
cursor: pointer;
transition: all 0.15s ease;
}
@@ -529,7 +529,7 @@ export interface GateContext {
gap: 0.375rem;
padding: 1.5rem;
border: 2px dashed var(--color-border-primary);
border-radius: 8px;
border-radius: var(--radius-lg);
text-align: center;
transition: all 0.15s ease;
}
@@ -551,7 +551,7 @@ export interface GateContext {
.file-input__name {
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
}
@@ -576,7 +576,7 @@ export interface GateContext {
padding: 1rem;
background: var(--color-warning-50);
border: 1px solid var(--color-warning-200);
border-radius: 8px;
border-radius: var(--radius-lg);
cursor: pointer;
}
@@ -608,7 +608,7 @@ export interface GateContext {
padding: 1rem;
background: var(--color-error-50);
border: 1px solid var(--color-error-200);
border-radius: 8px;
border-radius: var(--radius-lg);
color: var(--color-error-700);
font-size: 0.875rem;
}
@@ -626,9 +626,9 @@ export interface GateContext {
gap: 0.5rem;
padding: 0.625rem 1.25rem;
border: none;
border-radius: 8px;
border-radius: var(--radius-lg);
font-size: 0.875rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
transition: all 0.15s ease;
}
@@ -662,7 +662,7 @@ export interface GateContext {
height: 14px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
border-radius: var(--radius-full);
animation: spin 0.6s linear infinite;
}

View File

@@ -89,37 +89,37 @@ import { AuditAnomalyAlert } from '../../core/api/audit-log.models';
styles: [`
.anomalies-page { padding: 1.5rem; max-width: 1200px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
h1 { margin: 0 0 0.25rem; }
.description { color: var(--text-secondary); margin: 0; }
.description { color: var(--color-text-secondary); margin: 0; }
.filter-bar { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; }
.filter-bar button { padding: 0.5rem 1rem; background: var(--surface-card); border: 1px solid var(--border); border-radius: 4px; cursor: pointer; }
.filter-bar button.active { background: var(--primary); color: var(--color-text-heading); border-color: var(--primary); }
.filter-bar button { padding: 0.5rem 1rem; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); cursor: pointer; }
.filter-bar button.active { background: var(--color-brand-primary); color: var(--color-text-heading); border-color: var(--color-brand-primary); }
.alerts-list { display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem; }
.alert-card { background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; }
.alert-card.warning { border-left: 4px solid var(--warning); }
.alert-card.error { border-left: 4px solid var(--error); }
.alert-card.critical { border-left: 4px solid #7f1d1d; }
.alert-card { background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; }
.alert-card.warning { border-left: 4px solid var(--color-status-warning); }
.alert-card.error { border-left: 4px solid var(--color-status-error); }
.alert-card.critical { border-left: 4px solid var(--color-status-error-text); }
.alert-card.acknowledged { opacity: 0.7; }
.alert-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; }
.alert-type { font-weight: 600; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.75rem; }
.badge.severity.warning { background: #fef3c7; color: #92400e; }
.badge.severity.error { background: #fee2e2; color: #991b1b; }
.badge.severity.critical { background: #7f1d1d; color: white; }
.alert-type { font-weight: var(--font-weight-semibold); }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.75rem; }
.badge.severity.warning { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.badge.severity.error { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.severity.critical { background: var(--color-status-error-text); color: white; }
.alert-description { margin: 0 0 0.75rem; font-size: 0.9rem; }
.alert-meta { display: flex; gap: 1.5rem; font-size: 0.8rem; color: var(--text-secondary); margin-bottom: 0.75rem; }
.ack-info { font-size: 0.8rem; color: var(--text-secondary); font-style: italic; }
.alert-meta { display: flex; gap: 1.5rem; font-size: 0.8rem; color: var(--color-text-secondary); margin-bottom: 0.75rem; }
.ack-info { font-size: 0.8rem; color: var(--color-text-secondary); font-style: italic; }
.alert-actions { display: flex; gap: 0.75rem; }
.btn-primary { background: var(--primary); color: var(--color-text-heading); border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; }
.btn-secondary { background: var(--surface-elevated); border: 1px solid var(--border); padding: 0.5rem 1rem; border-radius: 4px; text-decoration: none; color: inherit; }
.no-alerts { text-align: center; padding: 3rem; color: var(--text-secondary); background: var(--surface-card); border-radius: 8px; }
.btn-primary { background: var(--color-brand-primary); color: var(--color-text-heading); border: none; padding: 0.5rem 1rem; border-radius: var(--radius-sm); cursor: pointer; }
.btn-secondary { background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); padding: 0.5rem 1rem; border-radius: var(--radius-sm); text-decoration: none; color: inherit; }
.no-alerts { text-align: center; padding: 3rem; color: var(--color-text-secondary); background: var(--color-surface-primary); border-radius: var(--radius-lg); }
.anomaly-types h2 { margin: 0 0 1rem; font-size: 1.1rem; }
.types-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; }
.type-card { background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; }
.type-card { background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; }
.type-card h4 { margin: 0 0 0.5rem; font-size: 0.95rem; }
.type-card p { margin: 0; font-size: 0.85rem; color: var(--text-secondary); }
.type-card p { margin: 0; font-size: 0.85rem; color: var(--color-text-secondary); }
`]
})
export class AuditAnomaliesComponent implements OnInit {

View File

@@ -79,32 +79,32 @@ import { AuditEvent } from '../../core/api/audit-log.models';
styles: [`
.authority-audit-page { padding: 1.5rem; max-width: 1400px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
h1 { margin: 0 0 0.25rem; }
.description { color: var(--text-secondary); margin: 0; }
.description { color: var(--color-text-secondary); margin: 0; }
.tabs { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; }
.tabs button { padding: 0.5rem 1rem; background: var(--surface-card); border: 1px solid var(--border); border-radius: 4px; cursor: pointer; }
.tabs button.active { background: var(--primary); color: var(--color-text-heading); border-color: var(--primary); }
.events-table { width: 100%; border-collapse: collapse; background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; }
.events-table th, .events-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--border); }
.events-table th { background: var(--surface-elevated); font-weight: 600; font-size: 0.85rem; }
.tabs button { padding: 0.5rem 1rem; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); cursor: pointer; }
.tabs button.active { background: var(--color-brand-primary); color: var(--color-text-heading); border-color: var(--color-brand-primary); }
.events-table { width: 100%; border-collapse: collapse; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); }
.events-table th, .events-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--color-border-primary); }
.events-table th { background: var(--color-surface-elevated); font-weight: var(--font-weight-semibold); font-size: 0.85rem; }
.clickable { cursor: pointer; }
.clickable:hover { background: var(--surface-elevated); }
.clickable.warning { background: #fffbeb; }
.clickable.error { background: #fef2f2; }
.clickable:hover { background: var(--color-surface-elevated); }
.clickable.warning { background: var(--color-status-warning-bg); }
.clickable.error { background: var(--color-status-error-bg); }
.mono { font-family: monospace; font-size: 0.8rem; }
.token-id { max-width: 100px; overflow: hidden; text-overflow: ellipsis; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.75rem; }
.badge.action { background: var(--surface-elevated); }
.badge.action.issue { background: #dcfce7; color: #166534; }
.badge.action.refresh { background: #dbeafe; color: #1e40af; }
.badge.action.revoke { background: #fee2e2; color: #991b1b; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.75rem; }
.badge.action { background: var(--color-surface-elevated); }
.badge.action.issue { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.action.refresh { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.action.revoke { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.scopes { max-width: 200px; }
.scope-badge { display: inline-block; background: var(--surface-elevated); padding: 0.1rem 0.3rem; border-radius: 4px; font-size: 0.7rem; margin-right: 0.25rem; }
.more { font-size: 0.7rem; color: var(--text-tertiary); }
.reason { color: var(--error); font-size: 0.8rem; }
.expires { font-size: 0.8rem; color: var(--text-secondary); }
.scope-badge { display: inline-block; background: var(--color-surface-elevated); padding: 0.1rem 0.3rem; border-radius: var(--radius-sm); font-size: 0.7rem; margin-right: 0.25rem; }
.more { font-size: 0.7rem; color: var(--color-text-muted); }
.reason { color: var(--color-status-error); font-size: 0.8rem; }
.expires { font-size: 0.8rem; color: var(--color-text-secondary); }
.pagination { display: flex; justify-content: center; margin-top: 1rem; }
.pagination button { padding: 0.5rem 1rem; cursor: pointer; }
.pagination button:disabled { opacity: 0.5; cursor: not-allowed; }

View File

@@ -77,36 +77,36 @@ import { AuditCorrelationCluster } from '../../core/api/audit-log.models';
styles: [`
.correlations-page { padding: 1.5rem; max-width: 1200px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
h1 { margin: 0 0 0.25rem; }
.description { color: var(--text-secondary); margin: 0; }
.description { color: var(--color-text-secondary); margin: 0; }
.clusters-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 1rem; }
.cluster-card { background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; cursor: pointer; transition: border-color 0.2s; }
.cluster-card:hover { border-color: var(--primary); }
.cluster-card { background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; cursor: pointer; transition: border-color 0.2s; }
.cluster-card:hover { border-color: var(--color-brand-primary); }
.cluster-header { display: flex; justify-content: space-between; margin-bottom: 0.5rem; }
.correlation-id { font-family: monospace; font-size: 0.85rem; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.75rem; }
.badge.success { background: #dcfce7; color: #166534; }
.badge.failure { background: #fee2e2; color: #991b1b; }
.badge.partial { background: #fef3c7; color: #92400e; }
.badge.module { background: var(--surface-elevated); }
.badge.module.policy { background: #dbeafe; color: #1e40af; }
.badge.module.authority { background: #ede9fe; color: #6d28d9; }
.cluster-summary { display: flex; gap: 1rem; font-size: 0.8rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.75rem; }
.badge.success { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.failure { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.partial { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.badge.module { background: var(--color-surface-elevated); }
.badge.module.policy { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.module.authority { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.cluster-summary { display: flex; gap: 1rem; font-size: 0.8rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.cluster-root { font-size: 0.85rem; margin-bottom: 0.5rem; }
.cluster-time { font-size: 0.75rem; color: var(--text-tertiary); font-family: monospace; }
.cluster-detail { background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; }
.cluster-time { font-size: 0.75rem; color: var(--color-text-muted); font-family: monospace; }
.cluster-detail { background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.5rem; }
.detail-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; }
.detail-header h2 { margin: 0; font-size: 1.1rem; }
.btn-secondary { padding: 0.5rem 1rem; background: var(--surface-elevated); border: 1px solid var(--border); border-radius: 4px; cursor: pointer; }
.cluster-meta { display: flex; gap: 1.5rem; margin-bottom: 1.5rem; font-size: 0.85rem; color: var(--text-secondary); }
.btn-secondary { padding: 0.5rem 1rem; background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); cursor: pointer; }
.cluster-meta { display: flex; gap: 1.5rem; margin-bottom: 1.5rem; font-size: 0.85rem; color: var(--color-text-secondary); }
.root-event, .related-events { margin-bottom: 1.5rem; }
.root-event h3, .related-events h3 { margin: 0 0 0.75rem; font-size: 0.95rem; }
.event-card { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem; background: var(--surface-elevated); border-radius: 4px; cursor: pointer; margin-bottom: 0.5rem; }
.event-card:hover { background: #eff6ff; }
.event-card { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem; background: var(--color-surface-elevated); border-radius: var(--radius-sm); cursor: pointer; margin-bottom: 0.5rem; }
.event-card:hover { background: var(--color-status-info-bg); }
.desc { flex: 1; font-size: 0.85rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.time { font-size: 0.75rem; color: var(--text-tertiary); font-family: monospace; }
.time { font-size: 0.75rem; color: var(--color-text-muted); font-family: monospace; }
`]
})
export class AuditCorrelationsComponent implements OnInit {

View File

@@ -166,58 +166,58 @@ import { AuditEvent, AuditCorrelationCluster } from '../../core/api/audit-log.mo
styles: [`
.event-detail-page { padding: 1.5rem; max-width: 1200px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
h1 { margin: 0; }
.event-card { background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.event-header { display: flex; align-items: center; gap: 0.75rem; padding: 1rem; background: var(--surface-elevated); border-bottom: 1px solid var(--border); }
.timestamp { margin-left: auto; font-size: 0.85rem; color: var(--text-secondary); font-family: monospace; }
.event-card { background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); overflow: hidden; }
.event-header { display: flex; align-items: center; gap: 0.75rem; padding: 1rem; background: var(--color-surface-elevated); border-bottom: 1px solid var(--color-border-primary); }
.timestamp { margin-left: auto; font-size: 0.85rem; color: var(--color-text-secondary); font-family: monospace; }
.event-body { padding: 1.5rem; }
.detail-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
.detail-item { display: flex; flex-direction: column; gap: 0.25rem; }
.label { font-size: 0.75rem; color: var(--text-secondary); text-transform: uppercase; }
.label { font-size: 0.75rem; color: var(--color-text-secondary); text-transform: uppercase; }
.value { font-size: 0.9rem; }
.mono { font-family: monospace; font-size: 0.85rem; }
.link { color: var(--primary); text-decoration: none; }
.link { color: var(--color-brand-primary); text-decoration: none; }
.description-section, .tags-section, .details-section, .diff-section { margin-bottom: 1.5rem; }
.description-section h3, .tags-section h3, .details-section h3, .diff-section h3 { margin: 0 0 0.75rem; font-size: 1rem; }
.description-section p { margin: 0; }
.tags { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.tag { display: inline-block; background: var(--surface-elevated); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; }
.json-block { background: var(--surface-elevated); padding: 1rem; border-radius: 4px; font-size: 0.8rem; overflow-x: auto; max-height: 300px; margin: 0; }
.badge { display: inline-block; padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.75rem; text-transform: uppercase; }
.badge.module { background: var(--surface-elevated); }
.badge.module.policy { background: #dbeafe; color: #1e40af; }
.badge.module.authority { background: #ede9fe; color: #6d28d9; }
.badge.module.vex { background: #dcfce7; color: #166534; }
.badge.action { background: var(--surface-elevated); }
.badge.action.create { background: #dcfce7; color: #166534; }
.badge.action.update { background: #dbeafe; color: #1e40af; }
.badge.action.delete { background: #fee2e2; color: #991b1b; }
.badge.severity.info { background: #dbeafe; color: #1e40af; }
.badge.severity.warning { background: #fef3c7; color: #92400e; }
.badge.severity.error { background: #fee2e2; color: #991b1b; }
.badge.success { background: #dcfce7; color: #166534; }
.badge.failure { background: #fee2e2; color: #991b1b; }
.badge.partial { background: #fef3c7; color: #92400e; }
.tag { display: inline-block; background: var(--color-surface-elevated); padding: 0.25rem 0.5rem; border-radius: var(--radius-sm); font-size: 0.8rem; }
.json-block { background: var(--color-surface-elevated); padding: 1rem; border-radius: var(--radius-sm); font-size: 0.8rem; overflow-x: auto; max-height: 300px; margin: 0; }
.badge { display: inline-block; padding: 0.2rem 0.5rem; border-radius: var(--radius-sm); font-size: 0.75rem; text-transform: uppercase; }
.badge.module { background: var(--color-surface-elevated); }
.badge.module.policy { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.module.authority { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.badge.module.vex { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.action { background: var(--color-surface-elevated); }
.badge.action.create { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.action.update { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.action.delete { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.severity.info { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.severity.warning { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.badge.severity.error { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.success { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.failure { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.partial { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.diff-container { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.diff-pane { background: var(--surface-elevated); border-radius: 4px; overflow: hidden; }
.diff-pane h4 { margin: 0; padding: 0.5rem 0.75rem; font-size: 0.85rem; border-bottom: 1px solid var(--border); }
.diff-pane.before h4 { background: #fef2f2; color: #991b1b; }
.diff-pane.after h4 { background: #dcfce7; color: #166534; }
.diff-pane { background: var(--color-surface-elevated); border-radius: var(--radius-sm); overflow: hidden; }
.diff-pane h4 { margin: 0; padding: 0.5rem 0.75rem; font-size: 0.85rem; border-bottom: 1px solid var(--color-border-primary); }
.diff-pane.before h4 { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.diff-pane.after h4 { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.diff-pane pre { margin: 0; padding: 0.75rem; font-size: 0.75rem; max-height: 300px; overflow: auto; }
.changed-fields { margin-top: 1rem; font-size: 0.85rem; }
.field-badge { display: inline-block; background: #fef3c7; color: #92400e; padding: 0.15rem 0.4rem; border-radius: 4px; margin-left: 0.5rem; font-size: 0.75rem; }
.field-badge { display: inline-block; background: var(--color-status-warning-bg); color: var(--color-status-warning-text); padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); margin-left: 0.5rem; font-size: 0.75rem; }
.correlation-section { margin-top: 2rem; }
.correlation-section h2 { margin: 0 0 1rem; font-size: 1.1rem; }
.correlation-summary { display: flex; gap: 1.5rem; align-items: center; margin-bottom: 1rem; font-size: 0.85rem; color: var(--text-secondary); }
.related-events-table { width: 100%; border-collapse: collapse; background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; }
.related-events-table th, .related-events-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--border); }
.related-events-table th { background: var(--surface-elevated); font-weight: 600; font-size: 0.85rem; }
.correlation-summary { display: flex; gap: 1.5rem; align-items: center; margin-bottom: 1rem; font-size: 0.85rem; color: var(--color-text-secondary); }
.related-events-table { width: 100%; border-collapse: collapse; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); }
.related-events-table th, .related-events-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--color-border-primary); }
.related-events-table th { background: var(--color-surface-elevated); font-weight: var(--font-weight-semibold); font-size: 0.85rem; }
.related-events-table tr { cursor: pointer; }
.related-events-table tr:hover { background: var(--surface-elevated); }
.related-events-table tr.current { background: #eff6ff; }
.loading { text-align: center; padding: 3rem; color: var(--text-secondary); }
.related-events-table tr:hover { background: var(--color-surface-elevated); }
.related-events-table tr.current { background: var(--color-status-info-bg); }
.loading { text-align: center; padding: 3rem; color: var(--color-text-secondary); }
`]
})
export class AuditEventDetailComponent implements OnInit {

View File

@@ -166,49 +166,49 @@ import { AuditExportRequest, AuditExportResponse, AuditLogFilters, AuditModule,
styles: [`
.export-page { padding: 1.5rem; max-width: 900px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
h1 { margin: 0 0 0.25rem; }
.description { color: var(--text-secondary); margin: 0; }
.export-config { background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; }
.description { color: var(--color-text-secondary); margin: 0; }
.export-config { background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.5rem; margin-bottom: 2rem; }
.config-section { margin-bottom: 1.5rem; }
.config-section:last-of-type { margin-bottom: 0; }
.config-section h3 { margin: 0 0 0.75rem; font-size: 1rem; }
.date-range { display: flex; gap: 1.5rem; }
.field { display: flex; flex-direction: column; gap: 0.25rem; }
.field label { font-size: 0.8rem; color: var(--text-secondary); }
.field input, .field select { padding: 0.5rem; border: 1px solid var(--border); border-radius: 4px; }
.field label { font-size: 0.8rem; color: var(--color-text-secondary); }
.field input, .field select { padding: 0.5rem; border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); }
.field select[multiple] { height: 100px; }
.filter-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
.format-options { display: flex; flex-direction: column; gap: 0.75rem; }
.radio { display: flex; align-items: flex-start; gap: 0.5rem; cursor: pointer; padding: 0.5rem; border: 1px solid var(--border); border-radius: 4px; }
.radio:has(input:checked) { border-color: var(--primary); background: #eff6ff; }
.radio { display: flex; align-items: flex-start; gap: 0.5rem; cursor: pointer; padding: 0.5rem; border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); }
.radio:has(input:checked) { border-color: var(--color-brand-primary); background: var(--color-status-info-bg); }
.radio input { margin-top: 0.25rem; }
.radio-label { font-weight: 600; }
.radio-desc { font-size: 0.8rem; color: var(--text-secondary); margin-left: 0.5rem; }
.radio-label { font-weight: var(--font-weight-semibold); }
.radio-desc { font-size: 0.8rem; color: var(--color-text-secondary); margin-left: 0.5rem; }
.options { display: flex; flex-direction: column; gap: 0.5rem; }
.checkbox { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; }
.actions { margin-top: 1.5rem; }
.btn-primary { background: var(--primary); color: var(--color-text-heading); border: none; padding: 0.75rem 1.5rem; border-radius: 4px; cursor: pointer; font-size: 1rem; }
.btn-primary { background: var(--color-brand-primary); color: var(--color-text-heading); border: none; padding: 0.75rem 1.5rem; border-radius: var(--radius-sm); cursor: pointer; font-size: 1rem; }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.exports-list { margin-bottom: 2rem; }
.exports-list h2 { margin: 0 0 1rem; font-size: 1.1rem; }
.exports-table { width: 100%; border-collapse: collapse; background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; }
.exports-table th, .exports-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--border); }
.exports-table th { background: var(--surface-elevated); font-weight: 600; font-size: 0.85rem; }
.exports-table { width: 100%; border-collapse: collapse; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); }
.exports-table th, .exports-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--color-border-primary); }
.exports-table th { background: var(--color-surface-elevated); font-weight: var(--font-weight-semibold); font-size: 0.85rem; }
.mono { font-family: monospace; font-size: 0.8rem; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.75rem; }
.badge.pending { background: #fef3c7; color: #92400e; }
.badge.processing { background: #dbeafe; color: #1e40af; }
.badge.completed { background: #dcfce7; color: #166534; }
.badge.failed { background: #fee2e2; color: #991b1b; }
.btn-sm { display: inline-block; padding: 0.35rem 0.75rem; background: var(--primary); color: var(--color-text-heading); border-radius: 4px; text-decoration: none; font-size: 0.8rem; }
.btn-xs { padding: 0.25rem 0.5rem; font-size: 0.75rem; cursor: pointer; background: var(--surface-elevated); border: 1px solid var(--border); border-radius: 4px; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.75rem; }
.badge.pending { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.badge.processing { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.completed { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.failed { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.btn-sm { display: inline-block; padding: 0.35rem 0.75rem; background: var(--color-brand-primary); color: var(--color-text-heading); border-radius: var(--radius-sm); text-decoration: none; font-size: 0.8rem; }
.btn-xs { padding: 0.25rem 0.5rem; font-size: 0.75rem; cursor: pointer; background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); }
.compliance-info h2 { margin: 0 0 1rem; font-size: 1.1rem; }
.info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; }
.info-card { background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; }
.info-card { background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; }
.info-card h4 { margin: 0 0 0.5rem; font-size: 0.95rem; }
.info-card p { margin: 0; font-size: 0.85rem; color: var(--text-secondary); }
.info-card p { margin: 0; font-size: 0.85rem; color: var(--color-text-secondary); }
`]
})
export class AuditExportComponent {

View File

@@ -103,41 +103,41 @@ import { AuditEvent } from '../../core/api/audit-log.models';
styles: [`
.integrations-audit-page { padding: 1.5rem; max-width: 1400px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
h1 { margin: 0 0 0.25rem; }
.description { color: var(--text-secondary); margin: 0; }
.events-table { width: 100%; border-collapse: collapse; background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; }
.events-table th, .events-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--border); }
.events-table th { background: var(--surface-elevated); font-weight: 600; font-size: 0.85rem; }
.description { color: var(--color-text-secondary); margin: 0; }
.events-table { width: 100%; border-collapse: collapse; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); }
.events-table th, .events-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--color-border-primary); }
.events-table th { background: var(--color-surface-elevated); font-weight: var(--font-weight-semibold); font-size: 0.85rem; }
.clickable { cursor: pointer; }
.clickable:hover { background: var(--surface-elevated); }
.clickable.selected { background: #eff6ff; }
.clickable:hover { background: var(--color-surface-elevated); }
.clickable.selected { background: var(--color-status-info-bg); }
.mono { font-family: monospace; font-size: 0.8rem; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.75rem; }
.badge.action { background: var(--surface-elevated); }
.badge.action.create { background: #dcfce7; color: #166534; }
.badge.action.update { background: #dbeafe; color: #1e40af; }
.badge.action.delete { background: #fee2e2; color: #991b1b; }
.badge.action.test { background: #fef3c7; color: #92400e; }
.badge.status { background: var(--surface-elevated); }
.badge.status.connected { background: #dcfce7; color: #166534; }
.badge.status.disconnected { background: #fee2e2; color: #991b1b; }
.badge.status.error { background: #fee2e2; color: #991b1b; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.75rem; }
.badge.action { background: var(--color-surface-elevated); }
.badge.action.create { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.action.update { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.action.delete { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.action.test { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.badge.status { background: var(--color-surface-elevated); }
.badge.status.connected { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.status.disconnected { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.status.error { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.changed-fields { max-width: 200px; }
.field-badge { display: inline-block; background: #fef3c7; color: #92400e; padding: 0.1rem 0.3rem; border-radius: 4px; font-size: 0.7rem; margin-right: 0.25rem; }
.more { font-size: 0.7rem; color: var(--text-tertiary); }
.diff-viewer { margin-top: 1.5rem; background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.diff-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--border); }
.field-badge { display: inline-block; background: var(--color-status-warning-bg); color: var(--color-status-warning-text); padding: 0.1rem 0.3rem; border-radius: var(--radius-sm); font-size: 0.7rem; margin-right: 0.25rem; }
.more { font-size: 0.7rem; color: var(--color-text-muted); }
.diff-viewer { margin-top: 1.5rem; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); overflow: hidden; }
.diff-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--color-border-primary); }
.diff-header h3 { margin: 0; }
.close-btn { background: none; border: none; font-size: 1.5rem; cursor: pointer; }
.diff-meta { padding: 0.75rem 1rem; background: var(--surface-elevated); font-size: 0.85rem; display: flex; justify-content: space-between; border-bottom: 1px solid var(--border); }
.diff-meta { padding: 0.75rem 1rem; background: var(--color-surface-elevated); font-size: 0.85rem; display: flex; justify-content: space-between; border-bottom: 1px solid var(--color-border-primary); }
.diff-container { display: grid; grid-template-columns: 1fr 1fr; }
.diff-pane { overflow: hidden; }
.diff-pane h4 { margin: 0; padding: 0.5rem 1rem; font-size: 0.85rem; border-bottom: 1px solid var(--border); }
.diff-pane.before h4 { background: #fef2f2; color: #991b1b; }
.diff-pane.after h4 { background: #dcfce7; color: #166534; }
.diff-pane pre { margin: 0; padding: 1rem; font-size: 0.75rem; max-height: 400px; overflow: auto; background: var(--surface-elevated); }
.diff-pane h4 { margin: 0; padding: 0.5rem 1rem; font-size: 0.85rem; border-bottom: 1px solid var(--color-border-primary); }
.diff-pane.before h4 { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.diff-pane.after h4 { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.diff-pane pre { margin: 0; padding: 1rem; font-size: 0.75rem; max-height: 400px; overflow: auto; background: var(--color-surface-elevated); }
.pagination { display: flex; justify-content: center; margin-top: 1rem; }
.pagination button { padding: 0.5rem 1rem; cursor: pointer; }
.pagination button:disabled { opacity: 0.5; cursor: not-allowed; }

View File

@@ -130,62 +130,62 @@ import { AuditStatsSummary, AuditEvent, AuditAnomalyAlert, AuditModule } from '.
.audit-dashboard { padding: 1.5rem; max-width: 1400px; margin: 0 auto; }
.page-header { margin-bottom: 2rem; }
.page-header h1 { margin: 0 0 0.25rem; }
.description { color: var(--text-secondary); margin: 0 0 1rem; }
.description { color: var(--color-text-secondary); margin: 0 0 1rem; }
.header-actions { display: flex; gap: 0.75rem; }
.btn-primary { background: var(--primary); color: var(--color-text-heading); border: none; padding: 0.5rem 1rem; border-radius: 4px; text-decoration: none; }
.btn-secondary { background: var(--surface-elevated); border: 1px solid var(--border); padding: 0.5rem 1rem; border-radius: 4px; text-decoration: none; color: inherit; }
.btn-primary { background: var(--color-brand-primary); color: var(--color-text-heading); border: none; padding: 0.5rem 1rem; border-radius: var(--radius-sm); text-decoration: none; }
.btn-secondary { background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); padding: 0.5rem 1rem; border-radius: var(--radius-sm); text-decoration: none; color: inherit; }
.stats-strip { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 2rem; }
.stat-card { background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem 1.5rem; min-width: 120px; text-align: center; }
.stat-value { display: block; font-size: 1.75rem; font-weight: 700; }
.stat-label { font-size: 0.8rem; color: var(--text-secondary); }
.stat-card.policy { border-left: 4px solid #3b82f6; }
.stat-card.authority { border-left: 4px solid #8b5cf6; }
.stat-card.vex { border-left: 4px solid #10b981; }
.stat-card.integrations { border-left: 4px solid #f59e0b; }
.stat-card { background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem 1.5rem; min-width: 120px; text-align: center; }
.stat-value { display: block; font-size: 1.75rem; font-weight: var(--font-weight-bold); }
.stat-label { font-size: 0.8rem; color: var(--color-text-secondary); }
.stat-card.policy { border-left: 4px solid var(--color-status-info); }
.stat-card.authority { border-left: 4px solid var(--color-status-excepted); }
.stat-card.vex { border-left: 4px solid var(--color-status-success); }
.stat-card.integrations { border-left: 4px solid var(--color-status-warning); }
.stat-card.orchestrator { border-left: 4px solid var(--color-brand-secondary); }
.anomaly-alerts { margin-bottom: 2rem; }
.anomaly-alerts h2 { margin: 0 0 1rem; font-size: 1.1rem; }
.alert-list { display: flex; gap: 1rem; flex-wrap: wrap; }
.alert-card { background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; min-width: 280px; flex: 1; }
.alert-card.warning { border-left: 4px solid var(--warning); }
.alert-card.error, .alert-card.critical { border-left: 4px solid var(--error); }
.alert-card { background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; min-width: 280px; flex: 1; }
.alert-card.warning { border-left: 4px solid var(--color-status-warning); }
.alert-card.error, .alert-card.critical { border-left: 4px solid var(--color-status-error); }
.alert-header { display: flex; justify-content: space-between; margin-bottom: 0.5rem; }
.alert-type { font-weight: 600; font-size: 0.9rem; }
.alert-time { font-size: 0.75rem; color: var(--text-tertiary); }
.alert-desc { font-size: 0.85rem; margin: 0 0 0.75rem; color: var(--text-secondary); }
.alert-type { font-weight: var(--font-weight-semibold); font-size: 0.9rem; }
.alert-time { font-size: 0.75rem; color: var(--color-text-muted); }
.alert-desc { font-size: 0.85rem; margin: 0 0 0.75rem; color: var(--color-text-secondary); }
.alert-footer { display: flex; justify-content: space-between; align-items: center; }
.affected { font-size: 0.75rem; color: var(--text-tertiary); }
.btn-sm { padding: 0.25rem 0.5rem; font-size: 0.8rem; cursor: pointer; background: var(--surface-elevated); border: 1px solid var(--border); border-radius: 4px; }
.ack { font-size: 0.75rem; color: var(--text-tertiary); }
.affected { font-size: 0.75rem; color: var(--color-text-muted); }
.btn-sm { padding: 0.25rem 0.5rem; font-size: 0.8rem; cursor: pointer; background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); }
.ack { font-size: 0.75rem; color: var(--color-text-muted); }
.quick-access { margin-bottom: 2rem; }
.quick-access h2 { margin: 0 0 1rem; font-size: 1.1rem; }
.access-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; }
.access-card { background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; text-decoration: none; color: inherit; transition: border-color 0.2s; }
.access-card:hover { border-color: var(--primary); }
.access-card { background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; text-decoration: none; color: inherit; transition: border-color 0.2s; }
.access-card:hover { border-color: var(--color-brand-primary); }
.access-icon { font-size: 1.25rem; display: block; margin-bottom: 0.5rem; }
.access-label { font-weight: 600; display: block; margin-bottom: 0.25rem; }
.access-desc { font-size: 0.8rem; color: var(--text-secondary); }
.recent-events { background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.section-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--border); }
.access-label { font-weight: var(--font-weight-semibold); display: block; margin-bottom: 0.25rem; }
.access-desc { font-size: 0.8rem; color: var(--color-text-secondary); }
.recent-events { background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); overflow: hidden; }
.section-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--color-border-primary); }
.section-header h2 { margin: 0; font-size: 1rem; }
.link { font-size: 0.85rem; color: var(--primary); text-decoration: none; }
.link { font-size: 0.85rem; color: var(--color-brand-primary); text-decoration: none; }
.events-table { width: 100%; border-collapse: collapse; }
.events-table th, .events-table td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid var(--border); }
.events-table th { background: var(--surface-elevated); font-weight: 600; font-size: 0.85rem; }
.events-table th, .events-table td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid var(--color-border-primary); }
.events-table th { background: var(--color-surface-elevated); font-weight: var(--font-weight-semibold); font-size: 0.85rem; }
.mono { font-family: monospace; font-size: 0.8rem; }
.clickable { cursor: pointer; }
.clickable:hover { background: var(--surface-elevated); }
.badge { display: inline-block; padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.75rem; text-transform: uppercase; }
.badge.module { background: var(--surface-elevated); }
.badge.module.policy { background: #dbeafe; color: #1e40af; }
.badge.module.authority { background: #ede9fe; color: #6d28d9; }
.badge.module.vex { background: #dcfce7; color: #166534; }
.badge.module.integrations { background: #fef3c7; color: #92400e; }
.badge.action { background: var(--surface-elevated); }
.badge.action.create { background: #dcfce7; color: #166534; }
.badge.action.update { background: #dbeafe; color: #1e40af; }
.badge.action.delete { background: #fee2e2; color: #991b1b; }
.badge.action.promote { background: #fef3c7; color: #92400e; }
.clickable:hover { background: var(--color-surface-elevated); }
.badge { display: inline-block; padding: 0.15rem 0.5rem; border-radius: var(--radius-sm); font-size: 0.75rem; text-transform: uppercase; }
.badge.module { background: var(--color-surface-elevated); }
.badge.module.policy { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.module.authority { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.badge.module.vex { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.module.integrations { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.badge.action { background: var(--color-surface-elevated); }
.badge.action.create { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.action.update { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.action.delete { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.action.promote { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.resource { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
`]
})

View File

@@ -240,84 +240,84 @@ import { AuditEvent, AuditLogFilters, AuditModule, AuditAction, AuditSeverity }
styles: [`
.audit-table-page { padding: 1.5rem; max-width: 1600px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
.page-header h1 { margin: 0; }
.filters-bar { background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem; }
.filters-bar { background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; margin-bottom: 1.5rem; }
.filter-row { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.75rem; }
.filter-row:last-child { margin-bottom: 0; }
.filter-group { display: flex; flex-direction: column; gap: 0.25rem; }
.filter-group label { font-size: 0.75rem; color: var(--text-secondary); }
.filter-group select, .filter-group input { padding: 0.5rem; border: 1px solid var(--border); border-radius: 4px; font-size: 0.9rem; }
.filter-group label { font-size: 0.75rem; color: var(--color-text-secondary); }
.filter-group select, .filter-group input { padding: 0.5rem; border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); font-size: 0.9rem; }
.filter-group select[multiple] { height: 80px; }
.search-group { flex: 1; min-width: 200px; flex-direction: row; align-items: flex-end; }
.search-group label { display: none; }
.search-group input { flex: 1; }
.btn-sm { padding: 0.5rem 0.75rem; cursor: pointer; background: var(--primary); color: var(--color-text-heading); border: none; border-radius: 4px; }
.btn-secondary { padding: 0.5rem 1rem; cursor: pointer; background: var(--surface-elevated); border: 1px solid var(--border); border-radius: 4px; align-self: flex-end; }
.loading { text-align: center; padding: 3rem; color: var(--text-secondary); }
.events-table { width: 100%; border-collapse: collapse; background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.events-table th, .events-table td { padding: 0.75rem 0.5rem; text-align: left; border-bottom: 1px solid var(--border); font-size: 0.85rem; }
.events-table th { background: var(--surface-elevated); font-weight: 600; position: sticky; top: 0; }
.btn-sm { padding: 0.5rem 0.75rem; cursor: pointer; background: var(--color-brand-primary); color: var(--color-text-heading); border: none; border-radius: var(--radius-sm); }
.btn-secondary { padding: 0.5rem 1rem; cursor: pointer; background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); align-self: flex-end; }
.loading { text-align: center; padding: 3rem; color: var(--color-text-secondary); }
.events-table { width: 100%; border-collapse: collapse; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); overflow: hidden; }
.events-table th, .events-table td { padding: 0.75rem 0.5rem; text-align: left; border-bottom: 1px solid var(--color-border-primary); font-size: 0.85rem; }
.events-table th { background: var(--color-surface-elevated); font-weight: var(--font-weight-semibold); position: sticky; top: 0; }
.events-table tr { cursor: pointer; }
.events-table tr:hover { background: var(--surface-elevated); }
.events-table tr.selected { background: #eff6ff; }
.events-table tr.error { background: #fef2f2; }
.events-table tr.critical { background: #fef2f2; }
.events-table tr.warning { background: #fffbeb; }
.events-table tr:hover { background: var(--color-surface-elevated); }
.events-table tr.selected { background: var(--color-status-info-bg); }
.events-table tr.error { background: var(--color-status-error-bg); }
.events-table tr.critical { background: var(--color-status-error-bg); }
.events-table tr.warning { background: var(--color-status-warning-bg); }
.mono { font-family: monospace; font-size: 0.8rem; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.7rem; text-transform: uppercase; }
.badge.module { background: var(--surface-elevated); }
.badge.module.policy { background: #dbeafe; color: #1e40af; }
.badge.module.authority { background: #ede9fe; color: #6d28d9; }
.badge.module.vex { background: #dcfce7; color: #166534; }
.badge.module.integrations { background: #fef3c7; color: #92400e; }
.badge.module.orchestrator { background: #e0e7ff; color: #3730a3; }
.badge.module.scanner { background: #fce7f3; color: #9d174d; }
.badge.action { background: var(--surface-elevated); }
.badge.action.create, .badge.action.issue { background: #dcfce7; color: #166534; }
.badge.action.update, .badge.action.refresh { background: #dbeafe; color: #1e40af; }
.badge.action.delete, .badge.action.revoke { background: #fee2e2; color: #991b1b; }
.badge.action.promote, .badge.action.approve { background: #fef3c7; color: #92400e; }
.badge.action.fail, .badge.action.reject { background: #fee2e2; color: #991b1b; }
.badge.severity.info { background: #dbeafe; color: #1e40af; }
.badge.severity.warning { background: #fef3c7; color: #92400e; }
.badge.severity.error { background: #fee2e2; color: #991b1b; }
.badge.severity.critical { background: #7f1d1d; color: white; }
.actor-type { font-size: 0.7rem; color: var(--text-tertiary); }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.7rem; text-transform: uppercase; }
.badge.module { background: var(--color-surface-elevated); }
.badge.module.policy { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.module.authority { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.badge.module.vex { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.module.integrations { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.badge.module.orchestrator { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.badge.module.scanner { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.badge.action { background: var(--color-surface-elevated); }
.badge.action.create, .badge.action.issue { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.action.update, .badge.action.refresh { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.action.delete, .badge.action.revoke { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.action.promote, .badge.action.approve { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.badge.action.fail, .badge.action.reject { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.severity.info { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.severity.warning { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.badge.severity.error { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.severity.critical { background: var(--color-status-error-text); color: white; }
.actor-type { font-size: 0.7rem; color: var(--color-text-muted); }
.resource, .description { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.link { color: var(--primary); text-decoration: none; font-size: 0.8rem; }
.link { color: var(--color-brand-primary); text-decoration: none; font-size: 0.8rem; }
.btn-xs { padding: 0.15rem 0.3rem; font-size: 0.7rem; cursor: pointer; margin-left: 0.5rem; }
.pagination { display: flex; justify-content: center; gap: 1rem; align-items: center; margin-top: 1rem; padding: 1rem; }
.pagination button { padding: 0.5rem 1rem; cursor: pointer; }
.pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
.detail-panel { position: fixed; top: 0; right: 0; width: 400px; height: 100vh; background: var(--surface-card); border-left: 1px solid var(--border); box-shadow: -4px 0 16px rgba(0,0,0,0.1); overflow-y: auto; z-index: 100; }
.panel-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--border); background: var(--surface-elevated); }
.detail-panel { position: fixed; top: 0; right: 0; width: 400px; height: 100vh; background: var(--color-surface-primary); border-left: 1px solid var(--color-border-primary); box-shadow: -4px 0 16px rgba(0,0,0,0.1); overflow-y: auto; z-index: 100; }
.panel-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--color-border-primary); background: var(--color-surface-elevated); }
.panel-header h3 { margin: 0; }
.close-btn { background: none; border: none; font-size: 1.5rem; cursor: pointer; color: var(--text-secondary); }
.close-btn { background: none; border: none; font-size: 1.5rem; cursor: pointer; color: var(--color-text-secondary); }
.panel-content { padding: 1rem; }
.detail-row { display: flex; margin-bottom: 0.75rem; }
.detail-row .label { width: 120px; font-weight: 600; font-size: 0.85rem; color: var(--text-secondary); }
.detail-row .label { width: 120px; font-weight: var(--font-weight-semibold); font-size: 0.85rem; color: var(--color-text-secondary); }
.detail-row .value { flex: 1; font-size: 0.85rem; word-break: break-all; }
.tag { display: inline-block; background: var(--surface-elevated); padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.75rem; margin-right: 0.25rem; }
.tag { display: inline-block; background: var(--color-surface-elevated); padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.75rem; margin-right: 0.25rem; }
.detail-section { margin-top: 1rem; }
.detail-section h4 { margin: 0 0 0.5rem; font-size: 0.9rem; }
.json-block { background: var(--surface-elevated); padding: 0.75rem; border-radius: 4px; font-size: 0.75rem; overflow-x: auto; max-height: 200px; }
.btn-primary { background: var(--primary); color: var(--color-text-heading); border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; margin-top: 1rem; }
.json-block { background: var(--color-surface-elevated); padding: 0.75rem; border-radius: var(--radius-sm); font-size: 0.75rem; overflow-x: auto; max-height: 200px; }
.btn-primary { background: var(--color-brand-primary); color: var(--color-text-heading); border: none; padding: 0.5rem 1rem; border-radius: var(--radius-sm); cursor: pointer; margin-top: 1rem; }
.diff-modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 200; }
.diff-modal { background: var(--surface-card); border-radius: 8px; width: 90%; max-width: 1000px; max-height: 80vh; overflow: hidden; display: flex; flex-direction: column; }
.modal-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--border); }
.diff-modal { background: var(--color-surface-primary); border-radius: var(--radius-lg); width: 90%; max-width: 1000px; max-height: 80vh; overflow: hidden; display: flex; flex-direction: column; }
.modal-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--color-border-primary); }
.modal-header h3 { margin: 0; }
.modal-content { padding: 1rem; overflow-y: auto; flex: 1; }
.diff-meta { display: flex; justify-content: space-between; margin-bottom: 1rem; font-size: 0.85rem; color: var(--text-secondary); }
.diff-meta { display: flex; justify-content: space-between; margin-bottom: 1rem; font-size: 0.85rem; color: var(--color-text-secondary); }
.diff-container { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.diff-pane { background: var(--surface-elevated); border-radius: 4px; overflow: hidden; }
.diff-pane h4 { margin: 0; padding: 0.5rem 0.75rem; background: var(--surface-card); border-bottom: 1px solid var(--border); font-size: 0.85rem; }
.diff-pane.before h4 { background: #fef2f2; color: #991b1b; }
.diff-pane.after h4 { background: #dcfce7; color: #166534; }
.diff-pane { background: var(--color-surface-elevated); border-radius: var(--radius-sm); overflow: hidden; }
.diff-pane h4 { margin: 0; padding: 0.5rem 0.75rem; background: var(--color-surface-primary); border-bottom: 1px solid var(--color-border-primary); font-size: 0.85rem; }
.diff-pane.before h4 { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.diff-pane.after h4 { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.diff-pane pre { margin: 0; padding: 0.75rem; font-size: 0.75rem; max-height: 400px; overflow: auto; }
.changed-fields { margin-top: 1rem; font-size: 0.85rem; }
.field-badge { display: inline-block; background: #fef3c7; color: #92400e; padding: 0.15rem 0.4rem; border-radius: 4px; margin-left: 0.5rem; font-size: 0.75rem; }
.field-badge { display: inline-block; background: var(--color-status-warning-bg); color: var(--color-status-warning-text); padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); margin-left: 0.5rem; font-size: 0.75rem; }
`]
})
export class AuditLogTableComponent implements OnInit {

View File

@@ -79,28 +79,28 @@ import { AuditEvent } from '../../core/api/audit-log.models';
styles: [`
.policy-audit-page { padding: 1.5rem; max-width: 1400px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
h1 { margin: 0 0 0.25rem; }
.description { color: var(--text-secondary); margin: 0; }
.description { color: var(--color-text-secondary); margin: 0; }
.event-categories { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; }
.event-categories button { padding: 0.5rem 1rem; background: var(--surface-card); border: 1px solid var(--border); border-radius: 4px; cursor: pointer; }
.event-categories button.active { background: var(--primary); color: var(--color-text-heading); border-color: var(--primary); }
.events-table { width: 100%; border-collapse: collapse; background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; }
.events-table th, .events-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--border); }
.events-table th { background: var(--surface-elevated); font-weight: 600; font-size: 0.85rem; }
.event-categories button { padding: 0.5rem 1rem; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); cursor: pointer; }
.event-categories button.active { background: var(--color-brand-primary); color: var(--color-text-heading); border-color: var(--color-brand-primary); }
.events-table { width: 100%; border-collapse: collapse; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); }
.events-table th, .events-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--color-border-primary); }
.events-table th { background: var(--color-surface-elevated); font-weight: var(--font-weight-semibold); font-size: 0.85rem; }
.clickable { cursor: pointer; }
.clickable:hover { background: var(--surface-elevated); }
.clickable:hover { background: var(--color-surface-elevated); }
.mono { font-family: monospace; font-size: 0.8rem; }
.hash { max-width: 120px; overflow: hidden; text-overflow: ellipsis; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.75rem; }
.badge.action { background: var(--surface-elevated); }
.badge.action.promote { background: #dcfce7; color: #166534; }
.badge.action.approve { background: #dbeafe; color: #1e40af; }
.badge.action.reject { background: #fee2e2; color: #991b1b; }
.badge.shadow { background: var(--surface-elevated); }
.badge.shadow.active { background: #fef3c7; color: #92400e; }
.badge.shadow.completed { background: #dcfce7; color: #166534; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.75rem; }
.badge.action { background: var(--color-surface-elevated); }
.badge.action.promote { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.action.approve { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.action.reject { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.shadow { background: var(--color-surface-elevated); }
.badge.shadow.active { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.badge.shadow.completed { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.pagination { display: flex; justify-content: center; margin-top: 1rem; }
.pagination button { padding: 0.5rem 1rem; cursor: pointer; }
.pagination button:disabled { opacity: 0.5; cursor: not-allowed; }

View File

@@ -67,38 +67,38 @@ import { AuditTimelineEntry } from '../../core/api/audit-log.models';
styles: [`
.timeline-page { padding: 1.5rem; max-width: 1000px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
h1 { margin: 0 0 0.25rem; }
.description { color: var(--text-secondary); margin: 0; }
.search-bar { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; margin-bottom: 2rem; }
.search-bar input[type="text"] { flex: 1; min-width: 250px; padding: 0.75rem; border: 1px solid var(--border); border-radius: 4px; font-size: 1rem; }
.description { color: var(--color-text-secondary); margin: 0; }
.search-bar { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; margin-bottom: 2rem; }
.search-bar input[type="text"] { flex: 1; min-width: 250px; padding: 0.75rem; border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); font-size: 1rem; }
.date-filters { display: flex; align-items: center; gap: 0.5rem; }
.date-filters input { padding: 0.5rem; border: 1px solid var(--border); border-radius: 4px; }
.date-filters span { color: var(--text-secondary); }
.btn-primary { background: var(--primary); color: var(--color-text-heading); border: none; padding: 0.75rem 1.5rem; border-radius: 4px; cursor: pointer; }
.date-filters input { padding: 0.5rem; border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); }
.date-filters span { color: var(--color-text-secondary); }
.btn-primary { background: var(--color-brand-primary); color: var(--color-text-heading); border: none; padding: 0.75rem 1.5rem; border-radius: var(--radius-sm); cursor: pointer; }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.timeline { position: relative; }
.timeline-entry { display: flex; gap: 1rem; margin-bottom: 1.5rem; }
.timeline-marker { display: flex; flex-direction: column; align-items: center; width: 20px; }
.marker-dot { width: 12px; height: 12px; border-radius: 50%; background: var(--primary); border: 2px solid var(--surface-card); z-index: 1; }
.marker-line { width: 2px; flex: 1; background: var(--border); margin-top: 4px; }
.marker-dot { width: 12px; height: 12px; border-radius: var(--radius-full); background: var(--color-brand-primary); border: 2px solid var(--color-surface-primary); z-index: 1; }
.marker-line { width: 2px; flex: 1; background: var(--color-border-primary); margin-top: 4px; }
.timeline-entry:last-child .marker-line { display: none; }
.entry-content { flex: 1; background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; }
.entry-time { font-family: monospace; font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.cluster-badge { display: inline-block; background: var(--surface-elevated); padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.75rem; margin-bottom: 0.5rem; }
.entry-content { flex: 1; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; }
.entry-time { font-family: monospace; font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.cluster-badge { display: inline-block; background: var(--color-surface-elevated); padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.75rem; margin-bottom: 0.5rem; }
.entry-events { display: flex; flex-direction: column; gap: 0.5rem; }
.event-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: var(--surface-elevated); border-radius: 4px; cursor: pointer; transition: background 0.2s; }
.event-item:hover { background: #eff6ff; }
.badge { display: inline-block; padding: 0.1rem 0.35rem; border-radius: 4px; font-size: 0.7rem; text-transform: uppercase; }
.badge.module { background: var(--surface-card); }
.badge.module.policy { background: #dbeafe; color: #1e40af; }
.badge.module.authority { background: #ede9fe; color: #6d28d9; }
.badge.module.vex { background: #dcfce7; color: #166534; }
.badge.action { background: var(--surface-card); }
.actor { font-size: 0.8rem; color: var(--text-secondary); }
.event-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: var(--color-surface-elevated); border-radius: var(--radius-sm); cursor: pointer; transition: background 0.2s; }
.event-item:hover { background: var(--color-status-info-bg); }
.badge { display: inline-block; padding: 0.1rem 0.35rem; border-radius: var(--radius-sm); font-size: 0.7rem; text-transform: uppercase; }
.badge.module { background: var(--color-surface-primary); }
.badge.module.policy { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.module.authority { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); }
.badge.module.vex { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.action { background: var(--color-surface-primary); }
.actor { font-size: 0.8rem; color: var(--color-text-secondary); }
.desc { font-size: 0.85rem; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.no-results { text-align: center; padding: 3rem; color: var(--text-secondary); }
.no-results { text-align: center; padding: 3rem; color: var(--color-text-secondary); }
`]
})
export class AuditTimelineSearchComponent {

View File

@@ -105,44 +105,44 @@ import { AuditEvent } from '../../core/api/audit-log.models';
styles: [`
.vex-audit-page { padding: 1.5rem; max-width: 1400px; margin: 0 auto; }
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--primary); text-decoration: none; }
.breadcrumb { font-size: 0.85rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; }
.breadcrumb a { color: var(--color-brand-primary); text-decoration: none; }
h1 { margin: 0 0 0.25rem; }
.description { color: var(--text-secondary); margin: 0; }
.events-table { width: 100%; border-collapse: collapse; background: var(--surface-card); border: 1px solid var(--border); border-radius: 8px; }
.events-table th, .events-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--border); }
.events-table th { background: var(--surface-elevated); font-weight: 600; font-size: 0.85rem; }
.description { color: var(--color-text-secondary); margin: 0; }
.events-table { width: 100%; border-collapse: collapse; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); }
.events-table th, .events-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid var(--color-border-primary); }
.events-table th { background: var(--color-surface-elevated); font-weight: var(--font-weight-semibold); font-size: 0.85rem; }
.clickable { cursor: pointer; }
.clickable:hover { background: var(--surface-elevated); }
.clickable:hover { background: var(--color-surface-elevated); }
.mono { font-family: monospace; font-size: 0.8rem; }
.vuln-id { color: var(--error); font-weight: 600; }
.vuln-id { color: var(--color-status-error); font-weight: var(--font-weight-semibold); }
.justification { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.75rem; }
.badge.action { background: var(--surface-elevated); }
.badge.action.create { background: #dcfce7; color: #166534; }
.badge.action.update { background: #dbeafe; color: #1e40af; }
.badge.action.reject { background: #fee2e2; color: #991b1b; }
.badge.status { background: var(--surface-elevated); }
.badge.status.not_affected { background: #dcfce7; color: #166534; }
.badge.status.affected { background: #fee2e2; color: #991b1b; }
.badge.status.fixed { background: #dbeafe; color: #1e40af; }
.badge.status.under_investigation { background: #fef3c7; color: #92400e; }
.event-detail-panel { position: fixed; top: 0; right: 0; width: 400px; height: 100vh; background: var(--surface-card); border-left: 1px solid var(--border); box-shadow: -4px 0 16px rgba(0,0,0,0.1); overflow-y: auto; z-index: 100; }
.panel-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--border); }
.badge { display: inline-block; padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.75rem; }
.badge.action { background: var(--color-surface-elevated); }
.badge.action.create { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.action.update { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.action.reject { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.status { background: var(--color-surface-elevated); }
.badge.status.not_affected { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.badge.status.affected { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.badge.status.fixed { background: var(--color-status-info-bg); color: var(--color-status-info-text); }
.badge.status.under_investigation { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); }
.event-detail-panel { position: fixed; top: 0; right: 0; width: 400px; height: 100vh; background: var(--color-surface-primary); border-left: 1px solid var(--color-border-primary); box-shadow: -4px 0 16px rgba(0,0,0,0.1); overflow-y: auto; z-index: 100; }
.panel-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--color-border-primary); }
.panel-header h3 { margin: 0; }
.close-btn { background: none; border: none; font-size: 1.5rem; cursor: pointer; }
.panel-content { padding: 1rem; }
.detail-section { margin-bottom: 1.5rem; }
.detail-section h4 { margin: 0 0 0.75rem; font-size: 0.9rem; }
.evidence-list { margin: 0; padding-left: 1.25rem; font-size: 0.85rem; }
.rejected-claim { display: flex; justify-content: space-between; padding: 0.5rem; background: var(--surface-elevated); border-radius: 4px; margin-bottom: 0.5rem; font-size: 0.85rem; }
.source { font-weight: 600; }
.reason { color: var(--text-secondary); }
.rejected-claim { display: flex; justify-content: space-between; padding: 0.5rem; background: var(--color-surface-elevated); border-radius: var(--radius-sm); margin-bottom: 0.5rem; font-size: 0.85rem; }
.source { font-weight: var(--font-weight-semibold); }
.reason { color: var(--color-text-secondary); }
.votes-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.5rem; }
.vote { padding: 0.5rem; background: var(--surface-elevated); border-radius: 4px; display: flex; justify-content: space-between; font-size: 0.85rem; }
.vote.agree { background: #dcfce7; }
.vote.disagree { background: #fee2e2; }
.vote.abstain { background: #fef3c7; }
.vote { padding: 0.5rem; background: var(--color-surface-elevated); border-radius: var(--radius-sm); display: flex; justify-content: space-between; font-size: 0.85rem; }
.vote.agree { background: var(--color-status-success-bg); }
.vote.disagree { background: var(--color-status-error-bg); }
.vote.abstain { background: var(--color-status-warning-bg); }
.pagination { display: flex; justify-content: center; margin-top: 1rem; }
.pagination button { padding: 0.5rem 1rem; cursor: pointer; }
.pagination button:disabled { opacity: 0.5; cursor: not-allowed; }

View File

@@ -8,30 +8,303 @@ import { AuthorityAuthService } from '../../core/auth/authority-auth.service';
selector: 'app-auth-callback',
imports: [],
template: `
<section class="auth-callback">
@if (state() === 'processing') {
<p>Completing sign-in…</p>
}
@if (state() === 'error') {
<p class="error">
We were unable to complete the sign-in flow. Please try again.
</p>
}
</section>
<div class="auth-callback-backdrop">
<section class="auth-callback-card" role="status" [attr.aria-busy]="state() === 'processing'">
@if (state() === 'processing') {
<!-- Brand icon -->
<div class="brand-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L4 7v6c0 5.25 3.4 10.15 8 11 4.6-.85 8-5.75 8-11V7l-8-5z"/>
<rect x="10" y="10" width="4" height="5" rx="0.5"/>
<path d="M10 10V8.5a2 2 0 1 1 4 0V10"/>
</svg>
</div>
<!-- Spinner -->
<div class="spinner-container" aria-hidden="true">
<div class="spinner"></div>
</div>
<!-- Status text -->
<p class="status-text">Completing sign-in&hellip;</p>
<p class="status-subtext">Securely verifying your credentials</p>
}
@if (state() === 'error') {
<!-- Error icon -->
<div class="error-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="15" y1="9" x2="9" y2="15"/>
<line x1="9" y1="9" x2="15" y2="15"/>
</svg>
</div>
<p class="error-heading">Sign-in failed</p>
<p class="error-message">
We were unable to complete the sign-in flow.
Please check your connection and try again.
</p>
<a class="retry-link" href="/" aria-label="Return to the home page to retry sign-in">
<svg class="retry-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<polyline points="1 4 1 10 7 10"/>
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
</svg>
Try again
</a>
}
</section>
</div>
`,
styles: [
`
.auth-callback {
margin: 4rem auto;
max-width: 420px;
text-align: center;
font-size: 1rem;
color: var(--color-text-heading);
/* ------------------------------------------------------------------ */
/* Keyframes */
/* ------------------------------------------------------------------ */
@keyframes spin {
to { transform: rotate(360deg); }
}
.error {
color: #dc2626;
font-weight: 500;
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
@keyframes cardEntrance {
from {
opacity: 0;
transform: translateY(12px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* ------------------------------------------------------------------ */
/* Backdrop (full viewport) */
/* ------------------------------------------------------------------ */
.auth-callback-backdrop {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
min-height: 100dvh;
padding: var(--space-4);
background:
radial-gradient(
ellipse 80% 60% at 50% 40%,
var(--color-brand-soft) 0%,
transparent 70%
),
var(--color-surface-primary);
font-family: var(--font-family-base);
}
/* ------------------------------------------------------------------ */
/* Card */
/* ------------------------------------------------------------------ */
.auth-callback-card {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 400px;
padding: var(--space-10) var(--space-8) var(--space-8);
background: var(--color-surface-elevated);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg), var(--shadow-brand-sm);
text-align: center;
animation: cardEntrance 500ms var(--motion-ease-entrance) both;
}
/* ------------------------------------------------------------------ */
/* Brand icon (shield/lock) */
/* ------------------------------------------------------------------ */
.brand-icon {
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
margin-bottom: var(--space-6);
border-radius: var(--radius-xl);
background: var(--color-brand-light);
color: var(--color-brand-primary);
animation: fadeInUp 600ms var(--motion-ease-entrance) both;
}
.brand-icon svg {
width: 28px;
height: 28px;
}
/* ------------------------------------------------------------------ */
/* Spinner */
/* ------------------------------------------------------------------ */
.spinner-container {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: var(--space-5);
animation: fadeInUp 600ms var(--motion-ease-entrance) 100ms both;
}
.spinner {
width: 36px;
height: 36px;
border: 3px solid var(--color-border-primary);
border-top-color: var(--color-brand-primary);
border-radius: var(--radius-full);
animation: spin 0.85s linear infinite;
}
/* ------------------------------------------------------------------ */
/* Status text (processing state) */
/* ------------------------------------------------------------------ */
.status-text {
margin: 0 0 var(--space-1-5) 0;
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
line-height: var(--line-height-snug);
color: var(--color-text-heading);
animation: fadeInUp 600ms var(--motion-ease-entrance) 200ms both;
}
.status-subtext {
margin: 0;
font-size: var(--font-size-base);
font-weight: var(--font-weight-normal);
line-height: var(--line-height-base);
color: var(--color-text-muted);
animation: pulse 2.4s ease-in-out infinite;
animation-delay: 800ms;
}
/* ------------------------------------------------------------------ */
/* Error state */
/* ------------------------------------------------------------------ */
.error-icon {
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
margin-bottom: var(--space-5);
border-radius: var(--radius-full);
background: var(--color-status-error-bg);
color: var(--color-status-error);
animation: fadeInUp 500ms var(--motion-ease-entrance) both;
}
.error-icon svg {
width: 28px;
height: 28px;
}
.error-heading {
margin: 0 0 var(--space-2) 0;
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
line-height: var(--line-height-snug);
color: var(--color-text-heading);
animation: fadeInUp 500ms var(--motion-ease-entrance) 80ms both;
}
.error-message {
margin: 0 0 var(--space-6) 0;
font-size: var(--font-size-base);
font-weight: var(--font-weight-normal);
line-height: var(--line-height-relaxed);
color: var(--color-text-secondary);
max-width: 300px;
animation: fadeInUp 500ms var(--motion-ease-entrance) 160ms both;
}
/* ------------------------------------------------------------------ */
/* Retry link */
/* ------------------------------------------------------------------ */
.retry-link {
display: inline-flex;
align-items: center;
gap: var(--space-1-5);
padding: var(--space-2) var(--space-5);
font-family: var(--font-family-base);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
line-height: var(--line-height-base);
color: var(--color-brand-primary);
text-decoration: none;
border: 1px solid var(--color-border-emphasis);
border-radius: var(--radius-lg);
background: transparent;
cursor: pointer;
transition:
background-color var(--motion-duration-sm) var(--motion-ease-standard),
border-color var(--motion-duration-sm) var(--motion-ease-standard),
color var(--motion-duration-sm) var(--motion-ease-standard),
box-shadow var(--motion-duration-sm) var(--motion-ease-standard);
animation: fadeInUp 500ms var(--motion-ease-entrance) 240ms both;
}
.retry-link:hover {
background: var(--color-brand-light);
border-color: var(--color-brand-primary);
box-shadow: var(--shadow-brand-sm);
}
.retry-link:focus-visible {
outline: 2px solid var(--color-focus-ring);
outline-offset: 2px;
}
.retry-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
/* ------------------------------------------------------------------ */
/* Reduced motion */
/* ------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
.auth-callback-card,
.brand-icon,
.spinner-container,
.status-text,
.status-subtext,
.error-icon,
.error-heading,
.error-message,
.retry-link {
animation: none;
}
.spinner {
animation: spin 1.6s linear infinite;
}
.status-subtext {
animation: none;
opacity: 1;
}
}
`,
]

View File

@@ -638,7 +638,7 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
.binidx-ops__title {
margin: 0 0 0.25rem 0;
font-size: 1.5rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: rgba(212, 201, 168, 0.3);
}
@@ -656,15 +656,15 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
.status-badge {
padding: 0.375rem 0.75rem;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
}
.status-badge--healthy { background: #14532d; color: #86efac; }
.status-badge--degraded { background: #713f12; color: #fde047; }
.status-badge--unhealthy { background: #450a0a; color: #fca5a5; }
.status-badge--healthy { background: var(--color-status-success-text); color: var(--color-status-success-border); }
.status-badge--degraded { background: var(--color-status-warning-text); color: var(--color-status-warning-border); }
.status-badge--unhealthy { background: var(--color-status-error-text); color: var(--color-status-error-border); }
.status-badge--unknown { background: var(--color-text-primary); color: var(--color-text-muted); }
.status-timestamp {
@@ -695,8 +695,8 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
}
.binidx-ops__tab--active {
color: #3b82f6;
border-bottom-color: #3b82f6;
color: var(--color-status-info);
border-bottom-color: var(--color-status-info);
}
.binidx-ops__content {
@@ -715,23 +715,23 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
justify-content: center;
gap: 0.75rem;
padding: 2rem;
background: #450a0a;
border: 1px solid #ef4444;
border-radius: 4px;
color: #fca5a5;
background: var(--color-status-error-text);
border: 1px solid var(--color-status-error);
border-radius: var(--radius-sm);
color: var(--color-status-error-border);
}
.error-icon {
font-family: ui-monospace, monospace;
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
.retry-button {
padding: 0.375rem 0.75rem;
background: transparent;
border: 1px solid #ef4444;
border-radius: 4px;
color: #fca5a5;
border: 1px solid var(--color-status-error);
border-radius: var(--radius-sm);
color: var(--color-status-error-border);
cursor: pointer;
}
@@ -751,7 +751,7 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
.section-title {
margin: 1.5rem 0 1rem 0;
font-size: 1rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: rgba(212, 201, 168, 0.3);
}
@@ -773,16 +773,16 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
padding: 1rem;
background: var(--color-text-primary);
border: 1px solid var(--color-text-primary);
border-radius: 4px;
border-radius: var(--radius-sm);
}
.lifter-card--warm {
border-color: #22c55e;
border-color: var(--color-status-success);
}
.lifter-isa {
font-family: ui-monospace, monospace;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: rgba(212, 201, 168, 0.3);
}
@@ -808,7 +808,7 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
}
.health-table th, .bench-table th {
font-weight: 500;
font-weight: var(--font-weight-medium);
color: var(--color-text-muted);
background: var(--color-text-heading);
}
@@ -818,10 +818,10 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
gap: 2rem;
padding: 1rem;
background: var(--color-text-primary);
border-radius: 4px;
border-radius: var(--radius-sm);
}
.cache-connected--yes { color: #4ade80; }
.cache-connected--yes { color: var(--color-status-success-border); }
/* Bench Tab */
.bench-controls {
@@ -833,16 +833,16 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
.bench-button {
padding: 0.625rem 1.25rem;
background: #3b82f6;
background: var(--color-status-info);
border: none;
border-radius: 4px;
border-radius: var(--radius-sm);
color: white;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
}
.bench-button:hover:not(:disabled) {
background: #2563eb;
background: var(--color-status-info-text);
}
.bench-button:disabled {
@@ -867,7 +867,7 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
align-items: center;
padding: 1rem;
background: var(--color-text-primary);
border-radius: 4px;
border-radius: var(--radius-sm);
}
.latency-label {
@@ -878,12 +878,12 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
.latency-value {
font-size: 1.25rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: rgba(212, 201, 168, 0.3);
}
.status--success { color: #4ade80; }
.status--failure { color: #f87171; }
.status--success { color: var(--color-status-success-border); }
.status--failure { color: var(--color-status-error-border); }
.bench-meta {
display: flex;
@@ -905,7 +905,7 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
flex-direction: column;
padding: 1rem;
background: var(--color-text-primary);
border-radius: 4px;
border-radius: var(--radius-sm);
}
.cache-label {
@@ -915,7 +915,7 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
}
.cache-value {
font-weight: 500;
font-weight: var(--font-weight-medium);
color: rgba(212, 201, 168, 0.3);
}
@@ -931,17 +931,17 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
align-items: center;
padding: 1.25rem;
background: var(--color-text-primary);
border-radius: 4px;
border-radius: var(--radius-sm);
}
.stat-card--primary {
background: #1e3a5f;
border: 1px solid #3b82f6;
background: var(--color-status-info-text);
border: 1px solid var(--color-status-info);
}
.stat-value {
font-size: 1.5rem;
font-weight: 600;
font-weight: var(--font-weight-semibold);
color: rgba(212, 201, 168, 0.3);
}
@@ -957,10 +957,10 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: #0c4a6e;
border: 1px solid #0ea5e9;
border-radius: 4px;
color: #7dd3fc;
background: var(--color-status-info-text);
border: 1px solid var(--color-status-info);
border-radius: var(--radius-sm);
color: var(--color-status-info-border);
font-size: 0.875rem;
margin-bottom: 1.5rem;
}
@@ -971,7 +971,7 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
.config-table {
background: var(--color-text-primary);
border-radius: 4px;
border-radius: var(--radius-sm);
overflow: hidden;
}
@@ -1028,7 +1028,7 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
padding: 0.625rem 0.875rem;
background: var(--color-text-primary);
border: 1px solid var(--color-text-primary);
border-radius: 4px;
border-radius: var(--radius-sm);
color: rgba(212, 201, 168, 0.3);
font-family: ui-monospace, monospace;
font-size: 0.875rem;
@@ -1036,14 +1036,14 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
.fingerprint-input:focus {
outline: none;
border-color: #3b82f6;
border-color: var(--color-status-info);
}
.fingerprint-select {
padding: 0.625rem 0.875rem;
background: var(--color-text-primary);
border: 1px solid var(--color-text-primary);
border-radius: 4px;
border-radius: var(--radius-sm);
color: rgba(212, 201, 168, 0.3);
font-size: 0.875rem;
cursor: pointer;
@@ -1051,17 +1051,17 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
.export-button {
padding: 0.625rem 1.25rem;
background: #3b82f6;
background: var(--color-status-info);
border: none;
border-radius: 4px;
border-radius: var(--radius-sm);
color: white;
font-weight: 500;
font-weight: var(--font-weight-medium);
cursor: pointer;
white-space: nowrap;
}
.export-button:hover:not(:disabled) {
background: #2563eb;
background: var(--color-status-info-text);
}
.export-button:disabled {
@@ -1074,10 +1074,10 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: #450a0a;
border: 1px solid #ef4444;
border-radius: 4px;
color: #fca5a5;
background: var(--color-status-error-text);
border: 1px solid var(--color-status-error);
border-radius: var(--radius-sm);
color: var(--color-status-error-border);
margin-bottom: 1.5rem;
}
@@ -1093,7 +1093,7 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
flex-direction: column;
padding: 1rem;
background: var(--color-text-primary);
border-radius: 4px;
border-radius: var(--radius-sm);
}
.fingerprint-stat-label {
@@ -1104,7 +1104,7 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
.fingerprint-stat-value {
font-size: 1.125rem;
font-weight: 500;
font-weight: var(--font-weight-medium);
color: rgba(212, 201, 168, 0.3);
}
@@ -1117,16 +1117,16 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
.download-button {
padding: 0.625rem 1rem;
background: #14532d;
border: 1px solid #22c55e;
border-radius: 4px;
color: #86efac;
font-weight: 500;
background: var(--color-status-success-text);
border: 1px solid var(--color-status-success);
border-radius: var(--radius-sm);
color: var(--color-status-success-border);
font-weight: var(--font-weight-medium);
cursor: pointer;
}
.download-button:hover {
background: #166534;
background: var(--color-status-success-text);
}
.fingerprint-meta {
@@ -1148,7 +1148,7 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
}
.fingerprint-table th {
font-weight: 500;
font-weight: var(--font-weight-medium);
color: var(--color-text-muted);
background: var(--color-text-heading);
}
@@ -1171,7 +1171,7 @@ type Tab = 'health' | 'bench' | 'cache' | 'config' | 'fingerprint';
.action-link {
background: transparent;
border: none;
color: #3b82f6;
color: var(--color-status-info);
cursor: pointer;
font-size: 0.875rem;
padding: 0;

View File

@@ -82,7 +82,7 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
(click)="loadCoverage()"
title="Refresh data"
>
<span class="btn-icon">&#64;</span>
<span class="btn-icon" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg></span>
Refresh
</button>
</div>
@@ -369,14 +369,14 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
}
.page-title {
font-size: 24px;
font-weight: 600;
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-semibold);
margin: 0 0 4px 0;
}
.page-subtitle {
font-size: 14px;
color: #666;
font-size: var(--font-size-base);
color: var(--color-text-secondary);
margin: 0;
}
@@ -393,15 +393,15 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
}
.filter-label {
font-size: 14px;
font-weight: 500;
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
}
.filter-input {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
font-size: var(--font-size-base);
width: 180px;
}
@@ -410,17 +410,17 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
align-items: center;
gap: 6px;
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
background: white;
font-size: 14px;
font-size: var(--font-size-base);
cursor: pointer;
transition: all 0.15s;
}
.action-btn:hover:not(:disabled) {
border-color: #007bff;
color: #007bff;
border-color: var(--color-status-info);
color: var(--color-status-info);
}
.action-btn:disabled {
@@ -441,9 +441,9 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
.loading-spinner {
width: 24px;
height: 24px;
border: 3px solid #f3f3f3;
border-top: 3px solid #007bff;
border-radius: 50%;
border: 3px solid var(--color-surface-secondary);
border-top: 3px solid var(--color-status-info);
border-radius: var(--radius-full);
animation: spin 1s linear infinite;
}
@@ -452,22 +452,22 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
}
.error-state {
background: #f8d7da;
border-radius: 8px;
color: #721c24;
background: var(--color-status-error-bg);
border-radius: var(--radius-lg);
color: var(--color-status-error-text);
}
.error-icon {
font-weight: bold;
font-size: 18px;
font-size: var(--font-size-lg);
}
.retry-btn {
padding: 6px 12px;
background: #721c24;
background: var(--color-status-error-text);
color: white;
border: none;
border-radius: 4px;
border-radius: var(--radius-sm);
cursor: pointer;
}
@@ -477,13 +477,13 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
align-items: center;
gap: 8px;
margin-bottom: 16px;
font-size: 14px;
font-size: var(--font-size-base);
}
.breadcrumb-link {
background: none;
border: none;
color: #007bff;
color: var(--color-status-info);
cursor: pointer;
padding: 0;
}
@@ -493,11 +493,11 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
}
.breadcrumb-separator {
color: #999;
color: var(--color-text-muted);
}
.breadcrumb-current {
font-weight: 500;
font-weight: var(--font-weight-medium);
}
/* Summary bar */
@@ -505,8 +505,8 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
display: flex;
gap: 24px;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
background: var(--color-surface-primary);
border-radius: var(--radius-lg);
margin-bottom: 24px;
}
@@ -517,17 +517,17 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
}
.stat-value {
font-size: 24px;
font-weight: 600;
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-semibold);
}
.stat-label {
font-size: 12px;
color: #666;
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
}
.stat-item.critical .stat-value { color: #dc3545; }
.stat-item.safe .stat-value { color: #28a745; }
.stat-item.critical .stat-value { color: var(--color-status-error); }
.stat-item.safe .stat-value { color: var(--color-status-success); }
/* Heatmap grid */
.heatmap-grid {
@@ -540,8 +540,8 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
display: flex;
flex-direction: column;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
background: white;
cursor: pointer;
text-align: left;
@@ -554,58 +554,58 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
}
.heatmap-cell:focus {
outline: 2px solid #007bff;
outline: 2px solid var(--color-status-info);
outline-offset: 2px;
}
/* Coverage colors */
.heatmap-cell.coverage-critical {
border-left: 4px solid #dc3545;
background: #fff5f5;
border-left: 4px solid var(--color-status-error);
background: var(--color-status-error-bg);
}
.heatmap-cell.coverage-high {
border-left: 4px solid #fd7e14;
background: #fff8f0;
border-left: 4px solid var(--color-severity-high);
background: var(--color-status-warning-bg);
}
.heatmap-cell.coverage-medium {
border-left: 4px solid #ffc107;
background: #fffef0;
border-left: 4px solid var(--color-status-warning);
background: var(--color-surface-primary);
}
.heatmap-cell.coverage-low {
border-left: 4px solid #20c997;
background: #f0fff8;
border-left: 4px solid var(--color-status-success);
background: var(--color-status-success-bg);
}
.heatmap-cell.coverage-safe {
border-left: 4px solid #28a745;
background: #f0fff0;
border-left: 4px solid var(--color-status-success);
background: var(--color-status-success-bg);
}
.cell-cve {
font-weight: 600;
font-size: 14px;
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-base);
margin-bottom: 2px;
}
.cell-package {
font-size: 12px;
color: #666;
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
margin-bottom: 8px;
}
.cell-coverage {
font-size: 20px;
font-weight: 600;
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
margin-bottom: 4px;
}
.cell-bar {
height: 4px;
background: #e9ecef;
border-radius: 2px;
background: var(--color-border-primary);
border-radius: var(--radius-sm);
overflow: hidden;
}
@@ -615,11 +615,11 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
transition: width 0.3s;
}
.coverage-critical .cell-bar-fill { background: #dc3545; }
.coverage-high .cell-bar-fill { background: #fd7e14; }
.coverage-medium .cell-bar-fill { background: #ffc107; }
.coverage-low .cell-bar-fill { background: #20c997; }
.coverage-safe .cell-bar-fill { background: #28a745; }
.coverage-critical .cell-bar-fill { background: var(--color-status-error); }
.coverage-high .cell-bar-fill { background: var(--color-severity-high); }
.coverage-medium .cell-bar-fill { background: var(--color-status-warning); }
.coverage-low .cell-bar-fill { background: var(--color-status-success); }
.coverage-safe .cell-bar-fill { background: var(--color-status-success); }
/* Pagination */
.pagination {
@@ -632,8 +632,8 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
.page-btn {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
background: white;
cursor: pointer;
}
@@ -644,15 +644,15 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
}
.page-info {
font-size: 14px;
color: #666;
font-size: var(--font-size-base);
color: var(--color-text-secondary);
}
/* Details section */
.details-section {
background: white;
border: 1px solid #e9ecef;
border-radius: 12px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-xl);
padding: 24px;
}
@@ -661,14 +661,14 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
}
.details-title {
font-size: 20px;
font-weight: 600;
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
margin: 0 0 4px 0;
}
.details-package {
font-size: 14px;
color: #666;
font-size: var(--font-size-base);
color: var(--color-text-secondary);
}
.details-summary {
@@ -680,24 +680,24 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
.summary-card {
flex: 1;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
background: var(--color-surface-primary);
border-radius: var(--radius-lg);
text-align: center;
}
.summary-card.vulnerable { background: #f8d7da; }
.summary-card.patched { background: #d4edda; }
.summary-card.unknown { background: #e2e3e5; }
.summary-card.vulnerable { background: var(--color-status-error-bg); }
.summary-card.patched { background: var(--color-status-success-bg); }
.summary-card.unknown { background: var(--color-border-primary); }
.card-value {
display: block;
font-size: 24px;
font-weight: 600;
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-semibold);
}
.card-label {
font-size: 12px;
color: #666;
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
}
/* Tables */
@@ -710,51 +710,51 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
.matches-table th, .matches-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e9ecef;
border-bottom: 1px solid var(--color-border-primary);
}
.function-table th, .matches-table th {
font-weight: 600;
font-size: 13px;
color: #666;
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-base);
color: var(--color-text-secondary);
text-transform: uppercase;
}
.symbol-name {
font-family: monospace;
font-size: 13px;
font-size: var(--font-size-base);
}
.count.vulnerable { color: #dc3545; }
.count.patched { color: #28a745; }
.count.unknown { color: #6c757d; }
.count.vulnerable { color: var(--color-status-error); }
.count.patched { color: var(--color-status-success); }
.count.unknown { color: var(--color-text-secondary); }
.delta-badge {
display: inline-block;
padding: 2px 6px;
background: #007bff;
background: var(--color-status-info);
color: white;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
}
.no-delta {
color: #999;
color: var(--color-text-muted);
}
.view-btn {
padding: 4px 8px;
font-size: 12px;
border: 1px solid #007bff;
border-radius: 4px;
font-size: var(--font-size-sm);
border: 1px solid var(--color-status-info);
border-radius: var(--radius-sm);
background: white;
color: #007bff;
color: var(--color-status-info);
cursor: pointer;
}
.view-btn:hover {
background: #007bff;
background: var(--color-status-info);
color: white;
}
@@ -767,14 +767,14 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
}
.matches-title {
font-size: 18px;
font-weight: 600;
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
margin: 0;
}
.matches-count {
font-size: 14px;
color: #666;
font-size: var(--font-size-base);
color: var(--color-text-secondary);
}
.matches-filters {
@@ -786,13 +786,13 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
.state-select {
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 6px;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
}
.binary-key {
font-family: monospace;
font-size: 12px;
font-size: var(--font-size-sm);
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
@@ -800,18 +800,18 @@ type ViewMode = 'heatmap' | 'details' | 'matches';
.state-badge {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
}
.state-vulnerable { background: #f8d7da; color: #721c24; }
.state-patched { background: #d4edda; color: #155724; }
.state-unknown { background: #e2e3e5; color: #383d41; }
.state-vulnerable { background: var(--color-status-error-bg); color: var(--color-status-error-text); }
.state-patched { background: var(--color-status-success-bg); color: var(--color-status-success-text); }
.state-unknown { background: var(--color-border-primary); color: var(--color-text-primary); }
.scanned-at {
font-size: 12px;
color: #666;
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
}
/* Responsive */

View File

@@ -122,8 +122,8 @@ import { ChangeTraceService } from './services/change-trace.service';
h1 {
margin: 0;
font-size: 24px;
font-weight: 600;
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
}
@@ -135,9 +135,9 @@ import { ChangeTraceService } from './services/change-trace.service';
.btn {
padding: 8px 16px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
border-radius: var(--radius-md);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
cursor: pointer;
border: none;
transition: all 0.15s ease;
@@ -190,7 +190,7 @@ import { ChangeTraceService } from './services/change-trace.service';
text-align: center;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 8px;
border-radius: var(--radius-lg);
}
.error-message {
@@ -204,7 +204,7 @@ import { ChangeTraceService } from './services/change-trace.service';
h2 {
margin: 0 0 8px;
font-size: 18px;
font-size: var(--font-size-lg);
color: var(--color-text-primary);
}

View File

@@ -22,9 +22,9 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
align-items: center;
gap: 4px;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
}
.trust-badge.positive {
@@ -43,7 +43,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
}
.icon {
font-size: 14px;
font-size: var(--font-size-base);
}
.score {

View File

@@ -1,15 +1,13 @@
<div class="actionables-panel">
<h4>
<mat-icon>task_alt</mat-icon>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
What to do next
</h4>
<mat-list>
@for (action of actionables(); track action) {
<mat-list-item>
<mat-icon matListItemIcon [class]="'action-' + action.type">
{{ getActionIcon(action.type) }}
</mat-icon>
<span matListItemIcon [class]="'action-' + action.type" [innerHTML]="getActionIconSvg(action.type)"></span>
<div matListItemTitle>
{{ action.title }}
<mat-chip [class]="'priority-' + action.priority">
@@ -44,7 +42,7 @@
@if (actionables().length === 0) {
<div class="empty-state">
<mat-icon>check_circle</mat-icon>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
<p>No immediate actions required</p>
</div>
}

View File

@@ -1,8 +1,8 @@
import { Component, ChangeDetectionStrategy, input, inject } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { MatListModule } from '@angular/material/list';
import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatSnackBar } from '@angular/material/snack-bar';
@@ -23,7 +23,6 @@ export interface Actionable {
imports: [
MatListModule,
MatChipsModule,
MatIconModule,
MatButtonModule
],
templateUrl: './actionables-panel.component.html',
@@ -32,6 +31,21 @@ export interface Actionable {
})
export class ActionablesPanelComponent {
private readonly snackBar = inject(MatSnackBar);
private readonly sanitizer = inject(DomSanitizer);
private readonly actionIconSvgMap: Record<string, string> = {
upgrade: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 11 12 6 7 11"/><polyline points="17 18 12 13 7 18"/></svg>',
build: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>',
description: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>',
settings: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>',
search: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>',
task: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
};
getActionIconSvg(type: string): SafeHtml {
const icon = this.getActionIcon(type);
return this.sanitizer.bypassSecurityTrustHtml(this.actionIconSvgMap[icon] || this.actionIconSvgMap['task']);
}
actionables = input<Actionable[]>([]);

Some files were not shown because too many files have changed in this diff Show More