feat(ui): sweep remaining components for Deep Space consistency
Replace old design tokens and hardcoded colors across 29 files: - bg-primary/text-primary-foreground -> bg-accent/text-white - text-muted-foreground -> text-text-muted - text-destructive/bg-destructive -> text-error/bg-error - bg-muted -> bg-elevated (background usage) - Hardcoded green/red/yellow/emerald/amber/slate -> semantic tokens - Remove shadow-md/lg from cards, tooltips, topology nodes - rounded-xl -> rounded-lg on cards/panels - focus:ring-1 focus:ring-ring -> focus:border-accent on inputs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -461,7 +461,7 @@ function ChannelFormDialog({
|
||||
<select
|
||||
value={smtpProvider}
|
||||
onChange={(e) => handleProviderChange(e.target.value)}
|
||||
className="w-full rounded-md bg-slate-700 border border-slate-600 text-white px-3 py-2 text-sm"
|
||||
className="w-full rounded-md bg-elevated border border-border text-text-primary px-3 py-2 text-sm"
|
||||
>
|
||||
{SMTP_PRESETS.map((p) => (
|
||||
<option key={p.id} value={p.id}>
|
||||
@@ -544,12 +544,12 @@ function ChannelFormDialog({
|
||||
type="button"
|
||||
onClick={handleTestSmtp}
|
||||
disabled={testing || !smtpHost || !toAddress}
|
||||
className="px-4 py-2 rounded-md bg-slate-600 text-white text-sm hover:bg-slate-500 disabled:opacity-50"
|
||||
className="px-4 py-2 rounded-md bg-elevated text-text-primary text-sm hover:bg-elevated/80 disabled:opacity-50"
|
||||
>
|
||||
{testing ? 'Testing...' : 'Test Connection'}
|
||||
</button>
|
||||
{testResult && (
|
||||
<p className={`text-sm ${testResult.success ? 'text-green-400' : 'text-red-400'}`}>
|
||||
<p className={`text-sm ${testResult.success ? 'text-success' : 'text-error'}`}>
|
||||
{testResult.message}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -224,7 +224,7 @@ export function EmergencyKitDialog({
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check className="h-4 w-4 text-green-500" />
|
||||
<Check className="h-4 w-4 text-success" />
|
||||
Copied
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -56,18 +56,18 @@ const SCORE_CONFIG: Record<
|
||||
},
|
||||
2: {
|
||||
label: 'Fair',
|
||||
color: 'text-yellow-500',
|
||||
barColor: 'bg-yellow-500',
|
||||
color: 'text-warning',
|
||||
barColor: 'bg-warning',
|
||||
},
|
||||
3: {
|
||||
label: 'Strong',
|
||||
color: 'text-green-500',
|
||||
barColor: 'bg-green-500',
|
||||
color: 'text-success',
|
||||
barColor: 'bg-success',
|
||||
},
|
||||
4: {
|
||||
label: 'Very Strong',
|
||||
color: 'text-green-400',
|
||||
barColor: 'bg-green-400',
|
||||
color: 'text-success',
|
||||
barColor: 'bg-success',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ export function BulkDeployDialog({
|
||||
|
||||
{availableDevices.length === 0 ? (
|
||||
<div className="rounded-lg border border-border bg-elevated/50 p-4 text-center">
|
||||
<CheckCircle className="h-6 w-6 text-green-500 mx-auto mb-2" />
|
||||
<CheckCircle className="h-6 w-6 text-success mx-auto mb-2" />
|
||||
<p className="text-sm font-medium text-text-primary">
|
||||
All devices have certificates
|
||||
</p>
|
||||
@@ -222,7 +222,7 @@ export function BulkDeployDialog({
|
||||
className={cn(
|
||||
'text-[10px] uppercase px-1.5 py-0.5 rounded',
|
||||
d.status === 'online'
|
||||
? 'bg-green-500/10 text-green-500'
|
||||
? 'bg-success/10 text-success'
|
||||
: 'bg-text-muted/10 text-text-muted',
|
||||
)}
|
||||
>
|
||||
@@ -263,9 +263,9 @@ export function BulkDeployDialog({
|
||||
<div className="space-y-4">
|
||||
{/* Summary */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="rounded-lg border border-green-500/30 bg-green-500/5 p-4 text-center">
|
||||
<CheckCircle className="h-6 w-6 text-green-500 mx-auto mb-1" />
|
||||
<p className="text-2xl font-bold text-green-500">
|
||||
<div className="rounded-lg border border-success/30 bg-success/5 p-4 text-center">
|
||||
<CheckCircle className="h-6 w-6 text-success mx-auto mb-1" />
|
||||
<p className="text-2xl font-bold text-success">
|
||||
{result.success}
|
||||
</p>
|
||||
<p className="text-xs text-text-muted">Succeeded</p>
|
||||
|
||||
@@ -76,7 +76,7 @@ export function CAStatusCard({ ca, canWrite: writable, tenantId }: CAStatusCardP
|
||||
if (!ca) {
|
||||
return (
|
||||
<div className="max-w-lg mx-auto">
|
||||
<div className="rounded-xl border border-border bg-surface p-8 text-center space-y-6">
|
||||
<div className="rounded-lg border border-border bg-surface p-8 text-center space-y-6">
|
||||
<div className="mx-auto w-16 h-16 rounded-2xl bg-accent/10 flex items-center justify-center">
|
||||
<Shield className="h-8 w-8 text-accent" />
|
||||
</div>
|
||||
@@ -118,8 +118,8 @@ export function CAStatusCard({ ca, canWrite: writable, tenantId }: CAStatusCardP
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-xl border bg-surface p-6 space-y-4',
|
||||
isExpired ? 'border-error/40' : 'border-green-500/30',
|
||||
'rounded-lg border bg-surface p-6 space-y-4',
|
||||
isExpired ? 'border-error/40' : 'border-success/30',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -127,13 +127,13 @@ export function CAStatusCard({ ca, canWrite: writable, tenantId }: CAStatusCardP
|
||||
<div
|
||||
className={cn(
|
||||
'w-10 h-10 rounded-xl flex items-center justify-center',
|
||||
isExpired ? 'bg-error/10' : 'bg-green-500/10',
|
||||
isExpired ? 'bg-error/10' : 'bg-success/10',
|
||||
)}
|
||||
>
|
||||
<ShieldCheck
|
||||
className={cn(
|
||||
'h-5 w-5',
|
||||
isExpired ? 'text-error' : 'text-green-500',
|
||||
isExpired ? 'text-error' : 'text-success',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -146,7 +146,7 @@ export function CAStatusCard({ ca, canWrite: writable, tenantId }: CAStatusCardP
|
||||
'inline-flex items-center gap-1 text-[10px] font-medium uppercase px-1.5 py-0.5 rounded border mt-0.5',
|
||||
isExpired
|
||||
? 'bg-error/20 text-error border-error/40'
|
||||
: 'bg-green-500/20 text-green-500 border-green-500/40',
|
||||
: 'bg-success/20 text-success border-success/40',
|
||||
)}
|
||||
>
|
||||
{isExpired ? 'Expired' : 'Active'}
|
||||
@@ -175,7 +175,7 @@ export function CAStatusCard({ ca, canWrite: writable, tenantId }: CAStatusCardP
|
||||
title="Copy fingerprint"
|
||||
>
|
||||
{copied ? (
|
||||
<CheckCircle className="h-3.5 w-3.5 text-green-500" />
|
||||
<CheckCircle className="h-3.5 w-3.5 text-success" />
|
||||
) : (
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
)}
|
||||
|
||||
@@ -148,7 +148,7 @@ export function DeployCertDialog({
|
||||
|
||||
{availableDevices.length === 0 ? (
|
||||
<div className="rounded-lg border border-border bg-elevated/50 p-4 text-center">
|
||||
<CheckCircle className="h-6 w-6 text-green-500 mx-auto mb-2" />
|
||||
<CheckCircle className="h-6 w-6 text-success mx-auto mb-2" />
|
||||
<p className="text-sm font-medium text-text-primary">
|
||||
All devices have certificates
|
||||
</p>
|
||||
@@ -231,7 +231,7 @@ export function DeployCertDialog({
|
||||
|
||||
{step === 'done' && (
|
||||
<div className="py-8 text-center space-y-3">
|
||||
<CheckCircle className="h-8 w-8 text-green-500 mx-auto" />
|
||||
<CheckCircle className="h-8 w-8 text-success mx-auto" />
|
||||
<p className="text-sm font-medium text-text-primary">
|
||||
Certificate deployed successfully
|
||||
</p>
|
||||
|
||||
@@ -54,11 +54,11 @@ const STATUS_CONFIG: Record<
|
||||
},
|
||||
deployed: {
|
||||
label: 'Deployed',
|
||||
className: 'bg-green-500/20 text-green-500 border-green-500/40',
|
||||
className: 'bg-success/20 text-success border-success/40',
|
||||
},
|
||||
expiring: {
|
||||
label: 'Expiring Soon',
|
||||
className: 'bg-yellow-500/20 text-yellow-500 border-yellow-500/40',
|
||||
className: 'bg-warning/20 text-warning border-warning/40',
|
||||
},
|
||||
expired: {
|
||||
label: 'Expired',
|
||||
@@ -236,7 +236,7 @@ export function DeviceCertTable({
|
||||
|
||||
{/* Empty state */}
|
||||
{filteredCerts.length === 0 ? (
|
||||
<div className="rounded-xl border border-dashed border-accent/30 bg-accent/5 p-8 text-center space-y-3">
|
||||
<div className="rounded-lg border border-dashed border-accent/30 bg-accent/5 p-8 text-center space-y-3">
|
||||
<ShieldCheck className="h-10 w-10 text-accent mx-auto" />
|
||||
<h3 className="text-base font-semibold text-text-primary">
|
||||
No device certificates yet
|
||||
|
||||
@@ -25,7 +25,7 @@ function triggerBadgeClass(type: ConfigBackupEntry['trigger_type']) {
|
||||
case 'config-change':
|
||||
return 'border-orange-500/50 bg-orange-500/10 text-orange-500'
|
||||
default:
|
||||
return 'border-muted bg-muted/10 text-muted-foreground'
|
||||
return 'border-muted bg-muted/10 text-text-muted'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ export function ConfigHistorySection({ tenantId, deviceId, deviceName }: ConfigH
|
||||
return (
|
||||
<div className="rounded-lg border border-border bg-surface p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<History className="h-4 w-4 text-muted-foreground" />
|
||||
<h3 className="text-sm font-medium text-muted-foreground">Configuration History</h3>
|
||||
<History className="h-4 w-4 text-text-muted" />
|
||||
<h3 className="text-sm font-medium text-text-muted">Configuration History</h3>
|
||||
</div>
|
||||
|
||||
{selectedSnapshotId && (
|
||||
|
||||
@@ -28,7 +28,7 @@ export function DiffViewer({ tenantId, deviceId, snapshotId, onClose }: DiffView
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<h3 className="text-sm font-medium text-muted-foreground">Config Diff</h3>
|
||||
<h3 className="text-sm font-medium text-text-muted">Config Diff</h3>
|
||||
{diff && (
|
||||
<span className="text-xs font-mono">
|
||||
<span className="text-success">+{diff.lines_added}</span>
|
||||
|
||||
@@ -20,10 +20,10 @@ interface RestorePreviewProps {
|
||||
}
|
||||
|
||||
const riskBadgeColors = {
|
||||
none: 'bg-muted text-text-secondary',
|
||||
none: 'bg-elevated text-text-secondary',
|
||||
low: 'bg-success/10 text-success border-success/30',
|
||||
medium: 'bg-warning/10 text-warning border-warning/30',
|
||||
high: 'bg-destructive/10 text-destructive border-destructive/30',
|
||||
high: 'bg-error/10 text-error border-error/30',
|
||||
} as const
|
||||
|
||||
export function RestorePreview({
|
||||
@@ -53,9 +53,9 @@ export function RestorePreview({
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<div className="h-12 rounded-lg bg-muted animate-pulse" />
|
||||
<div className="h-32 rounded-lg bg-muted animate-pulse" />
|
||||
<div className="h-16 rounded-lg bg-muted animate-pulse" />
|
||||
<div className="h-12 rounded-lg bg-elevated animate-pulse" />
|
||||
<div className="h-32 rounded-lg bg-elevated animate-pulse" />
|
||||
<div className="h-16 rounded-lg bg-elevated animate-pulse" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -63,10 +63,10 @@ export function RestorePreview({
|
||||
if (error || !preview) {
|
||||
return (
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-4 flex items-start gap-3">
|
||||
<XCircle className="h-5 w-5 text-destructive shrink-0 mt-0.5" />
|
||||
<div className="rounded-lg border border-error/30 bg-error/5 p-4 flex items-start gap-3">
|
||||
<XCircle className="h-5 w-5 text-error shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-destructive">Preview failed</p>
|
||||
<p className="text-sm font-medium text-error">Preview failed</p>
|
||||
<p className="text-xs text-text-secondary mt-1">
|
||||
Could not analyze the config. You may still proceed manually.
|
||||
</p>
|
||||
@@ -88,13 +88,13 @@ export function RestorePreview({
|
||||
<div className="space-y-4 p-4">
|
||||
{/* Validation errors */}
|
||||
{!validation.valid && (
|
||||
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-3 space-y-1">
|
||||
<p className="text-sm font-medium text-destructive flex items-center gap-2">
|
||||
<div className="rounded-lg border border-error/30 bg-error/5 p-3 space-y-1">
|
||||
<p className="text-sm font-medium text-error flex items-center gap-2">
|
||||
<XCircle className="h-4 w-4" />
|
||||
Validation errors found
|
||||
</p>
|
||||
{validation.errors.map((err, i) => (
|
||||
<p key={i} className="text-xs text-destructive/80 ml-6">{err}</p>
|
||||
<p key={i} className="text-xs text-error/80 ml-6">{err}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -103,13 +103,13 @@ export function RestorePreview({
|
||||
<div className="rounded-lg border border-border bg-surface-raised p-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<span className="text-success font-mono">+{diff.added}</span>
|
||||
<span className="text-destructive font-mono">-{diff.removed}</span>
|
||||
<span className="text-error font-mono">-{diff.removed}</span>
|
||||
<span className="text-text-secondary">
|
||||
across {changedCategories.length} categor{changedCategories.length === 1 ? 'y' : 'ies'}
|
||||
</span>
|
||||
</div>
|
||||
{hasHighRisk ? (
|
||||
<span className="text-xs font-medium text-destructive flex items-center gap-1">
|
||||
<span className="text-xs font-medium text-error flex items-center gap-1">
|
||||
<Shield className="h-3 w-3" /> High risk
|
||||
</span>
|
||||
) : (
|
||||
@@ -138,7 +138,7 @@ export function RestorePreview({
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{cat.adds > 0 && <span className="text-xs text-success">+{cat.adds}</span>}
|
||||
{cat.removes > 0 && <span className="text-xs text-destructive">-{cat.removes}</span>}
|
||||
{cat.removes > 0 && <span className="text-xs text-error">-{cat.removes}</span>}
|
||||
<span className={cn(
|
||||
'text-xs px-1.5 py-0.5 rounded border',
|
||||
riskBadgeColors[cat.risk],
|
||||
|
||||
@@ -41,11 +41,11 @@ export function RollbackAlert({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-destructive/30 bg-destructive/5 px-4 py-3 flex items-center justify-between">
|
||||
<div className="rounded-lg border border-error/30 bg-error/5 px-4 py-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertTriangle className="h-5 w-5 text-destructive shrink-0" />
|
||||
<AlertTriangle className="h-5 w-5 text-error shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-destructive">
|
||||
<p className="text-sm font-medium text-error">
|
||||
Device went offline after config change
|
||||
</p>
|
||||
<p className="text-xs text-text-secondary mt-0.5">
|
||||
|
||||
@@ -166,7 +166,7 @@ export function RemoteWinBoxButton({ tenantId, deviceId }: RemoteWinBoxButtonPro
|
||||
<button
|
||||
onClick={handleOpen}
|
||||
disabled={createMutation.isPending}
|
||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-md bg-accent text-white hover:bg-accent/90 disabled:opacity-50"
|
||||
>
|
||||
{createMutation.isPending ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
@@ -186,11 +186,11 @@ export function RemoteWinBoxButton({ tenantId, deviceId }: RemoteWinBoxButtonPro
|
||||
</div>
|
||||
{state === 'failed' && error && (
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<p className="text-sm text-destructive">{error}</p>
|
||||
<p className="text-sm text-error">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
{state === 'terminated' && (
|
||||
<p className="mt-2 text-sm text-muted-foreground">Session ended</p>
|
||||
<p className="mt-2 text-sm text-text-muted">Session ended</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
@@ -206,7 +206,7 @@ export function RemoteWinBoxButton({ tenantId, deviceId }: RemoteWinBoxButtonPro
|
||||
{state === 'requesting' ? 'Requesting session...' : 'Provisioning WinBox container...'}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">This may take a few seconds</p>
|
||||
<p className="text-xs text-text-muted">This may take a few seconds</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -234,12 +234,12 @@ export function RemoteWinBoxButton({ tenantId, deviceId }: RemoteWinBoxButtonPro
|
||||
}
|
||||
>
|
||||
{/* Header bar */}
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b bg-muted/50">
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b bg-elevated/50">
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe className="h-4 w-4 text-primary" />
|
||||
<Globe className="h-4 w-4 text-accent" />
|
||||
<span className="text-sm font-medium">Remote WinBox</span>
|
||||
{countdown && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
<span className="text-xs text-text-muted">
|
||||
Expires in {countdown}
|
||||
</span>
|
||||
)}
|
||||
@@ -281,7 +281,7 @@ export function RemoteWinBoxButton({ tenantId, deviceId }: RemoteWinBoxButtonPro
|
||||
// Active but no iframe URL (missing xpra_ws_port) — show reset option
|
||||
return (
|
||||
<div className="rounded-md border p-4 space-y-2">
|
||||
<p className="text-sm text-destructive">Session active but display unavailable</p>
|
||||
<p className="text-sm text-error">Session active but display unavailable</p>
|
||||
<button
|
||||
onClick={handleReset}
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 rounded-md border border-input bg-background hover:bg-accent text-sm"
|
||||
|
||||
@@ -162,7 +162,7 @@ export function SSHTerminal({ tenantId, deviceId, deviceName }: SSHTerminalProps
|
||||
return (
|
||||
<button
|
||||
onClick={handleOpen}
|
||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-md bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-md bg-accent text-white hover:bg-accent/90"
|
||||
>
|
||||
<TerminalIcon className="h-4 w-4" />
|
||||
SSH Terminal
|
||||
@@ -172,14 +172,14 @@ export function SSHTerminal({ tenantId, deviceId, deviceName }: SSHTerminalProps
|
||||
|
||||
return (
|
||||
<div className={`rounded-md border overflow-hidden ${expanded ? 'fixed inset-4 z-50 bg-background' : ''}`}>
|
||||
<div className="flex items-center justify-between px-3 py-2 bg-muted/50 border-b">
|
||||
<div className="flex items-center justify-between px-3 py-2 bg-elevated/50 border-b">
|
||||
<span className="text-sm font-medium">SSH: {deviceName}</span>
|
||||
<div className="flex gap-1">
|
||||
<button onClick={() => setExpanded(!expanded)} className="p-1 hover:bg-accent rounded">
|
||||
{expanded ? <Minimize2 className="h-4 w-4" /> : <Maximize2 className="h-4 w-4" />}
|
||||
</button>
|
||||
{state === 'disconnected' ? (
|
||||
<button onClick={handleReconnect} className="px-2 py-1 text-xs rounded bg-primary text-primary-foreground">
|
||||
<button onClick={handleReconnect} className="px-2 py-1 text-xs rounded bg-accent text-white">
|
||||
Reconnect
|
||||
</button>
|
||||
) : (
|
||||
@@ -191,7 +191,7 @@ export function SSHTerminal({ tenantId, deviceId, deviceName }: SSHTerminalProps
|
||||
</div>
|
||||
<div ref={termRef} className="h-80" tabIndex={0} style={expanded ? { height: 'calc(100% - 40px)' } : {}} />
|
||||
{state === 'connected' && (
|
||||
<div className="px-3 py-1 text-xs text-muted-foreground border-t">
|
||||
<div className="px-3 py-1 text-xs text-text-muted border-t">
|
||||
SSH session active — idle timeout: 15 min
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -78,7 +78,7 @@ export function WinBoxButton({ tenantId, deviceId }: WinBoxButtonProps) {
|
||||
openMutation.mutate()
|
||||
}}
|
||||
disabled={openMutation.isPending}
|
||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-md bg-accent text-white hover:bg-accent/90 disabled:opacity-50"
|
||||
>
|
||||
{openMutation.isPending ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
@@ -87,7 +87,7 @@ export function WinBoxButton({ tenantId, deviceId }: WinBoxButtonProps) {
|
||||
)}
|
||||
{openMutation.isPending ? 'Connecting...' : 'Open WinBox'}
|
||||
</button>
|
||||
{error && <p className="mt-2 text-sm text-destructive">{error}</p>}
|
||||
{error && <p className="mt-2 text-sm text-error">{error}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export function WinBoxButton({ tenantId, deviceId }: WinBoxButtonProps) {
|
||||
return (
|
||||
<div className="rounded-md border p-4 space-y-3">
|
||||
<p className="font-medium text-sm">WinBox tunnel ready</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-sm text-text-muted">
|
||||
Connect to: <code className="font-mono">{tunnelInfo.host}:{tunnelInfo.port}</code>
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
@@ -119,7 +119,7 @@ export function WinBoxButton({ tenantId, deviceId }: WinBoxButtonProps) {
|
||||
Close Tunnel
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-xs text-text-muted">
|
||||
Tunnel closes after 5 min of inactivity
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -36,8 +36,8 @@ const CONNECTION_LABELS: Record<ConnectionState, string> = {
|
||||
// Generate a deterministic color from a string
|
||||
function tenantColor(name: string): string {
|
||||
const colors = [
|
||||
'bg-blue-500', 'bg-emerald-500', 'bg-violet-500', 'bg-amber-500',
|
||||
'bg-rose-500', 'bg-cyan-500', 'bg-pink-500', 'bg-teal-500',
|
||||
'bg-info', 'bg-success', 'bg-accent', 'bg-warning',
|
||||
'bg-error', 'bg-info', 'bg-accent', 'bg-success',
|
||||
]
|
||||
let hash = 0
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
|
||||
@@ -298,7 +298,7 @@ export function Sidebar() {
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Navigation"
|
||||
className="lg:hidden fixed inset-y-0 left-0 z-50 w-[180px] flex flex-col bg-sidebar border-r border-border shadow-xl"
|
||||
className="lg:hidden fixed inset-y-0 left-0 z-50 w-[180px] flex flex-col bg-sidebar border-r border-border"
|
||||
>
|
||||
{sidebarContent(false)}
|
||||
</aside>
|
||||
|
||||
@@ -294,7 +294,7 @@ export function MaintenanceForm({
|
||||
onChange={(e) => setNotes(e.target.value)}
|
||||
placeholder="Reason for maintenance, ticket number, etc."
|
||||
rows={2}
|
||||
className="w-full rounded-md border border-border bg-elevated/50 px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-ring resize-none"
|
||||
className="w-full rounded-md border border-border bg-elevated/50 px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-accent focus:outline-none resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ function CustomTooltip({
|
||||
}: { active?: boolean; payload?: Array<{ value?: number }>; label?: string; unit: string }) {
|
||||
if (!active || !payload?.length) return null
|
||||
return (
|
||||
<div className="rounded border border-border bg-surface px-2 py-1.5 text-xs text-text-primary shadow-lg">
|
||||
<div className="rounded border border-border bg-surface px-2 py-1.5 text-xs text-text-primary">
|
||||
<div className="mb-1 text-text-muted">{label}</div>
|
||||
<div>
|
||||
{(payload[0].value ?? 0).toFixed(1)}
|
||||
|
||||
@@ -36,7 +36,7 @@ function formatBucket(bucket: string, useDate: boolean): string {
|
||||
function CustomTooltip({ active, payload, label }: { active?: boolean; payload?: Array<{ value?: number; dataKey?: string; name?: string; color?: string }>; label?: string }) {
|
||||
if (!active || !payload?.length) return null
|
||||
return (
|
||||
<div className="rounded border border-border bg-surface px-2 py-1.5 text-xs text-text-primary shadow-lg">
|
||||
<div className="rounded border border-border bg-surface px-2 py-1.5 text-xs text-text-primary">
|
||||
<div className="mb-1 text-text-muted">{label}</div>
|
||||
{payload.map((entry) => (
|
||||
<div key={entry.dataKey} className="flex items-center gap-2">
|
||||
|
||||
@@ -99,8 +99,8 @@ function DeviceNode({ data }: NodeProps<DeviceNodeData>) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-lg border bg-surface shadow-md px-3 py-2 min-w-[180px]',
|
||||
'transition-shadow hover:shadow-lg',
|
||||
'rounded-lg border bg-surface px-3 py-2 min-w-[180px]',
|
||||
'transition-colors',
|
||||
isOnline ? 'border-border' : 'border-error/30',
|
||||
)}
|
||||
>
|
||||
@@ -152,7 +152,7 @@ interface TooltipData {
|
||||
function NodeTooltip({ data }: { data: TooltipData; onClose?: () => void }) {
|
||||
return (
|
||||
<div
|
||||
className="absolute z-50 rounded-lg border border-border bg-elevated shadow-lg px-3 py-2 text-xs pointer-events-none"
|
||||
className="absolute z-50 rounded-lg border border-border bg-elevated px-3 py-2 text-xs pointer-events-none"
|
||||
style={{ left: data.x + 10, top: data.y - 10 }}
|
||||
>
|
||||
<div className="font-medium text-text-primary">{data.hostname}</div>
|
||||
@@ -339,7 +339,7 @@ export function TopologyMap({ tenantId }: TopologyMapProps) {
|
||||
>
|
||||
<Background color="hsl(var(--muted))" gap={20} size={1} />
|
||||
<Controls
|
||||
className="!bg-surface !border-border !shadow-md [&>button]:!bg-surface [&>button]:!border-border [&>button]:!text-text-secondary [&>button:hover]:!bg-elevated"
|
||||
className="!bg-surface !border-border [&>button]:!bg-surface [&>button]:!border-border [&>button]:!text-text-secondary [&>button:hover]:!bg-elevated"
|
||||
/>
|
||||
<MiniMap
|
||||
nodeColor={(node) => {
|
||||
|
||||
@@ -232,7 +232,7 @@ export function ReportsPage({ tenantId }: ReportsPageProps) {
|
||||
disabled={generateMutation.isPending || !tenantId}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 px-6 py-2.5 rounded-md text-sm font-medium transition-colors',
|
||||
'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
'bg-accent text-white hover:bg-accent/90',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -264,7 +264,7 @@ export function ApiKeysPage({ tenantId }: ApiKeysPageProps) {
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full rounded-md border border-border-bright bg-elevated/50 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
className="w-full rounded-md border border-border-bright bg-elevated/50 px-3 py-2 text-sm focus:border-accent focus:outline-none"
|
||||
placeholder="e.g. Monitoring Integration"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
@@ -300,7 +300,7 @@ export function ApiKeysPage({ tenantId }: ApiKeysPageProps) {
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
className="w-full rounded-md border border-border-bright bg-elevated/50 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
className="w-full rounded-md border border-border-bright bg-elevated/50 px-3 py-2 text-sm focus:border-accent focus:outline-none"
|
||||
value={expiresAt}
|
||||
onChange={(e) => setExpiresAt(e.target.value)}
|
||||
min={new Date().toISOString().split('T')[0]}
|
||||
|
||||
@@ -215,13 +215,13 @@ export function SettingsPage() {
|
||||
{/* Delete Account */}
|
||||
<div className="flex items-center justify-between py-2 border-t border-border/50">
|
||||
<div>
|
||||
<span className="text-sm text-destructive">Delete Account</span>
|
||||
<span className="text-sm text-error">Delete Account</span>
|
||||
<p className="text-xs text-text-muted">Permanently delete your account and all personal data</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive border-destructive/30 hover:bg-destructive/10"
|
||||
className="text-error border-error/30 hover:bg-error/10"
|
||||
onClick={() => setShowDeleteDialog(true)}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 mr-1.5" />
|
||||
@@ -237,7 +237,7 @@ export function SettingsPage() {
|
||||
}}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-destructive">
|
||||
<DialogTitle className="flex items-center gap-2 text-error">
|
||||
<AlertTriangle className="h-5 w-5" />
|
||||
Delete Account
|
||||
</DialogTitle>
|
||||
@@ -247,8 +247,8 @@ export function SettingsPage() {
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="rounded-md bg-destructive/10 border border-destructive/20 p-3">
|
||||
<p className="text-sm text-destructive font-medium">This will permanently:</p>
|
||||
<div className="rounded-md bg-error/10 border border-error/20 p-3">
|
||||
<p className="text-sm text-error font-medium">This will permanently:</p>
|
||||
<ul className="text-sm text-text-secondary mt-1 space-y-1 list-disc pl-4">
|
||||
<li>Delete your user account</li>
|
||||
<li>Remove all your API keys</li>
|
||||
|
||||
@@ -484,7 +484,7 @@ export function SetupWizard() {
|
||||
<StepIndicator currentStep={step} />
|
||||
|
||||
{/* Card */}
|
||||
<div className="bg-surface border border-border rounded-xl shadow-lg p-8">
|
||||
<div className="bg-surface border border-border rounded-lg p-8">
|
||||
{step === 1 && (
|
||||
<CreateTenantStep
|
||||
onComplete={(tenant) => {
|
||||
|
||||
@@ -201,7 +201,7 @@ export function VpnPage() {
|
||||
<div className="p-6 space-y-6">
|
||||
<h1 className="text-2xl font-bold text-text-primary">VPN</h1>
|
||||
<div className="max-w-lg mx-auto mt-12">
|
||||
<div className="rounded-xl border border-border bg-surface p-8 text-center space-y-6">
|
||||
<div className="rounded-lg border border-border bg-surface p-8 text-center space-y-6">
|
||||
<div className="mx-auto w-16 h-16 rounded-2xl bg-accent/10 flex items-center justify-center">
|
||||
<Shield className="h-8 w-8 text-accent" />
|
||||
</div>
|
||||
@@ -244,8 +244,8 @@ export function VpnPage() {
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium',
|
||||
config.is_enabled
|
||||
? 'bg-green-500/10 text-green-500'
|
||||
: 'bg-yellow-500/10 text-yellow-500',
|
||||
? 'bg-success/10 text-success'
|
||||
: 'bg-warning/10 text-warning',
|
||||
)}
|
||||
>
|
||||
{config.is_enabled ? (
|
||||
@@ -271,7 +271,7 @@ export function VpnPage() {
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-red-400 border-red-800 hover:bg-red-900/30"
|
||||
className="text-error border-error/30 hover:bg-error/10"
|
||||
onClick={() => {
|
||||
if (confirm('Delete VPN configuration? All peers will be removed.')) {
|
||||
deleteMutation.mutate()
|
||||
@@ -310,7 +310,7 @@ export function VpnPage() {
|
||||
{peersLoading ? (
|
||||
<TableSkeleton rows={3} />
|
||||
) : peers.length === 0 ? (
|
||||
<div className="rounded-xl border border-dashed border-accent/30 bg-accent/5 p-8 text-center space-y-3">
|
||||
<div className="rounded-lg border border-dashed border-accent/30 bg-accent/5 p-8 text-center space-y-3">
|
||||
<ShieldCheck className="h-10 w-10 text-accent mx-auto" />
|
||||
<h3 className="text-base font-semibold text-text-primary">VPN is ready</h3>
|
||||
<p className="text-sm text-text-secondary max-w-md mx-auto">
|
||||
@@ -346,7 +346,7 @@ export function VpnPage() {
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
{peer.last_handshake ? (
|
||||
<span className="inline-flex items-center gap-1 text-green-500 text-xs">
|
||||
<span className="inline-flex items-center gap-1 text-success text-xs">
|
||||
<Wifi className="h-3 w-3" /> Connected
|
||||
</span>
|
||||
) : (
|
||||
@@ -373,7 +373,7 @@ export function VpnPage() {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => removePeerMutation.mutate(peer.id)}
|
||||
className="text-red-400 hover:text-red-300"
|
||||
className="text-error hover:text-error/80"
|
||||
title="Remove from VPN"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
@@ -400,7 +400,7 @@ export function VpnPage() {
|
||||
</p>
|
||||
{availableDevices.length === 0 ? (
|
||||
<div className="rounded-lg border border-border bg-elevated/50 p-4 text-center">
|
||||
<CheckCircle className="h-6 w-6 text-green-500 mx-auto mb-2" />
|
||||
<CheckCircle className="h-6 w-6 text-success mx-auto mb-2" />
|
||||
<p className="text-sm font-medium text-text-primary">All devices are on VPN</p>
|
||||
<p className="text-xs text-text-muted mt-1">
|
||||
Every device in your fleet is already connected. Add more devices to your fleet first.
|
||||
@@ -454,7 +454,7 @@ export function VpnPage() {
|
||||
className="absolute top-2 right-2"
|
||||
onClick={() => copyToClipboard(peerConfig.routeros_commands.join('\n'))}
|
||||
>
|
||||
{copied ? <CheckCircle className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
|
||||
{copied ? <CheckCircle className="h-4 w-4 text-success" /> : <Copy className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
|
||||
@@ -672,7 +672,7 @@ function DeviceDetailPage() {
|
||||
|
||||
{/* Interface Utilization */}
|
||||
<div className="rounded-lg border border-border bg-surface p-4">
|
||||
<h3 className="text-sm font-medium text-muted-foreground mb-3">Interface Utilization</h3>
|
||||
<h3 className="text-sm font-medium text-text-muted mb-3">Interface Utilization</h3>
|
||||
<InterfaceGauges tenantId={tenantId} deviceId={deviceId} active={activeTab === 'overview'} />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -13,25 +13,25 @@ export const Route = createFileRoute('/_authenticated/traffic')({
|
||||
|
||||
function cpuColor(cpu: number | null): string {
|
||||
if (cpu === null) return 'text-text-muted'
|
||||
if (cpu < 50) return 'text-emerald-400'
|
||||
if (cpu < 80) return 'text-yellow-400'
|
||||
return 'text-red-400'
|
||||
if (cpu < 50) return 'text-success'
|
||||
if (cpu < 80) return 'text-warning'
|
||||
return 'text-error'
|
||||
}
|
||||
|
||||
function memColor(mem: number | null): string {
|
||||
if (mem === null) return 'text-text-muted'
|
||||
if (mem < 60) return 'text-emerald-400'
|
||||
if (mem < 85) return 'text-yellow-400'
|
||||
return 'text-red-400'
|
||||
if (mem < 60) return 'text-success'
|
||||
if (mem < 85) return 'text-warning'
|
||||
return 'text-error'
|
||||
}
|
||||
|
||||
function statusDot(status: string) {
|
||||
const color =
|
||||
status === 'online'
|
||||
? 'bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,0.6)]'
|
||||
? 'bg-success'
|
||||
: status === 'degraded'
|
||||
? 'bg-yellow-400 shadow-[0_0_6px_rgba(250,204,21,0.6)]'
|
||||
: 'bg-red-400 shadow-[0_0_6px_rgba(248,113,113,0.6)]'
|
||||
? 'bg-warning'
|
||||
: 'bg-error'
|
||||
return <span className={cn('inline-block h-2 w-2 rounded-full', color)} />
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ export const Route = createFileRoute('/_authenticated/wireless')({
|
||||
|
||||
function signalColor(signal: number | null): string {
|
||||
if (signal === null) return 'text-text-muted'
|
||||
if (signal > -60) return 'text-emerald-400'
|
||||
if (signal > -70) return 'text-yellow-400'
|
||||
return 'text-red-400'
|
||||
if (signal > -60) return 'text-success'
|
||||
if (signal > -70) return 'text-warning'
|
||||
return 'text-error'
|
||||
}
|
||||
|
||||
function WirelessPage() {
|
||||
@@ -105,7 +105,7 @@ function WirelessPage() {
|
||||
) : issues.length === 0 ? (
|
||||
<Card className="border-border bg-surface">
|
||||
<CardContent className="flex flex-col items-center justify-center gap-3 p-12">
|
||||
<CheckCircle2 className="h-10 w-10 text-emerald-400" />
|
||||
<CheckCircle2 className="h-10 w-10 text-success" />
|
||||
<p className="text-sm font-medium text-text-secondary">
|
||||
All Clear — no wireless issues detected
|
||||
</p>
|
||||
@@ -153,7 +153,7 @@ function WirelessPage() {
|
||||
>
|
||||
<td className="px-4 py-3 text-sm text-text-secondary">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="inline-block h-2 w-2 rounded-full bg-red-400 shadow-[0_0_6px_rgba(248,113,113,0.6)]" />
|
||||
<span className="inline-block h-2 w-2 rounded-full bg-error" />
|
||||
{issue.hostname}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user