Visual redesign: design system, refined ops console aesthetic
- Full component library (Button, Input, Label, Select, Modal, Table, Badge, Card, etc.) - Tailwind design tokens: IBM Plex Sans + JetBrains Mono, teal accent, semantic status colors - NavBar with logo, responsive hamburger menu, real logout - All pages redesigned: Login, Dashboard (KPI cards), Machines, SyncPairs, JobHistory, JobDetail, SSHKeys, Settings - Fixed: hover:bg-gray-750 dead class, window.location.href navigation bug - Replaced alert()/confirm() with sonner toasts and accessible modals - Added ErrorBoundary, skip link, accessible modal dialogs (Radix) - Icons: lucide-react throughout, copy/download buttons - 1.0.5 → 1.0.6
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
export const badgeVariants = cva(
|
||||
'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium transition-colors',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
running: 'bg-emerald-500/15 text-emerald-400 border border-emerald-500/30',
|
||||
pending: 'bg-amber-500/15 text-amber-400 border border-amber-500/30',
|
||||
waking: 'bg-amber-500/15 text-amber-400 border border-amber-500/30',
|
||||
success: 'bg-emerald-500/15 text-emerald-400 border border-emerald-500/30',
|
||||
error: 'bg-rose-500/15 text-rose-400 border border-rose-500/30',
|
||||
info: 'bg-sky-500/15 text-sky-400 border border-sky-500/30',
|
||||
neutral: 'bg-zinc-500/15 text-zinc-400 border border-zinc-500/30',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'neutral',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export type BadgeVariant = VariantProps<typeof badgeVariants>['variant']
|
||||
|
||||
const STATUS_TO_VARIANT: Record<string, BadgeVariant> = {
|
||||
running: 'running',
|
||||
pending: 'pending',
|
||||
waking: 'waking',
|
||||
success: 'success',
|
||||
error: 'error',
|
||||
failed: 'error',
|
||||
completed: 'success',
|
||||
cancelled: 'neutral',
|
||||
info: 'info',
|
||||
unknown: 'neutral',
|
||||
}
|
||||
|
||||
export function statusVariant(status: string): BadgeVariant {
|
||||
return STATUS_TO_VARIANT[status.toLowerCase()] ?? 'neutral'
|
||||
}
|
||||
|
||||
export function statusLabel(status: string): string {
|
||||
const labels: Record<string, string> = {
|
||||
running: 'Running',
|
||||
pending: 'Pending',
|
||||
waking: 'Waking',
|
||||
success: 'Success',
|
||||
error: 'Error',
|
||||
failed: 'Failed',
|
||||
completed: 'Completed',
|
||||
cancelled: 'Cancelled',
|
||||
info: 'Info',
|
||||
unknown: 'Unknown',
|
||||
}
|
||||
return labels[status.toLowerCase()] ?? status
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function formatDuration(ms: number): string {
|
||||
if (ms < 1000) return `${ms}ms`
|
||||
const seconds = Math.floor(ms / 1000)
|
||||
if (seconds < 60) return `${seconds}s`
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const remainingSeconds = seconds % 60
|
||||
if (minutes < 60) return `${minutes}m ${remainingSeconds}s`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const remainingMinutes = minutes % 60
|
||||
return `${hours}h ${remainingMinutes}m`
|
||||
}
|
||||
|
||||
export function formatRelativeTime(date: Date | string): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - d.getTime()
|
||||
const seconds = Math.floor(diff / 1000)
|
||||
if (seconds < 60) return 'just now'
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
if (minutes < 60) return `${minutes}m ago`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
if (hours < 24) return `${hours}h ago`
|
||||
const days = Math.floor(hours / 24)
|
||||
if (days < 7) return `${days}d ago`
|
||||
return d.toLocaleDateString()
|
||||
}
|
||||
Reference in New Issue
Block a user