-
{health}
-
+
{health}
+
{health === 'Online' && 'Agent is responding to heartbeat checks'}
{health === 'Stale' && 'Agent has not sent a heartbeat recently'}
{health === 'Offline' && 'Agent is not responding'}
diff --git a/web/src/pages/AgentFleetPage.tsx b/web/src/pages/AgentFleetPage.tsx
index 876e29b..9905765 100644
--- a/web/src/pages/AgentFleetPage.tsx
+++ b/web/src/pages/AgentFleetPage.tsx
@@ -8,7 +8,7 @@ import type { Agent } from '../api/types';
const OS_COLORS: Record = {
linux: '#f97316',
- darwin: '#3b82f6',
+ darwin: '#2ea88f',
windows: '#8b5cf6',
unknown: '#64748b',
};
@@ -53,9 +53,9 @@ function groupAgents(agents: Agent[]): GroupedAgents[] {
const CustomTooltip = ({ active, payload }: any) => {
if (!active || !payload?.length) return null;
return (
-
+
{payload.map((entry: any, i: number) => (
-
+
{entry.name}: {entry.value}
))}
@@ -113,25 +113,25 @@ export default function AgentFleetPage() {
{/* Summary Cards */}
-
-
Total Agents
-
{totalAgents}
+
+
Total Agents
+
{totalAgents}
-
-
Online
-
{onlineAgents}
+
+
Online
+
{onlineAgents}
-
-
Offline
-
{offlineAgents}
+
+
Offline
+
{offlineAgents}
{/* Charts */}
{/* OS Distribution */}
-
-
OS Distribution
+
+
OS Distribution
{osPieData.length > 0 ? (
@@ -145,14 +145,14 @@ export default function AgentFleetPage() {
) : (
-
No data
+
No data
)}
{/* Status Distribution */}
-
-
Status Distribution
+
+
Status Distribution
{statusPieData.length > 0 ? (
@@ -166,33 +166,33 @@ export default function AgentFleetPage() {
) : (
-
No data
+
No data
)}
{/* Version Breakdown */}
-
-
Agent Versions
+
+
Agent Versions
{Object.entries(versionCounts)
.sort(([, a], [, b]) => b - a)
.map(([version, count]) => (
-
{version}
+
{version}
))}
{Object.keys(versionCounts).length === 0 && (
-
No version data
+
No version data
)}
@@ -200,50 +200,50 @@ export default function AgentFleetPage() {
{/* Environment Groups */}
-
Fleet by Platform
+
Fleet by Platform
{isLoading ? (
-
Loading fleet data...
+
Loading fleet data...
) : groups.length === 0 ? (
-
No agents registered
+
No agents registered
) : (
{groups.map(group => (
-
-
+
+
-
+
{group.os} / {group.arch}
-
+
{group.agents.length} agent{group.agents.length !== 1 ? 's' : ''}
- {group.online} online
- {group.offline > 0 && {group.offline} offline }
+ {group.online} online
+ {group.offline > 0 && {group.offline} offline }
-
+
{group.agents.map(agent => (
navigate(`/agents/${agent.id}`)}
- className="px-5 py-3 flex items-center justify-between hover:bg-slate-700/30 cursor-pointer transition-colors"
+ className="px-5 py-3 flex items-center justify-between hover:bg-surface-muted cursor-pointer transition-colors"
>
-
+
-
{agent.name || agent.hostname}
-
{agent.ip_address || agent.id}
+
{agent.name || agent.hostname}
+
{agent.ip_address || agent.id}
{agent.version && (
- {agent.version}
+ {agent.version}
)}
diff --git a/web/src/pages/AgentGroupsPage.tsx b/web/src/pages/AgentGroupsPage.tsx
index a4e4a0f..dba3bb9 100644
--- a/web/src/pages/AgentGroupsPage.tsx
+++ b/web/src/pages/AgentGroupsPage.tsx
@@ -27,10 +27,10 @@ export default function AgentGroupsPage() {
label: 'Group',
render: (g) => (
-
{g.name}
-
{g.id}
+
{g.name}
+
{g.id}
{g.description && (
-
{g.description}
+
{g.description}
)}
),
@@ -51,7 +51,7 @@ export default function AgentGroupsPage() {
))}
) : (
-
Manual only
+
Manual only
);
},
},
@@ -63,7 +63,7 @@ export default function AgentGroupsPage() {
{
key: 'created',
label: 'Created',
- render: (g) =>
{formatDateTime(g.created_at)} ,
+ render: (g) =>
{formatDateTime(g.created_at)} ,
},
{
key: 'actions',
@@ -71,7 +71,7 @@ export default function AgentGroupsPage() {
render: (g) => (
{ e.stopPropagation(); if (confirm(`Delete group ${g.name}?`)) deleteMutation.mutate(g.id); }}
- className="text-xs text-red-400 hover:text-red-300 transition-colors"
+ className="text-xs text-red-600 hover:text-red-700 transition-colors"
>
Delete
diff --git a/web/src/pages/AgentsPage.tsx b/web/src/pages/AgentsPage.tsx
index 11967ea..1ddeaad 100644
--- a/web/src/pages/AgentsPage.tsx
+++ b/web/src/pages/AgentsPage.tsx
@@ -31,8 +31,8 @@ export default function AgentsPage() {
label: 'Agent',
render: (a) => (
-
{a.name}
-
{a.id}
+
{a.name}
+
{a.id}
),
},
@@ -41,14 +41,14 @@ export default function AgentsPage() {
label: 'Health',
render: (a) =>
,
},
- { key: 'hostname', label: 'Hostname', render: (a) =>
{a.hostname || '—'} },
- { key: 'os', label: 'OS / Arch', render: (a) =>
{a.os && a.architecture ? `${a.os}/${a.architecture}` : a.os || '—'} },
- { key: 'ip', label: 'IP Address', render: (a) =>
{a.ip_address || '—'} },
- { key: 'version', label: 'Version', render: (a) =>
{a.version || '—'} },
+ { key: 'hostname', label: 'Hostname', render: (a) =>
{a.hostname || '—'} },
+ { key: 'os', label: 'OS / Arch', render: (a) =>
{a.os && a.architecture ? `${a.os}/${a.architecture}` : a.os || '—'} },
+ { key: 'ip', label: 'IP Address', render: (a) =>
{a.ip_address || '—'} },
+ { key: 'version', label: 'Version', render: (a) =>
{a.version || '—'} },
{
key: 'heartbeat',
label: 'Last Heartbeat',
- render: (a) =>
{timeAgo(a.last_heartbeat)} ,
+ render: (a) =>
{timeAgo(a.last_heartbeat)} ,
},
];
diff --git a/web/src/pages/AuditPage.tsx b/web/src/pages/AuditPage.tsx
index 0db10a2..7b4e816 100644
--- a/web/src/pages/AuditPage.tsx
+++ b/web/src/pages/AuditPage.tsx
@@ -9,16 +9,16 @@ import { formatDateTime } from '../api/utils';
import type { AuditEvent } from '../api/types';
const actionColors: Record
= {
- certificate_created: 'text-emerald-400',
- renewal_triggered: 'text-blue-400',
- renewal_job_created: 'text-blue-400',
- renewal_completed: 'text-emerald-400',
- deployment_completed: 'text-emerald-400',
- deployment_failed: 'text-red-400',
- expiration_alert_sent: 'text-amber-400',
- agent_registered: 'text-blue-400',
- policy_violated: 'text-red-400',
- certificate_revoked: 'text-red-400',
+ certificate_created: 'text-emerald-600',
+ renewal_triggered: 'text-brand-500',
+ renewal_job_created: 'text-brand-500',
+ renewal_completed: 'text-emerald-600',
+ deployment_completed: 'text-emerald-600',
+ deployment_failed: 'text-red-600',
+ expiration_alert_sent: 'text-amber-600',
+ agent_registered: 'text-brand-500',
+ policy_violated: 'text-red-600',
+ certificate_revoked: 'text-red-600',
};
const RESOURCE_TYPES = ['', 'certificate', 'agent', 'job', 'notification', 'policy', 'target', 'issuer'];
@@ -94,7 +94,7 @@ export default function AuditPage() {
key: 'action',
label: 'Action',
render: (e) => (
-
+
{e.action.replace(/_/g, ' ')}
),
@@ -104,8 +104,8 @@ export default function AuditPage() {
label: 'Actor',
render: (e) => (
-
{e.actor}
-
{e.actor_type}
+
{e.actor}
+
{e.actor_type}
),
},
@@ -114,8 +114,8 @@ export default function AuditPage() {
label: 'Resource',
render: (e) => (
-
{e.resource_type}
-
{e.resource_id}
+
{e.resource_type}
+
{e.resource_id}
),
},
@@ -123,15 +123,15 @@ export default function AuditPage() {
key: 'details',
label: 'Details',
render: (e) => {
- if (!e.details || Object.keys(e.details).length === 0) return — ;
+ if (!e.details || Object.keys(e.details).length === 0) return — ;
return (
-
+
{JSON.stringify(e.details).slice(0, 60)}
);
},
},
- { key: 'time', label: 'Time', render: (e) => {formatDateTime(e.timestamp)} },
+ { key: 'time', label: 'Time', render: (e) => {formatDateTime(e.timestamp)} },
];
const hasFilters = resourceType || actorFilter || timeRange || actionFilter;
@@ -144,21 +144,21 @@ export default function AuditPage() {
action={
filtered.length > 0 ? (
- exportCSV(filtered)} className="btn btn-ghost text-xs border border-slate-600">
+ exportCSV(filtered)} className="btn btn-ghost text-xs border border-surface-border">
Export CSV
- exportJSON(filtered)} className="btn btn-ghost text-xs border border-slate-600">
+ exportJSON(filtered)} className="btn btn-ghost text-xs border border-surface-border">
Export JSON
) : undefined
}
/>
-
+
setResourceType(e.target.value)}
- className="bg-slate-800 border border-slate-600 rounded px-3 py-1.5 text-xs text-slate-300 focus:outline-none focus:border-blue-500"
+ className="bg-surface border border-surface-border rounded px-3 py-1.5 text-xs text-ink focus:outline-none focus:border-brand-400"
>
All resources
{RESOURCE_TYPES.filter(Boolean).map((t) => (
@@ -170,19 +170,19 @@ export default function AuditPage() {
placeholder="Filter by actor..."
value={actorFilter}
onChange={(e) => setActorFilter(e.target.value)}
- className="bg-slate-800 border border-slate-600 rounded px-3 py-1.5 text-xs text-slate-300 placeholder-slate-500 focus:outline-none focus:border-blue-500 w-40"
+ className="bg-surface border border-surface-border rounded px-3 py-1.5 text-xs text-ink placeholder-ink-faint focus:outline-none focus:border-brand-400 w-40"
/>
setActionFilter(e.target.value)}
- className="bg-slate-800 border border-slate-600 rounded px-3 py-1.5 text-xs text-slate-300 placeholder-slate-500 focus:outline-none focus:border-blue-500 w-40"
+ className="bg-surface border border-surface-border rounded px-3 py-1.5 text-xs text-ink placeholder-ink-faint focus:outline-none focus:border-brand-400 w-40"
/>
setTimeRange(e.target.value)}
- className="bg-slate-800 border border-slate-600 rounded px-3 py-1.5 text-xs text-slate-300 focus:outline-none focus:border-blue-500"
+ className="bg-surface border border-surface-border rounded px-3 py-1.5 text-xs text-ink focus:outline-none focus:border-brand-400"
>
{TIME_RANGES.map((r) => (
{r.label}
@@ -191,7 +191,7 @@ export default function AuditPage() {
{hasFilters && (
{ setResourceType(''); setActorFilter(''); setTimeRange(''); setActionFilter(''); }}
- className="text-xs text-slate-400 hover:text-slate-200 transition-colors"
+ className="text-xs text-ink-muted hover:text-ink transition-colors"
>
Clear filters
diff --git a/web/src/pages/CertificateDetailPage.tsx b/web/src/pages/CertificateDetailPage.tsx
index 51f954b..6bdf85e 100644
--- a/web/src/pages/CertificateDetailPage.tsx
+++ b/web/src/pages/CertificateDetailPage.tsx
@@ -11,12 +11,12 @@ import type { Job } from '../api/types';
function InfoRow({ label, value, editable, onEdit }: { label: string; value: React.ReactNode; editable?: boolean; onEdit?: () => void }) {
return (
-
-
{label}
+
+
{label}
- {value}
+ {value}
{editable && onEdit && (
-
+
Edit
)}
@@ -28,22 +28,22 @@ function InfoRow({ label, value, editable, onEdit }: { label: string; value: Rea
// Timeline step component for deployment status
function TimelineStep({ label, status, time, isLast }: { label: string; status: 'completed' | 'active' | 'pending' | 'failed'; time?: string; isLast?: boolean }) {
const dotStyles = {
- completed: 'bg-emerald-500 ring-emerald-500/30',
- active: 'bg-blue-500 ring-blue-500/30 animate-pulse',
- pending: 'bg-slate-600 ring-slate-600/30',
- failed: 'bg-red-500 ring-red-500/30',
+ completed: 'bg-emerald-500 ring-emerald-200',
+ active: 'bg-brand-400 ring-brand-200 animate-pulse',
+ pending: 'bg-surface-muted ring-surface-border',
+ failed: 'bg-red-500 ring-red-200',
};
const lineStyles = {
- completed: 'bg-emerald-500/50',
- active: 'bg-blue-500/30',
- pending: 'bg-slate-700',
- failed: 'bg-red-500/30',
+ completed: 'bg-emerald-300',
+ active: 'bg-brand-200',
+ pending: 'bg-surface-border',
+ failed: 'bg-red-300',
};
const textStyles = {
- completed: 'text-emerald-400',
- active: 'text-blue-400',
- pending: 'text-slate-500',
- failed: 'text-red-400',
+ completed: 'text-emerald-600',
+ active: 'text-brand-400',
+ pending: 'text-ink-faint',
+ failed: 'text-red-600',
};
return (
@@ -54,7 +54,7 @@ function TimelineStep({ label, status, time, isLast }: { label: string; status:
{label}
- {time &&
{time}
}
+ {time &&
{time}
}
);
@@ -117,8 +117,8 @@ function DeploymentTimeline({ certId, certStatus, createdAt, issuedAt }: { certI
};
return (
-
-
Lifecycle Timeline
+
+
Lifecycle Timeline
@@ -161,10 +161,10 @@ function InlinePolicyEditor({ certId, currentPolicyId, currentProfileId }: { cer
if (!editing) {
return (
-
+
-
Policy & Profile
- setEditing(true)} className="text-xs text-blue-400 hover:text-blue-300 transition-colors">
+ Policy & Profile
+ setEditing(true)} className="text-xs text-brand-400 hover:text-brand-500 transition-colors">
Edit
@@ -175,28 +175,28 @@ function InlinePolicyEditor({ certId, currentPolicyId, currentProfileId }: { cer
}
return (
-
+
-
Edit Policy & Profile
+
Edit Policy & Profile
{ setEditing(false); setPolicyId(currentPolicyId); setProfileId(currentProfileId); }}
- className="text-xs text-slate-400 hover:text-slate-300">Cancel
+ className="text-xs text-ink-muted hover:text-ink">Cancel
saveMutation.mutate()} disabled={saveMutation.isPending}
- className="text-xs text-blue-400 hover:text-blue-300 font-medium disabled:opacity-50">
+ className="text-xs text-brand-400 hover:text-brand-500 font-medium disabled:opacity-50">
{saveMutation.isPending ? 'Saving...' : 'Save'}
{saveMutation.isError && (
-
+
{saveMutation.error instanceof Error ? saveMutation.error.message : 'Failed to save'}
)}
- Renewal Policy
+ Renewal Policy
setPolicyId(e.target.value)}
- className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200">
+ className="w-full bg-white border border-surface-border rounded px-3 py-2 text-sm text-ink">
None
{policies?.data?.map(p => (
{p.name} ({p.type})
@@ -204,9 +204,9 @@ function InlinePolicyEditor({ certId, currentPolicyId, currentProfileId }: { cer
-
Certificate Profile
+
Certificate Profile
setProfileId(e.target.value)}
- className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200">
+ className="w-full bg-white border border-surface-border rounded px-3 py-2 text-sm text-ink">
None
{profiles?.data?.map(p => (
{p.name} — max TTL {p.max_ttl_seconds ? `${Math.round(p.max_ttl_seconds / 86400)}d` : '∞'}
@@ -316,7 +316,7 @@ export default function CertificateDetailPage() {
setShowDeploy(true)}
disabled={isArchived || isRevoked}
- className="btn btn-ghost text-xs border border-slate-600 disabled:opacity-50"
+ className="btn btn-ghost text-xs border border-surface-border disabled:opacity-50"
>
Deploy
@@ -349,53 +349,53 @@ export default function CertificateDetailPage() {
/>
{renewMutation.isSuccess && (
-
+
Renewal triggered successfully. A renewal job has been created.
)}
{renewMutation.isError && (
-
+
Failed to trigger renewal: {renewMutation.error instanceof Error ? renewMutation.error.message : 'Unknown error'}
)}
{deployMutation.isSuccess && (
-
+
Deployment triggered. A deployment job has been created.
)}
{deployMutation.isError && (
-
+
Failed to deploy: {deployMutation.error instanceof Error ? deployMutation.error.message : 'Unknown error'}
)}
{archiveMutation.isError && (
-
+
Failed to archive: {archiveMutation.error instanceof Error ? archiveMutation.error.message : 'Unknown error'}
)}
{revokeMutation.isSuccess && (
-
+
Certificate revoked successfully. It has been added to the CRL.
)}
{revokeMutation.isError && (
-
+
Failed to revoke: {revokeMutation.error instanceof Error ? revokeMutation.error.message : 'Unknown error'}
)}
{/* Revocation Banner */}
{isRevoked && (
-
+
-
-
+
-
Certificate Revoked
-
+
Certificate Revoked
+
Reason: {REVOCATION_REASONS.find(r => r.value === cert.revocation_reason)?.label || cert.revocation_reason || 'Unspecified'}
{cert.revoked_at && <> · Revoked {formatDateTime(cert.revoked_at)}>}
@@ -409,8 +409,8 @@ export default function CertificateDetailPage() {
{/* Certificate Info */}
-
-
Certificate Details
+
+
Certificate Details
} />
@@ -423,11 +423,11 @@ export default function CertificateDetailPage() {
{/* Lifecycle */}
-
-
Lifecycle
+
+
Lifecycle
+
{formatDate(cert.expires_at)} ({days <= 0 ? 'expired' : `${days} days`})
} />
@@ -438,10 +438,10 @@ export default function CertificateDetailPage() {
{isRevoked && (
<>
{cert.revoked_at ? formatDateTime(cert.revoked_at) : '—'}
+ {cert.revoked_at ? formatDateTime(cert.revoked_at) : '—'}
} />
+
{REVOCATION_REASONS.find(r => r.value === cert.revocation_reason)?.label || cert.revocation_reason || '—'}
} />
@@ -461,8 +461,8 @@ export default function CertificateDetailPage() {
{/* Tags */}
{cert.tags && Object.keys(cert.tags).length > 0 && (
-
-
Tags
+
+
Tags
{Object.entries(cert.tags).map(([k, v]) => (
{k}: {v}
@@ -472,32 +472,32 @@ export default function CertificateDetailPage() {
)}
{/* Version History */}
-
-
+
+
Version History {versions?.data?.length ? `(${versions.data.length})` : ''}
{!versions?.data?.length ? (
-
No versions yet
+
No versions yet
) : (
{versions.data.map((v, idx) => (
-
+
- Version {v.version}
- {idx === 0 && Current }
+ Version {v.version}
+ {idx === 0 && Current }
-
{v.serial_number}
+
{v.serial_number}
-
{formatDate(v.not_before)} — {formatDate(v.not_after)}
-
{formatDateTime(v.created_at)}
+
{formatDate(v.not_before)} — {formatDate(v.not_after)}
+
{formatDateTime(v.created_at)}
{idx > 0 && cert?.status !== 'Archived' && cert?.status !== 'Revoked' && (
setShowDeploy(true)}
- className="text-xs text-amber-400 hover:text-amber-300 border border-amber-500/30 px-2 py-1 rounded hover:bg-amber-500/10 transition-colors"
+ className="text-xs text-amber-600 hover:text-amber-700 border border-amber-300 px-2 py-1 rounded hover:bg-amber-50 transition-colors"
title="Redeploy this version to targets"
>
Rollback
@@ -513,19 +513,19 @@ export default function CertificateDetailPage() {
{/* Deploy Modal */}
{showDeploy && (
- setShowDeploy(false)}>
-
e.stopPropagation()}>
-
Deploy Certificate
+
setShowDeploy(false)}>
+
e.stopPropagation()}>
+
Deploy Certificate
{deployMutation.isError && (
-
+
{deployMutation.error instanceof Error ? deployMutation.error.message : 'Unknown error'}
)}
-
Select Target
+
Select Target
setDeployTargetId(e.target.value)}
- className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200 mb-4"
+ className="w-full bg-white border border-surface-border rounded px-3 py-2 text-sm text-ink mb-4"
>
Choose a target...
{targets?.data?.map(t => (
@@ -548,22 +548,22 @@ export default function CertificateDetailPage() {
{/* Revoke Modal */}
{showRevoke && (
- setShowRevoke(false)}>
-
e.stopPropagation()}>
-
Revoke Certificate
-
+
setShowRevoke(false)}>
+
e.stopPropagation()}>
+
Revoke Certificate
+
This action cannot be undone. The certificate will be added to the CRL and marked as revoked.
{revokeMutation.isError && (
-
+
{revokeMutation.error instanceof Error ? revokeMutation.error.message : 'Unknown error'}
)}
-
Revocation Reason (RFC 5280)
+
Revocation Reason (RFC 5280)
setRevokeReason(e.target.value)}
- className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200 mb-4"
+ className="w-full bg-white border border-surface-border rounded px-3 py-2 text-sm text-ink mb-4"
>
{REVOCATION_REASONS.map(r => (
{r.label}
diff --git a/web/src/pages/CertificatesPage.tsx b/web/src/pages/CertificatesPage.tsx
index 56b92cf..6106faf 100644
--- a/web/src/pages/CertificatesPage.tsx
+++ b/web/src/pages/CertificatesPage.tsx
@@ -30,57 +30,57 @@ function CreateCertificateModal({ onClose, onSuccess }: { onClose: () => void; o
});
return (
-
-
e.stopPropagation()}>
-
New Certificate
- {error &&
{error}
}
+
+
e.stopPropagation()}>
+
New Certificate
+ {error &&
{error}
}
- ID (optional)
+ ID (optional)
setForm(f => ({ ...f, id: e.target.value }))}
- className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500"
+ className="w-full bg-white border border-surface-border rounded px-3 py-2 text-sm text-ink focus:outline-none focus:border-brand-400 focus:ring-1 focus:ring-brand-400/20"
placeholder="mc-api-prod (auto-generated if empty)" />
- Common Name *
+ Common Name *
setForm(f => ({ ...f, common_name: e.target.value }))}
- className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-blue-500"
+ className="w-full bg-white border border-surface-border rounded px-3 py-2 text-sm text-ink focus:outline-none focus:border-brand-400 focus:ring-1 focus:ring-brand-400/20"
placeholder="api.example.com" />
@@ -124,27 +124,27 @@ function BulkRevokeModal({ ids, onClose, onSuccess }: { ids: string[]; onClose:
};
return (
-
-
e.stopPropagation()}>
-
Bulk Revoke
-
+
+
e.stopPropagation()}>
+
Bulk Revoke
+
Revoke {ids.length} certificate{ids.length > 1 ? 's' : ''}. This cannot be undone.
- {error &&
{error}
}
+ {error &&
{error}
}
{running && (
-
+
Progress
{progress}/{ids.length}
-
)}
-
Revocation Reason (RFC 5280)
+
Revocation Reason (RFC 5280)
setReason(e.target.value)}
- className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200 mb-4"
+ className="w-full bg-white border border-surface-border rounded px-3 py-2 text-sm text-ink mb-4"
disabled={running}
>
{REVOCATION_REASONS.map(r => (
@@ -193,27 +193,27 @@ function BulkReassignModal({ ids, onClose, onSuccess }: { ids: string[]; onClose
};
return (
-
-
e.stopPropagation()}>
-
Reassign Owner
-
+
+
e.stopPropagation()}>
+
Reassign Owner
+
Reassign {ids.length} certificate{ids.length > 1 ? 's' : ''} to a new owner.
- {error &&
{error}
}
+ {error &&
{error}
}
{running && (
-
+
Progress
{progress}/{ids.length}
-
)}
-
New Owner
+
New Owner
setOwnerId(e.target.value)}
- className="w-full bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-sm text-slate-200 mb-4"
+ className="w-full bg-white border border-surface-border rounded px-3 py-2 text-sm text-ink mb-4"
disabled={running}
>
Select owner...
@@ -276,8 +276,8 @@ export default function CertificatesPage() {
label: 'Certificate',
render: (c) => (
-
{c.common_name}
-
{c.id}
+
{c.common_name}
+
{c.id}
),
},
@@ -290,14 +290,14 @@ export default function CertificatesPage() {
return (
{formatDate(c.expires_at)}
-
{days <= 0 ? 'Expired' : `${days} days`}
+
{days <= 0 ? 'Expired' : `${days} days`}
);
},
},
- { key: 'env', label: 'Environment', render: (c) => {c.environment || '—'} },
- { key: 'issuer', label: 'Issuer', render: (c) => {c.issuer_id} },
- { key: 'owner', label: 'Owner', render: (c) => {c.owner_id} },
+ { key: 'env', label: 'Environment', render: (c) => {c.environment || '—'} },
+ { key: 'issuer', label: 'Issuer', render: (c) => {c.issuer_id} },
+ { key: 'owner', label: 'Owner', render: (c) => {c.owner_id} },
];
const selectedArray = Array.from(selectedIds);
@@ -317,8 +317,8 @@ export default function CertificatesPage() {
{/* Bulk Action Bar */}
{hasSelection && (
-
-
{selectedArray.length} selected
+
+
{selectedArray.length} selected
@@ -331,11 +331,11 @@ export default function CertificatesPage() {
Revoke
setShowBulkReassign(true)}
- className="btn btn-ghost text-xs text-blue-400 hover:text-blue-300 border border-blue-600/50">
+ className="btn btn-ghost text-xs text-brand-400 hover:text-brand-300 border border-brand-600/50">
Reassign Owner
setSelectedIds(new Set())}
- className="btn btn-ghost text-xs text-slate-400">
+ className="btn btn-ghost text-xs text-ink-muted">
Clear
@@ -344,18 +344,18 @@ export default function CertificatesPage() {
{/* Bulk Renewal Success */}
{bulkRenewProgress && !bulkRenewProgress.running && (
-
-
+
+
Triggered renewal for {bulkRenewProgress.done} certificate{bulkRenewProgress.done > 1 ? 's' : ''}.
)}
-
+
setStatusFilter(e.target.value)}
- className="bg-slate-800 border border-slate-600 rounded-lg px-3 py-1.5 text-sm text-slate-300"
+ className="bg-white border border-surface-border rounded px-3 py-1.5 text-sm text-ink"
>
All statuses
Active
@@ -368,7 +368,7 @@ export default function CertificatesPage() {
setEnvFilter(e.target.value)}
- className="bg-slate-800 border border-slate-600 rounded-lg px-3 py-1.5 text-sm text-slate-300"
+ className="bg-white border border-surface-border rounded px-3 py-1.5 text-sm text-ink"
>
All environments
Production
diff --git a/web/src/pages/DashboardPage.tsx b/web/src/pages/DashboardPage.tsx
index dc1db85..952acca 100644
--- a/web/src/pages/DashboardPage.tsx
+++ b/web/src/pages/DashboardPage.tsx
@@ -19,28 +19,29 @@ const STATUS_COLORS: Record = {
Expired: '#ef4444',
Revoked: '#8b5cf6',
Pending: '#6366f1',
- RenewalInProgress: '#3b82f6',
+ RenewalInProgress: '#2ea88f',
Failed: '#f43f5e',
Archived: '#64748b',
};
function StatCard({ label, value, icon, color }: { label: string; value: string | number; icon: string; color: string }) {
- const colorMap: Record = {
- success: 'bg-emerald-500/10 text-emerald-400',
- warning: 'bg-amber-500/10 text-amber-400',
- danger: 'bg-red-500/10 text-red-400',
- info: 'bg-blue-500/10 text-blue-400',
+ const colorMap: Record = {
+ success: { bg: 'bg-emerald-50', border: 'border-t-emerald-500', text: 'text-emerald-700' },
+ warning: { bg: 'bg-amber-50', border: 'border-t-amber-500', text: 'text-amber-700' },
+ danger: { bg: 'bg-red-50', border: 'border-t-red-500', text: 'text-red-700' },
+ info: { bg: 'bg-blue-50', border: 'border-t-brand-400', text: 'text-brand-500' },
};
+ const config = colorMap[color] || colorMap.info;
return (
-
-
+
+
-
{label}
-
{value}
+
{label}
+
{value}
);
@@ -48,8 +49,8 @@ function StatCard({ label, value, icon, color }: { label: string; value: string
function ChartCard({ title, children }: { title: string; children: React.ReactNode }) {
return (
-
-
{title}
+
+
{title}
{children}
@@ -60,8 +61,8 @@ function ChartCard({ title, children }: { title: string; children: React.ReactNo
const CustomTooltip = ({ active, payload, label }: any) => {
if (!active || !payload?.length) return null;
return (
-
-
{label}
+
+
{label}
{payload.map((entry: any, i: number) => (
{entry.name}: {typeof entry.value === 'number' && entry.name?.includes('rate') ? `${entry.value.toFixed(1)}%` : entry.value}
@@ -159,12 +160,12 @@ export default function DashboardPage() {
{value} }
+ formatter={(value: string) => {value} }
/>
) : (
- No certificate data
+ No certificate data
)}
@@ -173,15 +174,15 @@ export default function DashboardPage() {
{weeklyExpiration.length > 0 ? (
-
-
-
+
+
+
} />
) : (
- No expiration data
+ No expiration data
)}
@@ -193,17 +194,17 @@ export default function DashboardPage() {
{(jobTrends || []).length > 0 ? (
-
-
-
+
+
+
} />
- {value} } />
+ {value} } />
) : (
-
No job trend data
+
No job trend data
)}
@@ -212,28 +213,28 @@ export default function DashboardPage() {
{(issuanceRate || []).length > 0 ? (
-
-
-
+
+
+
} />
-
+
) : (
-
No issuance data
+
No issuance data
)}
{/* Expiring Certificates */}
-
+
-
Certificates Expiring Soon
- navigate('/certificates')} className="text-xs text-blue-400 hover:text-blue-300">View all
+ Certificates Expiring Soon
+ navigate('/certificates')} className="text-xs text-brand-400 hover:text-brand-500">View all
{!certs?.data?.length ? (
-
No certificates
+
No certificates
) : (
{certs.data
@@ -246,17 +247,17 @@ export default function DashboardPage() {
navigate(`/certificates/${c.id}`)}
- className="flex items-center justify-between py-2 px-3 rounded-lg hover:bg-slate-700/50 cursor-pointer transition-colors"
+ className="flex items-center justify-between py-2 px-3 rounded hover:bg-surface-muted cursor-pointer transition-colors"
>
-
{c.common_name}
-
{c.environment || 'no env'}
+
{c.common_name}
+
{c.environment || 'no env'}
{days <= 0 ? 'Expired' : `${days} days`}
-
{formatDate(c.expires_at)}
+
{formatDate(c.expires_at)}
);
@@ -266,20 +267,20 @@ export default function DashboardPage() {
{/* Recent Jobs */}
-
+
-
Recent Jobs
- navigate('/jobs')} className="text-xs text-blue-400 hover:text-blue-300">View all
+ Recent Jobs
+ navigate('/jobs')} className="text-xs text-brand-400 hover:text-brand-500">View all
{!jobs?.data?.length ? (
-
No jobs
+
No jobs
) : (
{jobs.data.slice(0, 5).map(j => (
-
+
-
{j.type}
-
{j.certificate_id}
+
{j.type}
+
{j.certificate_id}
@@ -291,10 +292,10 @@ export default function DashboardPage() {
{/* Pending Jobs Banner */}
{pendingJobs > 0 && (
-
+
-
{pendingJobs} pending job{pendingJobs > 1 ? 's' : ''}
-
Jobs are waiting to be processed
+
{pendingJobs} pending job{pendingJobs > 1 ? 's' : ''}
+
Jobs are waiting to be processed
navigate('/jobs')} className="btn btn-primary text-xs">View Jobs
diff --git a/web/src/pages/IssuersPage.tsx b/web/src/pages/IssuersPage.tsx
index 5dc5d36..945e998 100644
--- a/web/src/pages/IssuersPage.tsx
+++ b/web/src/pages/IssuersPage.tsx
@@ -42,8 +42,8 @@ export default function IssuersPage() {
label: 'Issuer',
render: (i) => (
-
{i.name}
-
{i.id}
+
{i.name}
+
{i.id}
),
},
@@ -63,9 +63,9 @@ export default function IssuersPage() {
key: 'config',
label: 'Config',
render: (i) => {
- if (!i.config || Object.keys(i.config).length === 0) return
— ;
+ if (!i.config || Object.keys(i.config).length === 0) return
— ;
return (
-
+
{JSON.stringify(i.config).slice(0, 60)}
);
@@ -74,7 +74,7 @@ export default function IssuersPage() {
{
key: 'created',
label: 'Created',
- render: (i) => {formatDateTime(i.created_at)} ,
+ render: (i) => {formatDateTime(i.created_at)} ,
},
{
key: 'actions',
@@ -84,13 +84,13 @@ export default function IssuersPage() {
{ e.stopPropagation(); testMutation.mutate(i.id); }}
disabled={testMutation.isPending}
- className="text-xs text-blue-400 hover:text-blue-300 transition-colors"
+ className="text-xs text-brand-400 hover:text-brand-500 transition-colors"
>
Test
{ e.stopPropagation(); if (confirm(`Delete issuer ${i.name}?`)) deleteMutation.mutate(i.id); }}
- className="text-xs text-red-400 hover:text-red-300 transition-colors"
+ className="text-xs text-red-600 hover:text-red-700 transition-colors"
>
Delete
@@ -103,7 +103,7 @@ export default function IssuersPage() {
<>
{testResult && (
-
+
{testResult.id}: {testResult.msg}
setTestResult(null)} className="ml-3 text-xs opacity-60 hover:opacity-100">dismiss
diff --git a/web/src/pages/JobsPage.tsx b/web/src/pages/JobsPage.tsx
index 2d889f7..0560129 100644
--- a/web/src/pages/JobsPage.tsx
+++ b/web/src/pages/JobsPage.tsx
@@ -35,20 +35,20 @@ export default function JobsPage() {
label: 'Job',
render: (j) => (
-
{j.id}
-
{j.type}
+
{j.id}
+
{j.type}
),
},
{ key: 'status', label: 'Status', render: (j) =>
},
- { key: 'cert', label: 'Certificate', render: (j) =>
{j.certificate_id} },
+ { key: 'cert', label: 'Certificate', render: (j) =>
{j.certificate_id} },
{
key: 'attempts',
label: 'Attempts',
- render: (j) =>
{j.attempts}/{j.max_attempts} ,
+ render: (j) =>
{j.attempts}/{j.max_attempts} ,
},
- { key: 'scheduled', label: 'Scheduled', render: (j) =>
{formatDateTime(j.scheduled_at)} },
- { key: 'completed', label: 'Completed', render: (j) =>
{formatDateTime(j.completed_at)} },
+ { key: 'scheduled', label: 'Scheduled', render: (j) =>
{formatDateTime(j.scheduled_at)} },
+ { key: 'completed', label: 'Completed', render: (j) =>
{formatDateTime(j.completed_at)} },
{
key: 'actions',
label: '',
@@ -68,11 +68,11 @@ export default function JobsPage() {
return (
<>
-
+
setStatusFilter(e.target.value)}
- className="bg-slate-800 border border-slate-600 rounded-lg px-3 py-1.5 text-sm text-slate-300"
+ className="bg-white border border-surface-border rounded px-3 py-1.5 text-sm text-ink"
>
All statuses
Pending
@@ -84,7 +84,7 @@ export default function JobsPage() {
setTypeFilter(e.target.value)}
- className="bg-slate-800 border border-slate-600 rounded-lg px-3 py-1.5 text-sm text-slate-300"
+ className="bg-white border border-surface-border rounded px-3 py-1.5 text-sm text-ink"
>
All types
Renewal
diff --git a/web/src/pages/LoginPage.tsx b/web/src/pages/LoginPage.tsx
index 5f33ad2..4c62c2d 100644
--- a/web/src/pages/LoginPage.tsx
+++ b/web/src/pages/LoginPage.tsx
@@ -24,16 +24,16 @@ export default function LoginPage() {
}
return (
-
+
-
certctl
-
Certificate Control Plane
+
certctl
+
Certificate Control Plane
-