1- import { type MachineRuntime , RUNTIME_LABELS , type UsageWindow } from "@agent-kanban/shared" ;
1+ import { RUNTIME_LABELS , type UsageWindow } from "@agent-kanban/shared" ;
2+ import dayjs from "dayjs" ;
23import { useState } from "react" ;
34import { Link , useNavigate , useParams } from "react-router-dom" ;
45import { Header } from "../components/Header" ;
6+ import { MachineRuntimeList } from "../components/MachineRuntimes" ;
57import { formatRelative } from "../components/TaskDetailFields" ;
68import { Button } from "../components/ui/button" ;
79import { Dialog , DialogContent , DialogDescription , DialogFooter , DialogHeader , DialogTitle } from "../components/ui/dialog" ;
@@ -13,13 +15,16 @@ function usageBarColor(pct: number): string {
1315 return "bg-success" ;
1416}
1517
16- function formatResetCountdown ( resetsAt : string ) : string {
17- const diff = new Date ( resetsAt ) . getTime ( ) - Date . now ( ) ;
18- if ( diff <= 0 ) return "resetting..." ;
19- const h = Math . floor ( diff / 3600000 ) ;
20- const m = Math . floor ( ( diff % 3600000 ) / 60000 ) ;
21- if ( h > 0 ) return `${ h } h ${ m } m` ;
22- return `${ m } m` ;
18+ function usagePercent ( window : UsageWindow ) : number {
19+ return Math . round ( window . utilization < 1 ? window . utilization * 100 : window . utilization ) ;
20+ }
21+
22+ function formatResetTime ( resetsAt : string ) : string {
23+ return dayjs ( resetsAt ) . format ( "MMM D, YYYY h:mm A" ) ;
24+ }
25+
26+ function isPendingReset ( window : UsageWindow ) : boolean {
27+ return new Date ( window . resets_at ) . getTime ( ) > Date . now ( ) ;
2328}
2429
2530const statusDotColors : Record < string , string > = {
@@ -33,18 +38,6 @@ const agentStatusDotColors: Record<string, string> = {
3338 offline : "bg-warning" ,
3439} ;
3540
36- const runtimeStatusColors : Record < string , string > = {
37- ready : "text-accent bg-accent-soft" ,
38- limited : "text-warning bg-warning/10" ,
39- unauthorized : "text-error bg-error/10" ,
40- unhealthy : "text-error bg-error/10" ,
41- missing : "text-content-tertiary bg-surface-tertiary" ,
42- } ;
43-
44- function runtimeLabel ( runtime : MachineRuntime ) : string {
45- return `${ RUNTIME_LABELS [ runtime . name ] ?? runtime . name } :${ runtime . status } ` ;
46- }
47-
4841export function MachineDetailPage ( ) {
4942 const { id } = useParams < { id : string } > ( ) ;
5043 const navigate = useNavigate ( ) ;
@@ -85,6 +78,7 @@ export function MachineDetailPage() {
8578 const isOffline = machine . status === "offline" ;
8679 const apiUrl = window . location . origin ;
8780 const runtimes = machine . runtimes || [ ] ;
81+ const usageWindows = ( ( machine . usage_info ?. windows ?? [ ] ) as UsageWindow [ ] ) . filter ( isPendingReset ) ;
8882
8983 return (
9084 < div className = "min-h-screen bg-surface-primary" >
@@ -135,17 +129,7 @@ export function MachineDetailPage() {
135129 </ div >
136130 < div >
137131 < span className = "text-[11px] text-content-tertiary uppercase tracking-wide block mb-1.5" > Runtimes</ span >
138- { runtimes . length > 0 ? (
139- < div className = "flex gap-1.5 flex-wrap" >
140- { runtimes . map ( ( runtime : MachineRuntime ) => (
141- < span key = { runtime . name } className = { `text-[11px] font-mono px-2 py-0.5 rounded ${ runtimeStatusColors [ runtime . status ] } ` } >
142- { runtimeLabel ( runtime ) }
143- </ span >
144- ) ) }
145- </ div >
146- ) : (
147- < span className = "text-[11px] font-mono text-content-tertiary" > No runtimes detected</ span >
148- ) }
132+ < MachineRuntimeList runtimes = { runtimes } />
149133 </ div >
150134 </ div >
151135
@@ -162,7 +146,7 @@ export function MachineDetailPage() {
162146 </ div >
163147
164148 { /* Usage quota */ }
165- { machine . usage_info && machine . usage_info . windows . length > 0 && (
149+ { machine . usage_info && usageWindows . length > 0 && (
166150 < div className = "bg-surface-secondary border border-border rounded-lg px-5 py-4 space-y-3" >
167151 < div className = "flex items-center justify-between" >
168152 < span className = "text-[11px] font-medium text-content-tertiary uppercase tracking-wide" > Usage</ span >
@@ -171,7 +155,7 @@ export function MachineDetailPage() {
171155 </ span >
172156 </ div >
173157 < div className = "space-y-2.5" >
174- { ( machine . usage_info . windows as UsageWindow [ ] ) . map ( ( w , i ) => (
158+ { usageWindows . map ( ( w , i ) => (
175159 < div key = { `${ w . runtime } -${ i } ` } >
176160 < div className = "flex items-center justify-between mb-1" >
177161 < div className = "flex items-center gap-1.5" >
@@ -181,14 +165,14 @@ export function MachineDetailPage() {
181165 < span className = "text-xs text-content-secondary" > { w . label } </ span >
182166 </ div >
183167 < div className = "flex items-center gap-2" >
184- < span className = "font-mono text-xs text-content-primary" > { Math . round ( w . utilization ) } %</ span >
185- < span className = "text-[11px] text-content-tertiary" > resets { formatResetCountdown ( w . resets_at ) } </ span >
168+ < span className = "font-mono text-xs text-content-primary" > { usagePercent ( w ) } %</ span >
169+ < span className = "text-[11px] text-content-tertiary" > Resets { formatResetTime ( w . resets_at ) } </ span >
186170 </ div >
187171 </ div >
188172 < div className = "h-1.5 bg-surface-tertiary rounded-full overflow-hidden" >
189173 < div
190- className = { `h-full rounded-full transition-all ${ usageBarColor ( w . utilization ) } ` }
191- style = { { width : `${ Math . min ( w . utilization , 100 ) } %` } }
174+ className = { `h-full rounded-full transition-all ${ usageBarColor ( usagePercent ( w ) ) } ` }
175+ style = { { width : `${ Math . min ( usagePercent ( w ) , 100 ) } %` } }
192176 />
193177 </ div >
194178 </ div >
0 commit comments