Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ export interface DashboardVPSRoute {
vpsInstanceId?: string;
assignmentCount: number;
peakAssignmentCount: number;
provisionReason?: string;
deprecationReason?: string;
trackName: string;
protocolName: string;
locationName?: string;
Expand Down
54 changes: 46 additions & 8 deletions src/components/VPSOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ function formatUptime(createdISO: string): string {
return `${minutes}m`;
}

// Short human-friendly labels for the reason codes the API stamps on
// vps_routes.provision_reason / deprecation_reason. Unknown codes fall
// through with underscores replaced by spaces so they still read cleanly
// on screen (e.g. a future reason code ships before the UI is updated).
const REASON_LABELS: Record<string, string> = {
pool_deficit: "pool deficit",
capacity_scale_up: "capacity scale-up",
admin_create: "admin create",
blocked_grace: "blocked (grace)",
manual: "manual",
track_deleted: "track deleted",
force_release: "force released",
};

function formatReason(code: string): string {
return REASON_LABELS[code] || code.replace(/_/g, " ");
}
Comment on lines +37 to +53

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REASON_LABELS/formatReason duplicates the reason-code mapping logic already present in src/components/ProtocolFeed.tsx (including overlapping keys like pool_deficit, capacity_scale_up, blocked_grace, etc.), but with different labels (e.g. blocked_grace is rendered as "blocked (grace elapsed)" in the feed vs "blocked (grace)" here). This will drift over time and can produce inconsistent operator-facing explanations. Consider extracting a shared reason-code formatter (e.g. formatRouteReason(code) in a small src/utils/routeReasons.ts) and reusing it in both places so the labels stay consistent.

Copilot uses AI. Check for mistakes.

interface RegionGroup {
regionName: string;
city?: string;
Expand Down Expand Up @@ -415,16 +433,36 @@ function VPSOverview({ routes, summary, isLoading, error }: VPSOverviewProps) {
<span style={{ color: "#667080" }}> / {route.peakAssignmentCount}</span>
</span>

{/* Status / Deprecated badge */}
<span>
{/* Status / Deprecated badge + reason chip */}
<span style={{ display: "inline-flex", alignItems: "center", gap: "0.3rem", flexWrap: "wrap" }}>
{isDeprecated ? (
<span style={{ ...badgeBase, background: "#e0606018", color: "#e06060", border: "1px solid #e0606030" }}>
deprecated
</span>
<>
<span style={{ ...badgeBase, background: "#e0606018", color: "#e06060", border: "1px solid #e0606030" }}>
deprecated
</span>
{route.deprecationReason && (
<span
style={{ ...badgeBase, background: "#e0606010", color: "#d08080", border: "1px solid #e0606020" }}
title={`deprecation_reason: ${route.deprecationReason}`}
>
{formatReason(route.deprecationReason)}
</span>
)}
</>
) : (
<span style={{ ...badgeBase, background: `${sc}18`, color: sc, border: `1px solid ${sc}30` }}>
{route.status}
</span>
<>
<span style={{ ...badgeBase, background: `${sc}18`, color: sc, border: `1px solid ${sc}30` }}>
{route.status}
</span>
{route.provisionReason && route.status !== "running" && (
<span
style={{ ...badgeBase, background: "#ffffff06", color: "#8890a0", border: "1px solid #ffffff10" }}
title={`provision_reason: ${route.provisionReason}`}
>
{formatReason(route.provisionReason)}
</span>
)}
</>
)}
</span>

Expand Down