@@ -2,7 +2,6 @@ import { useState } from 'react'
22import { Link , useNavigate } from 'react-router-dom'
33import { Button } from '@/components/ui/button'
44import { Badge } from '@/components/ui/badge'
5- import { EmptyState } from '@/components/ui/empty-state'
65import { SkeletonTable } from '@/components/ui/skeleton'
76import {
87 Dialog ,
@@ -12,11 +11,10 @@ import {
1211 DialogHeader ,
1312 DialogTitle ,
1413} from '@/components/ui/dialog'
15- import { DeploymentStatusBadge } from './DeploymentStatusBadge'
1614import { useDeleteDeployment , type DeploymentStatus } from '@/hooks/useDeployments'
1715import { useToast } from '@/hooks/useToast'
1816import { formatRelativeTime , generateAynaUrl } from '@/lib/utils'
19- import { Eye , Trash2 , MessageSquare } from 'lucide-react'
17+ import { Eye , Trash2 , MessageSquare , Rocket } from 'lucide-react'
2018
2119interface DeploymentListProps {
2220 deployments : DeploymentStatus [ ]
@@ -25,13 +23,34 @@ interface DeploymentListProps {
2523
2624function getProviderBadgeClass ( provider : string ) : string {
2725 switch ( provider ) {
28- case 'kuberay' : return 'bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300'
29- case 'kaito' : return 'bg-purple-100 text-purple-700 dark:bg-purple-950 dark:text-purple-300'
30- case 'llmd' : return 'bg-orange-100 text-orange-700 dark:bg-orange-950 dark:text-orange-300'
31- default : return 'bg-green-100 text-green-700 dark:bg-green-950 dark:text-green-300'
26+ case 'kuberay' : return 'bg-blue-500/10 text-blue-400 border-blue-500/20'
27+ case 'kaito' : return 'bg-purple-500/10 text-purple-400 border-purple-500/20'
28+ case 'llmd' : return 'bg-orange-500/10 text-orange-400 border-orange-500/20'
29+ case 'dynamo' : return 'bg-teal-500/10 text-teal-400 border-teal-500/20'
30+ default : return 'bg-green-500/10 text-green-400 border-green-500/20'
3231 }
3332}
3433
34+ function getStatusDotColor ( phase : DeploymentStatus [ 'phase' ] ) : string {
35+ switch ( phase ) {
36+ case 'Running' : return 'bg-green-500'
37+ case 'Pending' : return 'bg-amber-400 animate-pulse'
38+ case 'Deploying' : return 'bg-blue-500 animate-pulse'
39+ case 'Failed' : return 'bg-red-400'
40+ case 'Terminating' : return 'bg-slate-400 animate-pulse'
41+ default : return 'bg-slate-500'
42+ }
43+ }
44+
45+ function getReplicaColorClass ( deployment : DeploymentStatus ) : string {
46+ if ( deployment . mode === 'disaggregated' && deployment . prefillReplicas && deployment . decodeReplicas ) {
47+ const allReady = deployment . prefillReplicas . ready === deployment . prefillReplicas . desired &&
48+ deployment . decodeReplicas . ready === deployment . decodeReplicas . desired
49+ return allReady ? 'text-green-400' : 'text-amber-400'
50+ }
51+ return deployment . replicas . ready === deployment . replicas . desired ? 'text-green-400' : 'text-amber-400'
52+ }
53+
3554function getProviderDisplayName ( provider : string ) : string {
3655 switch ( provider ) {
3756 case 'kuberay' : return 'KubeRay'
@@ -95,72 +114,77 @@ export function DeploymentList({ deployments, isLoading }: DeploymentListProps)
95114 // Empty state
96115 if ( deployments . length === 0 ) {
97116 return (
98- < EmptyState
99- preset = "no-deployments"
100- title = "No deployments yet"
101- description = "Deploy your first model to start serving inference requests. Choose from our curated model library or search HuggingFace."
102- actionLabel = "Browse Models"
103- onAction = { ( ) => navigate ( '/' ) }
104- />
117+ < div className = "glass-panel flex flex-col items-center justify-center py-16 text-center" >
118+ < Rocket className = "h-12 w-12 text-muted-foreground/50 mb-4" />
119+ < h3 className = "text-lg font-medium text-foreground mb-1" > No deployments yet</ h3 >
120+ < p className = "text-sm text-muted-foreground mb-6 max-w-md" >
121+ Deploy your first model to start serving inference requests.
122+ </ p >
123+ < Button onClick = { ( ) => navigate ( '/' ) } className = "bg-cyan-600 hover:bg-cyan-700 text-white" >
124+ Deploy your first model
125+ </ Button >
126+ </ div >
105127 )
106128 }
107129
108130 return (
109131 < >
110- { /* Mobile Card View */ }
111- < div className = "md:hidden space-y-3" >
132+ { /* Card-based rows */ }
133+ < div className = "space-y-3" >
112134 { deployments . map ( ( deployment , index ) => (
113135 < div
114136 key = { deployment . name }
115- className = "rounded-lg border shadow-soft-sm p -4 space-y-3 bg-card "
137+ className = "glass-panel !p-4 flex items-center gap -4 group hover:bg-white/5 hover:border-white/10 transition-all duration-200 "
116138 style = { { animationDelay : `${ index * 50 } ms` } }
117139 >
118- { /* Header: Name and Status */ }
119- < div className = "flex items-start justify-between gap-2" >
140+ { /* Status dot */ }
141+ < div className = "shrink-0" >
142+ < span className = { `h-3 w-3 rounded-full inline-block ${ getStatusDotColor ( deployment . phase ) } ` } />
143+ </ div >
144+
145+ { /* Name & Model */ }
146+ < div className = "flex-1 min-w-0" >
120147 < Link
121148 to = { `/deployments/${ deployment . name } ?namespace=${ deployment . namespace } ` }
122- className = "font-medium hover:text-primary transition-colors text-base break-all "
149+ className = "font-medium text-foreground hover:text-primary transition-colors"
123150 >
124151 { deployment . name }
125152 </ Link >
126- < DeploymentStatusBadge phase = { deployment . phase } />
153+ < p className = "text-sm text-muted-foreground truncate" >
154+ { deployment . modelId }
155+ </ p >
127156 </ div >
128157
129- { /* Model */ }
130- < p className = "text-sm text-muted-foreground break-all" >
131- { deployment . modelId }
132- </ p >
133-
134- { /* Badges Row */ }
135- < div className = "flex flex-wrap items-center gap-2" >
136- < Badge variant = "outline" >
137- { deployment . engine ? ( deployment . engine === 'llamacpp' ? 'Llama.cpp' : deployment . engine . toUpperCase ( ) ) : 'Pending' }
138- </ Badge >
158+ { /* Badges (hidden on small screens) */ }
159+ < div className = "hidden md:flex items-center gap-2" >
139160 < Badge
140161 variant = "secondary"
141162 className = { getProviderBadgeClass ( deployment . provider ) }
142163 >
143164 { getProviderDisplayName ( deployment . provider ) }
144165 </ Badge >
166+ < Badge variant = "outline" >
167+ { deployment . engine ? ( deployment . engine === 'llamacpp' ? 'Llama.cpp' : deployment . engine . toUpperCase ( ) ) : 'Pending' }
168+ </ Badge >
145169 { deployment . mode === 'disaggregated' && (
146170 < Badge variant = "secondary" className = "text-xs" > P/D</ Badge >
147171 ) }
148- </ div >
149-
150- { /* Meta Row */ }
151- < div className = "flex items-center justify-between text-sm text-muted-foreground pt-1 border-t" >
152- < span title = { deployment . mode === 'disaggregated' ? 'Prefill / Decode replicas' : 'Worker replicas' } >
153- Replicas: { formatReplicaStatus ( deployment ) }
172+ < span
173+ className = { `text-sm tabular-nums ${ getReplicaColorClass ( deployment ) } ` }
174+ title = { deployment . mode === 'disaggregated' ? 'Prefill / Decode replicas' : 'Worker replicas' }
175+ >
176+ { formatReplicaStatus ( deployment ) } ready
154177 </ span >
155- < span > { formatRelativeTime ( deployment . createdAt ) } </ span >
156178 </ div >
157179
158- { /* Actions */ }
159- < div className = "flex items-center gap-2 pt-2 border-t" >
160- < Link to = { `/deployments/${ deployment . name } ?namespace=${ deployment . namespace } ` } className = "flex-1" >
161- < Button size = "sm" variant = "outline" className = "w-full" >
162- < Eye className = "h-4 w-4 mr-2" />
163- View
180+ { /* Age & Actions */ }
181+ < div className = "flex items-center gap-1" >
182+ < span className = "text-sm text-muted-foreground hidden lg:inline mr-2" >
183+ { formatRelativeTime ( deployment . createdAt ) }
184+ </ span >
185+ < Link to = { `/deployments/${ deployment . name } ?namespace=${ deployment . namespace } ` } >
186+ < Button size = "sm" variant = "ghost" title = "View details" >
187+ < Eye className = "h-4 w-4" />
164188 </ Button >
165189 </ Link >
166190 < a
@@ -170,18 +194,18 @@ export function DeploymentList({ deployments, isLoading }: DeploymentListProps)
170194 endpoint : 'http://localhost:8000' ,
171195 type : 'chat' ,
172196 } ) }
173- className = "flex-1 "
197+ title = "Open in Ayna "
174198 >
175- < Button size = "sm" variant = "outline" className = "w-full" >
176- < MessageSquare className = "h-4 w-4 mr-2" />
177- Chat
199+ < Button size = "sm" variant = "ghost" >
200+ < MessageSquare className = "h-4 w-4" />
178201 </ Button >
179202 </ a >
180203 < Button
181204 size = "sm"
182- variant = "outline "
205+ variant = "ghost "
183206 onClick = { ( ) => setDeleteTarget ( deployment ) }
184- className = "text-destructive hover:text-destructive"
207+ title = "Delete deployment"
208+ className = "text-red-400 hover:bg-red-500/10 hover:text-red-400"
185209 >
186210 < Trash2 className = "h-4 w-4" />
187211 </ Button >
@@ -190,106 +214,6 @@ export function DeploymentList({ deployments, isLoading }: DeploymentListProps)
190214 ) ) }
191215 </ div >
192216
193- { /* Desktop Table View */ }
194- < div className = "hidden md:block rounded-lg border shadow-soft-sm overflow-x-auto" >
195- < table className = "w-full min-w-[600px]" >
196- < thead >
197- < tr className = "border-b bg-muted/50" >
198- < th className = "px-4 py-3 text-left text-sm font-medium whitespace-nowrap" > Name</ th >
199- < th className = "px-4 py-3 text-left text-sm font-medium whitespace-nowrap" > Model</ th >
200- < th className = "px-4 py-3 text-left text-sm font-medium whitespace-nowrap hidden lg:table-cell" > Engine</ th >
201- < th className = "px-4 py-3 text-left text-sm font-medium whitespace-nowrap hidden lg:table-cell" > Runtime</ th >
202- < th className = "px-4 py-3 text-left text-sm font-medium whitespace-nowrap" > Status</ th >
203- < th className = "px-4 py-3 text-left text-sm font-medium whitespace-nowrap hidden xl:table-cell" > Replicas</ th >
204- < th className = "px-4 py-3 text-left text-sm font-medium whitespace-nowrap" > Age</ th >
205- < th className = "px-4 py-3 text-right text-sm font-medium whitespace-nowrap" > Actions</ th >
206- </ tr >
207- </ thead >
208- < tbody >
209- { deployments . map ( ( deployment , index ) => (
210- < tr
211- key = { deployment . name }
212- className = "border-b last:border-0 hover:bg-muted/30 transition-colors duration-150"
213- style = { { animationDelay : `${ index * 50 } ms` } }
214- >
215- < td className = "px-4 py-3" >
216- < Link
217- to = { `/deployments/${ deployment . name } ?namespace=${ deployment . namespace } ` }
218- className = "font-medium hover:text-primary transition-colors whitespace-nowrap"
219- >
220- { deployment . name }
221- </ Link >
222- </ td >
223- < td className = "px-4 py-3" >
224- < span className = "text-sm text-muted-foreground truncate max-w-[200px] block" >
225- { deployment . modelId }
226- </ span >
227- </ td >
228- < td className = "px-4 py-3 hidden lg:table-cell" >
229- < Badge variant = "outline" >
230- { deployment . engine ? ( deployment . engine === 'llamacpp' ? 'Llama.cpp' : deployment . engine . toUpperCase ( ) ) : 'Pending' }
231- </ Badge >
232- </ td >
233- < td className = "px-4 py-3 hidden lg:table-cell" >
234- < Badge
235- variant = "secondary"
236- className = { getProviderBadgeClass ( deployment . provider ) }
237- >
238- { getProviderDisplayName ( deployment . provider ) }
239- </ Badge >
240- </ td >
241- < td className = "px-4 py-3" >
242- < DeploymentStatusBadge phase = { deployment . phase } />
243- </ td >
244- < td className = "px-4 py-3 hidden xl:table-cell" >
245- < span className = "text-sm whitespace-nowrap" title = { deployment . mode === 'disaggregated' ? 'Prefill / Decode replicas' : 'Worker replicas' } >
246- { formatReplicaStatus ( deployment ) }
247- </ span >
248- { deployment . mode === 'disaggregated' && (
249- < Badge variant = "secondary" className = "ml-2 text-xs" > P/D</ Badge >
250- ) }
251- </ td >
252- < td className = "px-4 py-3" >
253- < span className = "text-sm text-muted-foreground whitespace-nowrap" >
254- { formatRelativeTime ( deployment . createdAt ) }
255- </ span >
256- </ td >
257- < td className = "px-4 py-3" >
258- < div className = "flex items-center justify-end gap-1" >
259- < Link to = { `/deployments/${ deployment . name } ?namespace=${ deployment . namespace } ` } >
260- < Button size = "sm" variant = "ghost" title = "View details" >
261- < Eye className = "h-4 w-4" />
262- </ Button >
263- </ Link >
264- < a
265- href = { generateAynaUrl ( {
266- model : deployment . modelId ,
267- provider : 'openai' ,
268- endpoint : 'http://localhost:8000' ,
269- type : 'chat' ,
270- } ) }
271- title = "Open in Ayna"
272- >
273- < Button size = "sm" variant = "ghost" >
274- < MessageSquare className = "h-4 w-4" />
275- </ Button >
276- </ a >
277- < Button
278- size = "sm"
279- variant = "ghost"
280- onClick = { ( ) => setDeleteTarget ( deployment ) }
281- title = "Delete deployment"
282- >
283- < Trash2 className = "h-4 w-4 text-destructive" />
284- </ Button >
285- </ div >
286- </ td >
287- </ tr >
288- ) ) }
289- </ tbody >
290- </ table >
291- </ div >
292-
293217 { /* Delete Confirmation Dialog */ }
294218 < Dialog open = { ! ! deleteTarget } onOpenChange = { ( ) => setDeleteTarget ( null ) } >
295219 < DialogContent >
0 commit comments