web: status/severity colors get one truth table
statusColors.ts in primitives — 9 domains, kept distinct on purpose. Eight files stop hand-rolling switches. Known discrepancy documented in place: CommandStatusBadge paints completed gray, both inline command timelines paint it green.
This commit is contained in:
parent
e0f2dd8b07
commit
d75939cb6a
12 changed files with 284 additions and 158 deletions
|
|
@ -19,6 +19,7 @@ import { formatRelativeTime } from '@/lib/utils';
|
|||
import { agentApi, securityApi } from '@/lib/api';
|
||||
import toast from 'react-hot-toast';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { securitySubsystemStatusColor } from '@/components/primitives/statusColors';
|
||||
import { AgentSubsystem } from '@/types';
|
||||
import { AgentUpdatesModal } from './AgentUpdatesModal';
|
||||
|
||||
|
|
@ -596,12 +597,6 @@ export function AgentHealth({ agentId }: AgentHealthProps) {
|
|||
{/* Security Grid - 2x2 Layout */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{Object.entries(securityOverview.subsystems).map(([key, subsystem]) => {
|
||||
const statusColors = {
|
||||
healthy: 'bg-green-100 text-green-700 border-green-200',
|
||||
enforced: 'bg-blue-100 text-blue-700 border-blue-200',
|
||||
degraded: 'bg-amber-100 text-amber-700 border-amber-200',
|
||||
unhealthy: 'bg-red-100 text-red-700 border-red-200'
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={key} className="group relative">
|
||||
|
|
@ -633,7 +628,7 @@ export function AgentHealth({ agentId }: AgentHealthProps) {
|
|||
</div>
|
||||
<div className={cn(
|
||||
'px-1.5 py-0.5 rounded text-[10px] font-medium border',
|
||||
statusColors[subsystem.status as keyof typeof statusColors] || statusColors.unhealthy
|
||||
securitySubsystemStatusColor(subsystem.status)
|
||||
)}>
|
||||
{subsystem.status === 'healthy' && <CheckCircle className="w-3 h-3 inline" />}
|
||||
{subsystem.status === 'enforced' && <Shield className="w-3 h-3 inline" />}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import {
|
|||
CheckCircle,
|
||||
XCircle,
|
||||
} from 'lucide-react';
|
||||
import { formatRelativeTime, formatBytes, getSeverityColor } from '@/lib/utils';
|
||||
import { formatRelativeTime, formatBytes } from '@/lib/utils';
|
||||
import { packageSeverityColor, packageSeverityTextColor } from '@/components/primitives/statusColors';
|
||||
import { updateApi } from '@/lib/api';
|
||||
import toast from 'react-hot-toast';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
|
@ -345,12 +346,7 @@ export function AgentUpdatesEnhanced({ agentId, onNavigateToHistory }: AgentUpda
|
|||
if (count === 0) return null;
|
||||
return (
|
||||
<span key={severity} className="text-gray-500">
|
||||
<span className={cn(
|
||||
'font-medium',
|
||||
severity === 'critical' ? 'text-red-600' :
|
||||
severity === 'high' ? 'text-orange-600' :
|
||||
severity === 'medium' ? 'text-yellow-600' : 'text-blue-600'
|
||||
)}>{count}</span> {severity}
|
||||
<span className={cn('font-medium', packageSeverityTextColor(severity))}>{count}</span> {severity}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
|
|
@ -461,7 +457,7 @@ export function AgentUpdatesEnhanced({ agentId, onNavigateToHistory }: AgentUpda
|
|||
{statusMeta.label}
|
||||
</span>
|
||||
|
||||
<span className={cn('chip', getSeverityColor(update.severity))}>
|
||||
<span className={cn('chip', packageSeverityColor(update.severity))}>
|
||||
{update.severity.toUpperCase()}
|
||||
</span>
|
||||
<span className="text-sm text-gray-900 truncate">{update.package_name}</span>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { useQuery } from '@tanstack/react-query';
|
|||
import { logApi } from '@/lib/api';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { formatRelativeTime } from '@/lib/utils';
|
||||
import { historyResultColor } from '@/components/primitives/statusColors';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
interface HistoryEntry {
|
||||
|
|
@ -188,23 +189,6 @@ const HistoryTimeline: React.FC<HistoryTimelineProps> = ({ agentId, className })
|
|||
}
|
||||
};
|
||||
|
||||
// Get status color
|
||||
const getStatusColor = (result: string) => {
|
||||
switch (result) {
|
||||
case 'success':
|
||||
return 'text-green-700 bg-green-100 border-green-200';
|
||||
case 'failed':
|
||||
return 'text-red-700 bg-red-100 border-red-200';
|
||||
case 'started':
|
||||
return 'text-blue-700 bg-blue-100 border-blue-200';
|
||||
case 'running':
|
||||
return 'text-blue-700 bg-blue-100 border-blue-200';
|
||||
case 'partial':
|
||||
return 'text-amber-700 bg-amber-100 border-amber-200';
|
||||
default:
|
||||
return 'text-gray-700 bg-gray-100 border-gray-200';
|
||||
}
|
||||
};
|
||||
|
||||
// Format duration
|
||||
const formatDuration = (seconds: number) => {
|
||||
|
|
@ -377,7 +361,7 @@ const HistoryTimeline: React.FC<HistoryTimelineProps> = ({ agentId, className })
|
|||
</span>
|
||||
<span className={cn(
|
||||
"badge border",
|
||||
getStatusColor(entry.result)
|
||||
historyResultColor(entry.result)
|
||||
)}>
|
||||
{entry.result}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import React from 'react';
|
|||
import { cn, getStatusColor, getSeverityColor } from '@/lib/utils';
|
||||
|
||||
// State badge primitives — the one way to render a lifecycle status or a
|
||||
// severity as a badge. Colour mapping lives in lib/utils (getStatusColor /
|
||||
// getSeverityColor); these wrap the repeated `cn('badge', ...)` span so pages
|
||||
// stop hand-rolling it (and drifting palettes while they're at it).
|
||||
// severity as a badge. Colour mapping lives in primitives/statusColors.ts
|
||||
// (packageStatusColor / packageSeverityColor); lib/utils re-exports them as
|
||||
// getStatusColor / getSeverityColor for backwards compat.
|
||||
|
||||
interface BadgeProps {
|
||||
/** Extra classes merged onto the badge (e.g. 'text-[10px]', 'ml-auto'). */
|
||||
|
|
|
|||
|
|
@ -16,3 +16,15 @@ export { default as CommandStatusBadge, getCommandStatus } from './CommandStatus
|
|||
export { default as SortableTable } from './SortableTable';
|
||||
export type { Column } from './SortableTable';
|
||||
export { StatusBadge, SeverityBadge } from './StateBadge';
|
||||
export {
|
||||
packageSeverityColor,
|
||||
packageSeverityTextColor,
|
||||
packageStatusColor,
|
||||
lifecycleHistoryStatusColor,
|
||||
commandStatusInlineColor,
|
||||
tokenStatusColor,
|
||||
historyResultColor,
|
||||
securityEventSeverityColor,
|
||||
securityHealthColor,
|
||||
securitySubsystemStatusColor,
|
||||
} from './statusColors';
|
||||
|
|
|
|||
241
web/src/components/primitives/statusColors.ts
Normal file
241
web/src/components/primitives/statusColors.ts
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
// Single source of truth for status/severity -> Tailwind color class mappings.
|
||||
//
|
||||
// Each domain is kept separate even when two domains share identical colors
|
||||
// today — they are semantically distinct and may diverge independently.
|
||||
//
|
||||
// Return value convention: all functions return a string of Tailwind classes
|
||||
// suitable for direct use in className. Badge and border classes are included
|
||||
// where the original code included them; text-only variants return only text
|
||||
// classes.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DOMAIN: package/vuln severity (critical / high / medium / low / unknown)
|
||||
// Used by: StateBadge (SeverityBadge), AgentUpdatesEnhanced severity chips
|
||||
// Source authority: lib/utils.ts getSeverityColor
|
||||
// ---------------------------------------------------------------------------
|
||||
export function packageSeverityColor(severity: string): string {
|
||||
switch (severity) {
|
||||
case 'critical':
|
||||
return 'text-danger-600 bg-danger-100';
|
||||
case 'important':
|
||||
case 'high':
|
||||
return 'text-warning-600 bg-warning-100';
|
||||
case 'moderate':
|
||||
case 'medium':
|
||||
return 'text-info-600 bg-info-100';
|
||||
case 'low':
|
||||
case 'none':
|
||||
return 'text-gray-600 bg-gray-100';
|
||||
default:
|
||||
return 'text-gray-600 bg-gray-100';
|
||||
}
|
||||
}
|
||||
|
||||
// Inline text-only severity accent used in AgentUpdatesEnhanced severity
|
||||
// count chips (e.g. "3 critical"). Only a text color — no bg.
|
||||
export function packageSeverityTextColor(severity: string): string {
|
||||
switch (severity) {
|
||||
case 'critical':
|
||||
return 'text-red-600';
|
||||
case 'high':
|
||||
return 'text-orange-600';
|
||||
case 'medium':
|
||||
return 'text-yellow-600';
|
||||
case 'low':
|
||||
return 'text-blue-600';
|
||||
default:
|
||||
return 'text-gray-600';
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DOMAIN: package lifecycle status
|
||||
// (online/offline/pending/approved/installing/installed/failed/ignored + docker variants)
|
||||
// Used by: StateBadge (StatusBadge)
|
||||
// Source authority: lib/utils.ts getStatusColor
|
||||
// ---------------------------------------------------------------------------
|
||||
export function packageStatusColor(status: string): string {
|
||||
switch (status) {
|
||||
case 'online':
|
||||
return 'text-success-600 bg-success-100';
|
||||
case 'offline':
|
||||
return 'text-danger-600 bg-danger-100';
|
||||
case 'pending':
|
||||
return 'text-warning-600 bg-warning-100';
|
||||
case 'checking_dependencies':
|
||||
return 'text-info-500 bg-info-100';
|
||||
case 'pending_dependencies':
|
||||
return 'text-orange-600 bg-orange-100';
|
||||
case 'approved':
|
||||
return 'text-info-600 bg-info-100';
|
||||
case 'installing':
|
||||
return 'text-indigo-600 bg-indigo-100';
|
||||
case 'installed':
|
||||
return 'text-success-600 bg-success-100';
|
||||
case 'failed':
|
||||
return 'text-danger-600 bg-danger-100';
|
||||
case 'ignored':
|
||||
return 'text-gray-500 bg-gray-100';
|
||||
// Docker image lifecycle (docker_images.status)
|
||||
case 'up-to-date':
|
||||
return 'text-success-600 bg-success-100';
|
||||
case 'update-available':
|
||||
return 'text-info-600 bg-info-100';
|
||||
case 'update-approved':
|
||||
return 'text-orange-600 bg-orange-100';
|
||||
case 'update-scheduled':
|
||||
return 'text-purple-600 bg-purple-100';
|
||||
case 'update-installing':
|
||||
return 'text-indigo-600 bg-indigo-100';
|
||||
case 'update-failed':
|
||||
return 'text-danger-600 bg-danger-100';
|
||||
default:
|
||||
return 'text-gray-600 bg-gray-100';
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DOMAIN: lifecycle history entry status
|
||||
// (installed / failed / rollback — values from update_version_history rows)
|
||||
// Used by: PackageDetail.tsx and Updates.tsx lifecycle history sections
|
||||
// Includes border class — these are rendered as bordered mini-badges.
|
||||
// ---------------------------------------------------------------------------
|
||||
export function lifecycleHistoryStatusColor(status: string): string {
|
||||
switch (status) {
|
||||
case 'installed':
|
||||
return 'bg-green-50 text-green-700 border-green-200';
|
||||
case 'failed':
|
||||
return 'bg-red-50 text-red-700 border-red-200';
|
||||
case 'rollback':
|
||||
return 'bg-amber-50 text-amber-700 border-amber-200';
|
||||
default:
|
||||
return 'bg-gray-50 text-gray-600 border-gray-200';
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DOMAIN: command status (inline badge, not CommandStatusBadge primitive)
|
||||
// (completed / failed / timed_out / cancelled / pending / sent / running)
|
||||
//
|
||||
// DISCREPANCY: CommandStatusBadge (primitives/CommandStatusBadge.tsx) maps
|
||||
// 'completed' -> text-gray-600 bg-gray-50 border-gray-200. Both inline sites
|
||||
// (PackageDetail.tsx and Updates.tsx) map 'completed' -> bg-green-50
|
||||
// text-green-700 border-green-200. The two inline sites agree with each
|
||||
// other. The primitives/CommandStatusBadge.tsx is the odd one out.
|
||||
// Kept as-is per task constraint: deduplication, not restyle.
|
||||
// ---------------------------------------------------------------------------
|
||||
export function commandStatusInlineColor(status: string): string {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return 'bg-green-50 text-green-700 border-green-200';
|
||||
case 'failed':
|
||||
case 'timed_out':
|
||||
return 'bg-red-50 text-red-700 border-red-200';
|
||||
case 'cancelled':
|
||||
return 'bg-gray-50 text-gray-700 border-gray-200';
|
||||
case 'pending':
|
||||
case 'sent':
|
||||
case 'running':
|
||||
default:
|
||||
return 'bg-blue-50 text-blue-700 border-blue-200';
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DOMAIN: registration token status (active / used / expired / revoked)
|
||||
// Used by: TokenManagement.tsx
|
||||
// Returns text-only color — used inside a badge element that supplies bg.
|
||||
// ---------------------------------------------------------------------------
|
||||
export function tokenStatusColor(status: string): string {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
return 'text-green-600';
|
||||
case 'used':
|
||||
return 'text-yellow-600';
|
||||
case 'expired':
|
||||
return 'text-red-600';
|
||||
case 'revoked':
|
||||
return 'text-gray-500';
|
||||
default:
|
||||
return 'text-gray-500';
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DOMAIN: history log result (success / failed / started / running / partial)
|
||||
// Used by: HistoryTimeline.tsx
|
||||
// Includes border class — these are rendered as bordered badge spans.
|
||||
// ---------------------------------------------------------------------------
|
||||
export function historyResultColor(result: string): string {
|
||||
switch (result) {
|
||||
case 'success':
|
||||
return 'text-green-700 bg-green-100 border-green-200';
|
||||
case 'failed':
|
||||
return 'text-red-700 bg-red-100 border-red-200';
|
||||
case 'started':
|
||||
case 'running':
|
||||
return 'text-blue-700 bg-blue-100 border-blue-200';
|
||||
case 'partial':
|
||||
return 'text-amber-700 bg-amber-100 border-amber-200';
|
||||
default:
|
||||
return 'text-gray-700 bg-gray-100 border-gray-200';
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DOMAIN: security event severity (critical / error / warn / info)
|
||||
// Used by: SecurityEvents.tsx
|
||||
// These are audit-log severity levels — semantically distinct from package
|
||||
// vulnerability severity even though 'critical' appears in both.
|
||||
// Includes border class — icon wrapper uses a bordered rounded-lg container.
|
||||
// ---------------------------------------------------------------------------
|
||||
export function securityEventSeverityColor(severity: string): string {
|
||||
switch (severity) {
|
||||
case 'critical':
|
||||
case 'error':
|
||||
return 'text-red-600 bg-red-50 border-red-200';
|
||||
case 'warn':
|
||||
return 'text-yellow-600 bg-yellow-50 border-yellow-200';
|
||||
case 'info':
|
||||
return 'text-blue-600 bg-blue-50 border-blue-200';
|
||||
default:
|
||||
return 'text-gray-600 bg-gray-50 border-gray-200';
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DOMAIN: security overall health (healthy / warning / critical / unknown)
|
||||
// Used by: SecurityStatusCard.tsx
|
||||
// Includes border and text — applied to a bordered container.
|
||||
// ---------------------------------------------------------------------------
|
||||
export function securityHealthColor(overall: string): string {
|
||||
switch (overall) {
|
||||
case 'healthy':
|
||||
return 'bg-green-50 border-green-200 text-green-900';
|
||||
case 'warning':
|
||||
return 'bg-yellow-50 border-yellow-200 text-yellow-900';
|
||||
case 'critical':
|
||||
return 'bg-red-50 border-red-200 text-red-900';
|
||||
default:
|
||||
return 'bg-gray-50 border-gray-200 text-gray-900';
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DOMAIN: security subsystem status (healthy / enforced / degraded / unhealthy)
|
||||
// Used by: AgentHealth.tsx security grid
|
||||
// Includes border class — rendered as bordered pill badges.
|
||||
// ---------------------------------------------------------------------------
|
||||
export function securitySubsystemStatusColor(status: string): string {
|
||||
switch (status) {
|
||||
case 'healthy':
|
||||
return 'bg-green-100 text-green-700 border-green-200';
|
||||
case 'enforced':
|
||||
return 'bg-blue-100 text-blue-700 border-blue-200';
|
||||
case 'degraded':
|
||||
return 'bg-amber-100 text-amber-700 border-amber-200';
|
||||
case 'unhealthy':
|
||||
default:
|
||||
return 'bg-red-100 text-red-700 border-red-200';
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import {
|
|||
import { useSecurityEvents, useSecurityWebSocket } from '@/hooks/useSecuritySettings';
|
||||
import { SecurityEvent, EventFilters } from '@/types/security';
|
||||
import { clientLogger } from '@/lib/client-logger';
|
||||
import { securityEventSeverityColor } from '@/components/primitives/statusColors';
|
||||
|
||||
const SecurityEvents: React.FC = () => {
|
||||
const [filters, setFilters] = useState<EventFilters>({});
|
||||
|
|
@ -52,21 +53,6 @@ const SecurityEvents: React.FC = () => {
|
|||
return staticEvents;
|
||||
}, [eventsData, liveEvents, liveUpdates, pageSize]);
|
||||
|
||||
// Severity color mapping
|
||||
const getSeverityColor = (severity: string) => {
|
||||
switch (severity) {
|
||||
case 'critical':
|
||||
return 'text-red-600 bg-red-50 border-red-200';
|
||||
case 'error':
|
||||
return 'text-red-600 bg-red-50 border-red-200';
|
||||
case 'warn':
|
||||
return 'text-yellow-600 bg-yellow-50 border-yellow-200';
|
||||
case 'info':
|
||||
return 'text-blue-600 bg-blue-50 border-blue-200';
|
||||
default:
|
||||
return 'text-gray-600 bg-gray-50 border-gray-200';
|
||||
}
|
||||
};
|
||||
|
||||
const getSeverityIcon = (severity: string) => {
|
||||
switch (severity) {
|
||||
|
|
@ -415,7 +401,7 @@ const SecurityEvents: React.FC = () => {
|
|||
onClick={() => setSelectedEvent(event)}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`p-2 rounded-lg border ${getSeverityColor(event.severity)}`}>
|
||||
<div className={`p-2 rounded-lg border ${securityEventSeverityColor(event.severity)}`}>
|
||||
{getSeverityIcon(event.severity)}
|
||||
</div>
|
||||
|
||||
|
|
@ -514,7 +500,7 @@ const SecurityEvents: React.FC = () => {
|
|||
<div className="space-y-4">
|
||||
{/* Event Header */}
|
||||
<div className="flex items-start gap-4 pb-4 border-b">
|
||||
<div className={`p-2 rounded-lg border ${getSeverityColor(selectedEvent.severity)}`}>
|
||||
<div className={`p-2 rounded-lg border ${securityEventSeverityColor(selectedEvent.severity)}`}>
|
||||
{getSeverityIcon(selectedEvent.severity)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
Info
|
||||
} from 'lucide-react';
|
||||
import { SecurityStatusCardProps } from '@/types/security';
|
||||
import { securityHealthColor } from '@/components/primitives/statusColors';
|
||||
|
||||
const SecurityStatusCard: React.FC<SecurityStatusCardProps> = ({
|
||||
status,
|
||||
|
|
@ -31,18 +32,6 @@ const SecurityStatusCard: React.FC<SecurityStatusCardProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const getStatusColor = () => {
|
||||
switch (status.overall) {
|
||||
case 'healthy':
|
||||
return 'bg-green-50 border-green-200 text-green-900';
|
||||
case 'warning':
|
||||
return 'bg-yellow-50 border-yellow-200 text-yellow-900';
|
||||
case 'critical':
|
||||
return 'bg-red-50 border-red-200 text-red-900';
|
||||
default:
|
||||
return 'bg-gray-50 border-gray-200 text-gray-900';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusText = () => {
|
||||
switch (status.overall) {
|
||||
|
|
@ -75,7 +64,7 @@ const SecurityStatusCard: React.FC<SecurityStatusCardProps> = ({
|
|||
<div className="bg-white border border-gray-200 rounded-lg p-6">
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`p-3 rounded-lg border ${getStatusColor()}`}>
|
||||
<div className={`p-3 rounded-lg border ${securityHealthColor(status.overall)}`}>
|
||||
{getStatusIcon()}
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -177,7 +166,7 @@ const SecurityStatusCard: React.FC<SecurityStatusCardProps> = ({
|
|||
|
||||
{/* Alert Section */}
|
||||
{status.overall !== 'healthy' && (
|
||||
<div className={`alert ${getStatusColor()}`}>
|
||||
<div className={`alert ${securityHealthColor(status.overall)}`}>
|
||||
<div className="flex items-start gap-3">
|
||||
{status.overall === 'warning' ? (
|
||||
<AlertTriangle className="w-5 h-5 text-yellow-600 flex-shrink-0 mt-0.5" />
|
||||
|
|
|
|||
|
|
@ -116,64 +116,13 @@ export const versionCompare = (v1: string, v2: string): number => {
|
|||
return 0;
|
||||
};
|
||||
|
||||
// Status and severity utilities
|
||||
export const getStatusColor = (status: string): string => {
|
||||
switch (status) {
|
||||
case 'online':
|
||||
return 'text-success-600 bg-success-100';
|
||||
case 'offline':
|
||||
return 'text-danger-600 bg-danger-100';
|
||||
case 'pending':
|
||||
return 'text-warning-600 bg-warning-100';
|
||||
case 'checking_dependencies':
|
||||
return 'text-info-500 bg-info-100';
|
||||
case 'pending_dependencies':
|
||||
return 'text-orange-600 bg-orange-100';
|
||||
case 'approved':
|
||||
return 'text-info-600 bg-info-100';
|
||||
case 'installing':
|
||||
return 'text-indigo-600 bg-indigo-100';
|
||||
case 'installed':
|
||||
return 'text-success-600 bg-success-100';
|
||||
case 'failed':
|
||||
return 'text-danger-600 bg-danger-100';
|
||||
case 'ignored':
|
||||
return 'text-gray-500 bg-gray-100';
|
||||
// Docker image lifecycle (docker_images.status)
|
||||
case 'up-to-date':
|
||||
return 'text-success-600 bg-success-100';
|
||||
case 'update-available':
|
||||
return 'text-info-600 bg-info-100';
|
||||
case 'update-approved':
|
||||
return 'text-orange-600 bg-orange-100';
|
||||
case 'update-scheduled':
|
||||
return 'text-purple-600 bg-purple-100';
|
||||
case 'update-installing':
|
||||
return 'text-indigo-600 bg-indigo-100';
|
||||
case 'update-failed':
|
||||
return 'text-danger-600 bg-danger-100';
|
||||
default:
|
||||
return 'text-gray-600 bg-gray-100';
|
||||
}
|
||||
};
|
||||
// Status and severity utilities — thin re-exports delegating to the canonical
|
||||
// truth table in components/primitives/statusColors.ts. New code should import
|
||||
// from there directly; these shims keep existing callers working unchanged.
|
||||
import { packageStatusColor, packageSeverityColor } from '@/components/primitives/statusColors';
|
||||
|
||||
export const getSeverityColor = (severity: string): string => {
|
||||
switch (severity) {
|
||||
case 'critical':
|
||||
return 'text-danger-600 bg-danger-100';
|
||||
case 'important':
|
||||
case 'high':
|
||||
return 'text-warning-600 bg-warning-100';
|
||||
case 'moderate':
|
||||
case 'medium':
|
||||
return 'text-info-600 bg-info-100';
|
||||
case 'low':
|
||||
case 'none':
|
||||
return 'text-gray-600 bg-gray-100';
|
||||
default:
|
||||
return 'text-gray-600 bg-gray-100';
|
||||
}
|
||||
};
|
||||
export const getStatusColor = packageStatusColor;
|
||||
export const getSeverityColor = packageSeverityColor;
|
||||
|
||||
export const getPackageTypeIcon = (type: string): string => {
|
||||
switch (type) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import {
|
|||
formatRelativeTime,
|
||||
} from '@/lib/utils';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { lifecycleHistoryStatusColor, commandStatusInlineColor } from '@/components/primitives/statusColors';
|
||||
import { StatusBadge, SeverityBadge } from '@/components/primitives';
|
||||
import toast from 'react-hot-toast';
|
||||
import DependencyClosureTree from '@/components/DependencyClosureTree';
|
||||
|
|
@ -531,12 +532,7 @@ const AgentDetailPane: React.FC<AgentDetailPaneProps> = ({
|
|||
</h2>
|
||||
<ul className="divide-y divide-gray-100">
|
||||
{lifecycleData.history.map((h: any) => {
|
||||
const statusColors: Record<string, string> = {
|
||||
installed: 'bg-green-50 text-green-700 border-green-200',
|
||||
failed: 'bg-red-50 text-red-700 border-red-200',
|
||||
rollback: 'bg-amber-50 text-amber-700 border-amber-200',
|
||||
};
|
||||
const cls = statusColors[h.update_status] || 'bg-gray-50 text-gray-600 border-gray-200';
|
||||
const cls = lifecycleHistoryStatusColor(h.update_status);
|
||||
const reason =
|
||||
h.update_status === 'failed'
|
||||
? h.failure_reason || h.metadata?.failure_reason
|
||||
|
|
@ -589,14 +585,7 @@ const AgentDetailPane: React.FC<AgentDetailPaneProps> = ({
|
|||
)}
|
||||
<span className="font-mono text-gray-700 flex-shrink-0">{cmd.action}</span>
|
||||
<span
|
||||
className={cn(
|
||||
'text-[10px] border rounded px-1 py-0.5 flex-shrink-0',
|
||||
cmd.status === 'completed'
|
||||
? 'bg-green-50 text-green-700 border-green-200'
|
||||
: cmd.status === 'failed'
|
||||
? 'bg-red-50 text-red-700 border-red-200'
|
||||
: 'bg-gray-50 text-gray-600 border-gray-200'
|
||||
)}
|
||||
className={cn('text-[10px] border rounded px-1 py-0.5 flex-shrink-0', commandStatusInlineColor(cmd.status))}
|
||||
>
|
||||
{cmd.status}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
} from '../hooks/useRegistrationTokens';
|
||||
import { RegistrationToken, CreateRegistrationTokenRequest } from '@/types';
|
||||
import { formatDateTime } from '@/lib/utils';
|
||||
import { tokenStatusColor } from '@/components/primitives/statusColors';
|
||||
|
||||
const TokenManagement: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -98,13 +99,6 @@ const TokenManagement: React.FC = () => {
|
|||
};
|
||||
|
||||
|
||||
const getStatusColor = (token: RegistrationToken) => {
|
||||
if (token.status === 'revoked') return 'text-gray-500';
|
||||
if (token.status === 'expired') return 'text-red-600';
|
||||
if (token.status === 'used') return 'text-yellow-600';
|
||||
if (token.status === 'active') return 'text-green-600';
|
||||
return 'text-gray-500';
|
||||
};
|
||||
|
||||
const getStatusText = (token: RegistrationToken) => {
|
||||
if (token.status === 'revoked') return 'Revoked';
|
||||
|
|
@ -453,7 +447,7 @@ const TokenManagement: React.FC = () => {
|
|||
<div className="text-sm font-medium text-gray-900">{token.label}</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className={`badge badge-lg ${getStatusColor(token)}`}>
|
||||
<div className={`badge badge-lg ${tokenStatusColor(token.status)}`}>
|
||||
{getStatusText(token)}
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import { useRecentCommands } from '@/hooks/useCommands';
|
|||
import type { UpdatePackage } from '@/types';
|
||||
import { getPackageTypeIcon, formatBytes, formatRelativeTime } from '@/lib/utils';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { lifecycleHistoryStatusColor, commandStatusInlineColor } from '@/components/primitives/statusColors';
|
||||
import toast from 'react-hot-toast';
|
||||
import { updateApi } from '@/lib/api';
|
||||
import DependencyClosureTree from '@/components/DependencyClosureTree';
|
||||
|
|
@ -852,12 +853,7 @@ const Updates: React.FC = () => {
|
|||
</h2>
|
||||
<ul className="divide-y divide-gray-100">
|
||||
{lifecycleData.history.map((h: any) => {
|
||||
const statusColors: Record<string, string> = {
|
||||
installed: 'bg-green-50 text-green-700 border-green-200',
|
||||
failed: 'bg-red-50 text-red-700 border-red-200',
|
||||
rollback: 'bg-amber-50 text-amber-700 border-amber-200',
|
||||
};
|
||||
const cls = statusColors[h.update_status] || 'bg-gray-50 text-gray-600 border-gray-200';
|
||||
const cls = lifecycleHistoryStatusColor(h.update_status);
|
||||
const reason = h.update_status === 'failed'
|
||||
? (h.failure_reason || h.metadata?.failure_reason)
|
||||
: null;
|
||||
|
|
@ -900,14 +896,9 @@ const Updates: React.FC = () => {
|
|||
) : (
|
||||
<ul className="space-y-2">
|
||||
{updateCommands.map((cmd: any) => {
|
||||
const statusColor =
|
||||
cmd.status === 'completed' ? 'bg-green-50 text-green-700 border-green-200' :
|
||||
cmd.status === 'failed' || cmd.status === 'timed_out' ? 'bg-red-50 text-red-700 border-red-200' :
|
||||
cmd.status === 'cancelled' ? 'bg-gray-50 text-gray-700 border-gray-200' :
|
||||
'bg-blue-50 text-blue-700 border-blue-200';
|
||||
return (
|
||||
<li key={cmd.id} className="flex items-center gap-3 py-2 px-3 bg-white border border-gray-100 rounded">
|
||||
<span className={cn('text-xs font-medium border rounded px-2 py-0.5', statusColor)}>
|
||||
<span className={cn('text-xs font-medium border rounded px-2 py-0.5', commandStatusInlineColor(cmd.status))}>
|
||||
{cmd.status}
|
||||
</span>
|
||||
<span className="text-sm text-gray-900 font-mono truncate flex-1">
|
||||
|
|
|
|||
Loading…
Reference in a new issue