@supaku/agentfactory-dashboard 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +248 -0
- package/package.json +14 -1
- package/src/components/fleet/agent-card.tsx +31 -14
- package/src/components/fleet/fleet-overview.tsx +80 -44
- package/src/components/fleet/stat-card.tsx +31 -12
- package/src/components/fleet/status-dot.tsx +6 -3
- package/src/components/layout/bottom-bar.tsx +9 -12
- package/src/components/layout/shell.tsx +15 -7
- package/src/components/layout/sidebar.tsx +39 -23
- package/src/components/layout/top-bar.tsx +23 -15
- package/src/components/pipeline/pipeline-card.tsx +10 -5
- package/src/components/pipeline/pipeline-column.tsx +14 -11
- package/src/components/pipeline/pipeline-view.tsx +42 -34
- package/src/components/sessions/session-detail.tsx +75 -54
- package/src/components/sessions/session-list.tsx +39 -23
- package/src/components/sessions/session-timeline.tsx +49 -27
- package/src/components/sessions/token-chart.tsx +14 -14
- package/src/components/settings/settings-view.tsx +61 -52
- package/src/components/shared/empty-state.tsx +11 -6
- package/src/components/ui/badge.tsx +5 -5
- package/src/components/ui/button.tsx +7 -7
- package/src/components/ui/card.tsx +13 -6
- package/src/components/ui/skeleton.tsx +4 -1
- package/src/lib/status-config.ts +18 -4
- package/src/lib/work-type-config.ts +10 -1
- package/src/styles/globals.css +191 -11
- package/tailwind.config.ts +63 -12
|
@@ -21,7 +21,7 @@ export function SessionList({ onSelect, className }: SessionListProps) {
|
|
|
21
21
|
|
|
22
22
|
if (isLoading) {
|
|
23
23
|
return (
|
|
24
|
-
<div className={cn('space-y-2 p-
|
|
24
|
+
<div className={cn('space-y-2 p-6', className)}>
|
|
25
25
|
{Array.from({ length: 6 }).map((_, i) => (
|
|
26
26
|
<Skeleton key={i} className="h-14 rounded-lg" />
|
|
27
27
|
))}
|
|
@@ -31,7 +31,7 @@ export function SessionList({ onSelect, className }: SessionListProps) {
|
|
|
31
31
|
|
|
32
32
|
if (sessions.length === 0) {
|
|
33
33
|
return (
|
|
34
|
-
<div className="p-
|
|
34
|
+
<div className="p-6">
|
|
35
35
|
<EmptyState
|
|
36
36
|
title="No sessions"
|
|
37
37
|
description="Agent sessions will appear here once they start."
|
|
@@ -41,60 +41,76 @@ export function SessionList({ onSelect, className }: SessionListProps) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
return (
|
|
44
|
-
<div className={cn('p-
|
|
45
|
-
<div className="
|
|
46
|
-
<
|
|
44
|
+
<div className={cn('p-6', className)}>
|
|
45
|
+
<div className="mb-5 flex items-center gap-3">
|
|
46
|
+
<h2 className="font-display text-lg font-bold text-af-text-primary tracking-tight">
|
|
47
|
+
Sessions
|
|
48
|
+
</h2>
|
|
49
|
+
<span className="rounded-full bg-af-surface/60 px-2 py-0.5 text-2xs font-body font-medium tabular-nums text-af-text-secondary">
|
|
50
|
+
{sessions.length}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div className="rounded-xl border border-af-surface-border/40 overflow-hidden">
|
|
55
|
+
<table className="w-full text-sm font-body">
|
|
47
56
|
<thead>
|
|
48
|
-
<tr className="border-b border-af-surface-border bg-af-bg-secondary">
|
|
49
|
-
<th className="px-4 py-
|
|
50
|
-
<th className="px-4 py-
|
|
51
|
-
<th className="px-4 py-
|
|
52
|
-
<th className="px-4 py-
|
|
53
|
-
<th className="px-4 py-
|
|
54
|
-
<th className="px-4 py-
|
|
57
|
+
<tr className="border-b border-af-surface-border/40 bg-af-bg-secondary/50">
|
|
58
|
+
<th className="px-4 py-3 text-left text-2xs font-semibold uppercase tracking-wider text-af-text-tertiary">Issue</th>
|
|
59
|
+
<th className="px-4 py-3 text-left text-2xs font-semibold uppercase tracking-wider text-af-text-tertiary">Status</th>
|
|
60
|
+
<th className="px-4 py-3 text-left text-2xs font-semibold uppercase tracking-wider text-af-text-tertiary">Type</th>
|
|
61
|
+
<th className="px-4 py-3 text-left text-2xs font-semibold uppercase tracking-wider text-af-text-tertiary">Duration</th>
|
|
62
|
+
<th className="px-4 py-3 text-left text-2xs font-semibold uppercase tracking-wider text-af-text-tertiary">Cost</th>
|
|
63
|
+
<th className="px-4 py-3 text-left text-2xs font-semibold uppercase tracking-wider text-af-text-tertiary">Started</th>
|
|
55
64
|
</tr>
|
|
56
65
|
</thead>
|
|
57
66
|
<tbody>
|
|
58
|
-
{sessions.map((session) => {
|
|
67
|
+
{sessions.map((session, i) => {
|
|
59
68
|
const statusConfig = getStatusConfig(session.status)
|
|
60
69
|
const workTypeConfig = getWorkTypeConfig(session.workType)
|
|
61
70
|
return (
|
|
62
71
|
<tr
|
|
63
72
|
key={session.id}
|
|
64
|
-
className=
|
|
73
|
+
className={cn(
|
|
74
|
+
'border-b border-af-surface-border/20 last:border-0 cursor-pointer transition-all duration-200',
|
|
75
|
+
'hover:bg-af-surface/30'
|
|
76
|
+
)}
|
|
65
77
|
onClick={() => onSelect?.(session.id)}
|
|
78
|
+
style={{ animationDelay: `${i * 30}ms` }}
|
|
66
79
|
>
|
|
67
|
-
<td className="px-4 py-
|
|
80
|
+
<td className="px-4 py-3">
|
|
68
81
|
<span className="font-mono text-sm text-af-text-primary">{session.identifier}</span>
|
|
69
82
|
</td>
|
|
70
|
-
<td className="px-4 py-
|
|
71
|
-
<div className="flex items-center gap-
|
|
83
|
+
<td className="px-4 py-3">
|
|
84
|
+
<div className="flex items-center gap-2">
|
|
72
85
|
<StatusDot status={session.status} />
|
|
73
86
|
<span className={cn('text-xs', statusConfig.textColor)}>
|
|
74
87
|
{statusConfig.label}
|
|
75
88
|
</span>
|
|
76
89
|
</div>
|
|
77
90
|
</td>
|
|
78
|
-
<td className="px-4 py-
|
|
91
|
+
<td className="px-4 py-3">
|
|
79
92
|
<Badge
|
|
80
93
|
variant="outline"
|
|
81
|
-
className={cn(
|
|
94
|
+
className={cn(
|
|
95
|
+
'text-2xs border',
|
|
96
|
+
workTypeConfig.bgColor, workTypeConfig.color, workTypeConfig.borderColor
|
|
97
|
+
)}
|
|
82
98
|
>
|
|
83
99
|
{workTypeConfig.label}
|
|
84
100
|
</Badge>
|
|
85
101
|
</td>
|
|
86
|
-
<td className="px-4 py-
|
|
102
|
+
<td className="px-4 py-3">
|
|
87
103
|
<span className="text-xs text-af-text-secondary tabular-nums">
|
|
88
104
|
{formatDuration(session.duration)}
|
|
89
105
|
</span>
|
|
90
106
|
</td>
|
|
91
|
-
<td className="px-4 py-
|
|
107
|
+
<td className="px-4 py-3">
|
|
92
108
|
<span className="text-xs text-af-text-secondary tabular-nums font-mono">
|
|
93
109
|
{formatCost(session.costUsd)}
|
|
94
110
|
</span>
|
|
95
111
|
</td>
|
|
96
|
-
<td className="px-4 py-
|
|
97
|
-
<span className="text-xs text-af-text-
|
|
112
|
+
<td className="px-4 py-3">
|
|
113
|
+
<span className="text-xs text-af-text-tertiary">
|
|
98
114
|
{formatRelativeTime(session.startedAt)}
|
|
99
115
|
</span>
|
|
100
116
|
</td>
|
|
@@ -13,42 +13,64 @@ interface SessionTimelineProps {
|
|
|
13
13
|
className?: string
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
info:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const typeStyles = {
|
|
17
|
+
info: {
|
|
18
|
+
dot: 'bg-af-blue',
|
|
19
|
+
glow: 'shadow-[0_0_8px_2px_rgba(75,139,245,0.3)]',
|
|
20
|
+
line: 'bg-af-blue/20',
|
|
21
|
+
},
|
|
22
|
+
success: {
|
|
23
|
+
dot: 'bg-af-status-success',
|
|
24
|
+
glow: 'shadow-[0_0_8px_2px_rgba(34,197,94,0.3)]',
|
|
25
|
+
line: 'bg-af-status-success/20',
|
|
26
|
+
},
|
|
27
|
+
warning: {
|
|
28
|
+
dot: 'bg-af-status-warning',
|
|
29
|
+
glow: 'shadow-[0_0_8px_2px_rgba(245,158,11,0.3)]',
|
|
30
|
+
line: 'bg-af-status-warning/20',
|
|
31
|
+
},
|
|
32
|
+
error: {
|
|
33
|
+
dot: 'bg-af-status-error',
|
|
34
|
+
glow: 'shadow-[0_0_8px_2px_rgba(239,68,68,0.3)]',
|
|
35
|
+
line: 'bg-af-status-error/20',
|
|
36
|
+
},
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
export function SessionTimeline({ events, className }: SessionTimelineProps) {
|
|
24
40
|
return (
|
|
25
41
|
<div className={cn('relative space-y-0', className)}>
|
|
26
|
-
{/* Vertical line */}
|
|
27
|
-
|
|
42
|
+
{/* Vertical connector line */}
|
|
43
|
+
{events.length > 1 && (
|
|
44
|
+
<div className="absolute left-[5px] top-3 bottom-3 w-px bg-af-surface-border/40" />
|
|
45
|
+
)}
|
|
28
46
|
|
|
29
|
-
{events.map((event
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
47
|
+
{events.map((event) => {
|
|
48
|
+
const styles = typeStyles[event.type ?? 'info']
|
|
49
|
+
return (
|
|
50
|
+
<div key={event.id} className="relative flex gap-3.5 py-2.5">
|
|
51
|
+
{/* Glowing dot */}
|
|
52
|
+
<span
|
|
53
|
+
className={cn(
|
|
54
|
+
'relative z-10 mt-1.5 h-[10px] w-[10px] shrink-0 rounded-full',
|
|
55
|
+
styles.dot,
|
|
56
|
+
styles.glow
|
|
57
|
+
)}
|
|
58
|
+
/>
|
|
38
59
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
<div className="flex-1 min-w-0">
|
|
61
|
+
<div className="flex items-baseline justify-between gap-2">
|
|
62
|
+
<span className="text-sm font-body text-af-text-primary">{event.label}</span>
|
|
63
|
+
<span className="text-2xs font-body text-af-text-tertiary whitespace-nowrap tabular-nums">
|
|
64
|
+
{new Date(event.timestamp).toLocaleTimeString()}
|
|
65
|
+
</span>
|
|
66
|
+
</div>
|
|
67
|
+
{event.detail && (
|
|
68
|
+
<p className="mt-0.5 text-xs font-body text-af-text-tertiary">{event.detail}</p>
|
|
69
|
+
)}
|
|
45
70
|
</div>
|
|
46
|
-
{event.detail && (
|
|
47
|
-
<p className="mt-0.5 text-xs text-af-text-secondary">{event.detail}</p>
|
|
48
|
-
)}
|
|
49
71
|
</div>
|
|
50
|
-
|
|
51
|
-
)
|
|
72
|
+
)
|
|
73
|
+
})}
|
|
52
74
|
</div>
|
|
53
75
|
)
|
|
54
76
|
}
|
|
@@ -15,35 +15,35 @@ export function TokenChart({ inputTokens = 0, outputTokens = 0, className }: Tok
|
|
|
15
15
|
const outputPct = (outputTokens / total) * 100
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
|
-
<div className={cn('space-y-
|
|
19
|
-
<div className="flex items-center justify-between text-
|
|
20
|
-
<span>Token Usage</span>
|
|
21
|
-
<span className="tabular-nums font-mono">{formatTokens(total)} total</span>
|
|
18
|
+
<div className={cn('space-y-3', className)}>
|
|
19
|
+
<div className="flex items-center justify-between text-2xs font-body">
|
|
20
|
+
<span className="uppercase tracking-wider text-af-text-tertiary">Token Usage</span>
|
|
21
|
+
<span className="tabular-nums font-mono text-af-text-secondary">{formatTokens(total)} total</span>
|
|
22
22
|
</div>
|
|
23
23
|
|
|
24
24
|
{/* Bar */}
|
|
25
|
-
<div className="flex h-
|
|
25
|
+
<div className="flex h-2.5 w-full overflow-hidden rounded-full bg-af-bg-primary/60">
|
|
26
26
|
<div
|
|
27
|
-
className="bg-blue-
|
|
27
|
+
className="bg-af-blue rounded-l-full transition-all duration-500"
|
|
28
28
|
style={{ width: `${inputPct}%` }}
|
|
29
29
|
/>
|
|
30
30
|
<div
|
|
31
|
-
className="bg-af-accent transition-all"
|
|
31
|
+
className="bg-af-accent transition-all duration-500"
|
|
32
32
|
style={{ width: `${outputPct}%` }}
|
|
33
33
|
/>
|
|
34
34
|
</div>
|
|
35
35
|
|
|
36
36
|
{/* Legend */}
|
|
37
|
-
<div className="flex items-center gap-
|
|
37
|
+
<div className="flex items-center gap-5 text-2xs font-body">
|
|
38
38
|
<div className="flex items-center gap-1.5">
|
|
39
|
-
<span className="h-2 w-2 rounded-full bg-blue-
|
|
40
|
-
<span className="text-af-text-
|
|
41
|
-
<span className="tabular-nums font-mono text-af-text-
|
|
39
|
+
<span className="h-2 w-2 rounded-full bg-af-blue shadow-[0_0_6px_1px_rgba(75,139,245,0.3)]" />
|
|
40
|
+
<span className="text-af-text-tertiary">Input</span>
|
|
41
|
+
<span className="tabular-nums font-mono text-af-text-secondary">{formatTokens(inputTokens)}</span>
|
|
42
42
|
</div>
|
|
43
43
|
<div className="flex items-center gap-1.5">
|
|
44
|
-
<span className="h-2 w-2 rounded-full bg-af-accent" />
|
|
45
|
-
<span className="text-af-text-
|
|
46
|
-
<span className="tabular-nums font-mono text-af-text-
|
|
44
|
+
<span className="h-2 w-2 rounded-full bg-af-accent shadow-[0_0_6px_1px_rgba(255,107,53,0.3)]" />
|
|
45
|
+
<span className="text-af-text-tertiary">Output</span>
|
|
46
|
+
<span className="tabular-nums font-mono text-af-text-secondary">{formatTokens(outputTokens)}</span>
|
|
47
47
|
</div>
|
|
48
48
|
</div>
|
|
49
49
|
</div>
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { cn } from '../../lib/utils'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '../../components/ui/card'
|
|
5
4
|
import { Badge } from '../../components/ui/badge'
|
|
6
5
|
import { Separator } from '../../components/ui/separator'
|
|
7
6
|
import { useStats } from '../../hooks/use-stats'
|
|
8
7
|
import { useWorkers } from '../../hooks/use-workers'
|
|
9
8
|
import { ProviderIcon } from '../../components/fleet/provider-icon'
|
|
10
9
|
import { StatusDot } from '../../components/fleet/status-dot'
|
|
11
|
-
import { CheckCircle2, AlertCircle, Settings2 } from 'lucide-react'
|
|
10
|
+
import { CheckCircle2, AlertCircle, Settings2, Webhook, Server, Shield } from 'lucide-react'
|
|
12
11
|
|
|
13
12
|
interface SettingsViewProps {
|
|
14
13
|
className?: string
|
|
@@ -22,97 +21,106 @@ export function SettingsView({ className }: SettingsViewProps) {
|
|
|
22
21
|
const hasWorkerAuth = workersData !== null
|
|
23
22
|
|
|
24
23
|
return (
|
|
25
|
-
<div className={cn('space-y-6 p-
|
|
24
|
+
<div className={cn('space-y-6 p-6 max-w-3xl', className)}>
|
|
25
|
+
{/* Page header */}
|
|
26
26
|
<div>
|
|
27
|
-
<h1 className="text-
|
|
28
|
-
<p className="text-sm text-af-text-secondary">
|
|
27
|
+
<h1 className="font-display text-xl font-bold text-af-text-primary tracking-tight">Settings</h1>
|
|
28
|
+
<p className="mt-1 text-sm font-body text-af-text-secondary">
|
|
29
29
|
Configuration and integration status for your AgentFactory instance.
|
|
30
30
|
</p>
|
|
31
31
|
</div>
|
|
32
32
|
|
|
33
33
|
{/* Integration Status */}
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
<div className="rounded-xl border border-af-surface-border/40 bg-af-surface/30 overflow-hidden">
|
|
35
|
+
<div className="px-6 py-4 border-b border-af-surface-border/30">
|
|
36
|
+
<h3 className="flex items-center gap-2 font-display text-sm font-semibold text-af-text-primary tracking-tight">
|
|
37
|
+
<Webhook className="h-4 w-4 text-af-text-tertiary" />
|
|
38
|
+
Integration Status
|
|
39
|
+
</h3>
|
|
40
|
+
<p className="mt-0.5 text-xs font-body text-af-text-tertiary">Connected services and API endpoints</p>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div className="px-6 py-4 space-y-4">
|
|
40
44
|
<div className="flex items-center justify-between">
|
|
41
45
|
<div className="flex items-center gap-3">
|
|
42
46
|
<CheckCircle2 className="h-4 w-4 text-af-status-success" />
|
|
43
47
|
<div>
|
|
44
|
-
<p className="text-sm text-af-text-primary">Linear Webhook</p>
|
|
45
|
-
<p className="text-
|
|
48
|
+
<p className="text-sm font-body text-af-text-primary">Linear Webhook</p>
|
|
49
|
+
<p className="text-2xs font-mono text-af-text-tertiary">/webhook</p>
|
|
46
50
|
</div>
|
|
47
51
|
</div>
|
|
48
52
|
<Badge variant="success">Connected</Badge>
|
|
49
53
|
</div>
|
|
50
54
|
|
|
51
|
-
<Separator />
|
|
55
|
+
<Separator className="bg-af-surface-border/30" />
|
|
52
56
|
|
|
53
57
|
<div className="flex items-center justify-between">
|
|
54
58
|
<div className="flex items-center gap-3">
|
|
55
59
|
<CheckCircle2 className="h-4 w-4 text-af-status-success" />
|
|
56
60
|
<div>
|
|
57
|
-
<p className="text-sm text-af-text-primary">Public API</p>
|
|
58
|
-
<p className="text-
|
|
61
|
+
<p className="text-sm font-body text-af-text-primary">Public API</p>
|
|
62
|
+
<p className="text-2xs font-mono text-af-text-tertiary">/api/public/stats</p>
|
|
59
63
|
</div>
|
|
60
64
|
</div>
|
|
61
65
|
<Badge variant="success">Active</Badge>
|
|
62
66
|
</div>
|
|
63
67
|
|
|
64
|
-
<Separator />
|
|
68
|
+
<Separator className="bg-af-surface-border/30" />
|
|
65
69
|
|
|
66
70
|
<div className="flex items-center justify-between">
|
|
67
71
|
<div className="flex items-center gap-3">
|
|
68
72
|
{hasWorkerAuth ? (
|
|
69
73
|
<CheckCircle2 className="h-4 w-4 text-af-status-success" />
|
|
70
74
|
) : (
|
|
71
|
-
<AlertCircle className="h-4 w-4 text-af-text-
|
|
75
|
+
<AlertCircle className="h-4 w-4 text-af-text-tertiary" />
|
|
72
76
|
)}
|
|
73
77
|
<div>
|
|
74
|
-
<p className="text-sm text-af-text-primary">Worker API</p>
|
|
75
|
-
<p className="text-
|
|
78
|
+
<p className="text-sm font-body text-af-text-primary">Worker API</p>
|
|
79
|
+
<p className="text-2xs font-mono text-af-text-tertiary">/api/workers</p>
|
|
76
80
|
</div>
|
|
77
81
|
</div>
|
|
78
82
|
<Badge variant={hasWorkerAuth ? 'success' : 'secondary'}>
|
|
79
83
|
{hasWorkerAuth ? 'Authenticated' : 'No Auth Key'}
|
|
80
84
|
</Badge>
|
|
81
85
|
</div>
|
|
82
|
-
</
|
|
83
|
-
</
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
84
88
|
|
|
85
89
|
{/* Workers */}
|
|
86
|
-
<
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
+
<div className="rounded-xl border border-af-surface-border/40 bg-af-surface/30 overflow-hidden">
|
|
91
|
+
<div className="px-6 py-4 border-b border-af-surface-border/30">
|
|
92
|
+
<h3 className="flex items-center gap-2 font-display text-sm font-semibold text-af-text-primary tracking-tight">
|
|
93
|
+
<Server className="h-4 w-4 text-af-text-tertiary" />
|
|
94
|
+
Workers
|
|
95
|
+
</h3>
|
|
96
|
+
<p className="mt-0.5 text-xs font-body text-af-text-tertiary">
|
|
90
97
|
{workers.length > 0
|
|
91
98
|
? `${workers.length} worker${workers.length !== 1 ? 's' : ''} registered`
|
|
92
99
|
: 'No workers connected'}
|
|
93
|
-
</
|
|
94
|
-
</
|
|
95
|
-
|
|
100
|
+
</p>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div className="px-6 py-4">
|
|
96
104
|
{workers.length === 0 ? (
|
|
97
|
-
<p className="text-sm text-af-text-
|
|
105
|
+
<p className="text-sm font-body text-af-text-tertiary">
|
|
98
106
|
Workers will appear here once they register with the server.
|
|
99
107
|
</p>
|
|
100
108
|
) : (
|
|
101
109
|
<div className="space-y-3">
|
|
102
110
|
{workers.map((worker) => (
|
|
103
|
-
<div key={worker.id} className="flex items-center justify-between">
|
|
111
|
+
<div key={worker.id} className="flex items-center justify-between py-1">
|
|
104
112
|
<div className="flex items-center gap-3">
|
|
105
113
|
<StatusDot status={worker.status === 'active' ? 'working' : 'stopped'} />
|
|
106
114
|
<div>
|
|
107
115
|
<p className="text-sm font-mono text-af-text-primary">
|
|
108
116
|
{worker.hostname ?? worker.id.slice(0, 8)}
|
|
109
117
|
</p>
|
|
110
|
-
<p className="text-
|
|
118
|
+
<p className="text-2xs font-body text-af-text-tertiary">
|
|
111
119
|
{worker.activeSessions}/{worker.capacity} slots
|
|
112
120
|
</p>
|
|
113
121
|
</div>
|
|
114
122
|
</div>
|
|
115
|
-
<div className="flex items-center gap-2">
|
|
123
|
+
<div className="flex items-center gap-2.5">
|
|
116
124
|
<ProviderIcon provider={worker.provider} size={14} />
|
|
117
125
|
<Badge variant={worker.status === 'active' ? 'success' : 'secondary'}>
|
|
118
126
|
{worker.status}
|
|
@@ -122,34 +130,35 @@ export function SettingsView({ className }: SettingsViewProps) {
|
|
|
122
130
|
))}
|
|
123
131
|
</div>
|
|
124
132
|
)}
|
|
125
|
-
</
|
|
126
|
-
</
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
127
135
|
|
|
128
136
|
{/* Fleet Stats */}
|
|
129
|
-
<
|
|
130
|
-
<
|
|
131
|
-
<
|
|
132
|
-
<
|
|
137
|
+
<div className="rounded-xl border border-af-surface-border/40 bg-af-surface/30 overflow-hidden">
|
|
138
|
+
<div className="px-6 py-4 border-b border-af-surface-border/30">
|
|
139
|
+
<h3 className="flex items-center gap-2 font-display text-sm font-semibold text-af-text-primary tracking-tight">
|
|
140
|
+
<Shield className="h-4 w-4 text-af-text-tertiary" />
|
|
133
141
|
Fleet Configuration
|
|
134
|
-
</
|
|
135
|
-
</
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<
|
|
142
|
+
</h3>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div className="px-6 py-4">
|
|
146
|
+
<div className="grid grid-cols-2 gap-6">
|
|
147
|
+
<div className="space-y-1">
|
|
148
|
+
<dt className="text-2xs font-body uppercase tracking-wider text-af-text-tertiary">Total Capacity</dt>
|
|
149
|
+
<dd className="font-display text-lg font-bold tabular-nums text-af-text-primary">
|
|
141
150
|
{stats?.availableCapacity ?? '—'}
|
|
142
151
|
</dd>
|
|
143
152
|
</div>
|
|
144
|
-
<div>
|
|
145
|
-
<dt className="text-af-text-
|
|
146
|
-
<dd className="
|
|
153
|
+
<div className="space-y-1">
|
|
154
|
+
<dt className="text-2xs font-body uppercase tracking-wider text-af-text-tertiary">Workers Online</dt>
|
|
155
|
+
<dd className="font-display text-lg font-bold tabular-nums text-af-text-primary">
|
|
147
156
|
{stats?.workersOnline ?? '—'}
|
|
148
157
|
</dd>
|
|
149
158
|
</div>
|
|
150
|
-
</
|
|
151
|
-
</
|
|
152
|
-
</
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
153
162
|
</div>
|
|
154
163
|
)
|
|
155
164
|
}
|
|
@@ -17,13 +17,18 @@ export function EmptyState({
|
|
|
17
17
|
children,
|
|
18
18
|
}: EmptyStateProps) {
|
|
19
19
|
return (
|
|
20
|
-
<div className={cn(
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
<div className={cn(
|
|
21
|
+
'flex flex-col items-center justify-center py-16 text-center',
|
|
22
|
+
className
|
|
23
|
+
)}>
|
|
24
|
+
<div className="mb-4 rounded-xl border border-af-surface-border/30 bg-af-surface/20 p-4">
|
|
25
|
+
<div className="text-af-text-tertiary">
|
|
26
|
+
{icon ?? <Inbox className="h-8 w-8" />}
|
|
27
|
+
</div>
|
|
23
28
|
</div>
|
|
24
|
-
<h3 className="text-sm font-
|
|
25
|
-
<p className="mt-1 text-
|
|
26
|
-
{children && <div className="mt-
|
|
29
|
+
<h3 className="font-display text-sm font-semibold text-af-text-primary tracking-tight">{title}</h3>
|
|
30
|
+
<p className="mt-1.5 max-w-xs text-xs font-body text-af-text-tertiary">{description}</p>
|
|
31
|
+
{children && <div className="mt-5">{children}</div>}
|
|
27
32
|
</div>
|
|
28
33
|
)
|
|
29
34
|
}
|
|
@@ -3,17 +3,17 @@ import { cva, type VariantProps } from 'class-variance-authority'
|
|
|
3
3
|
import { cn } from '../../lib/utils'
|
|
4
4
|
|
|
5
5
|
const badgeVariants = cva(
|
|
6
|
-
'inline-flex items-center rounded-full border px-2 py-0.5 text-
|
|
6
|
+
'inline-flex items-center rounded-full border px-2 py-0.5 text-2xs font-medium font-body transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
|
7
7
|
{
|
|
8
8
|
variants: {
|
|
9
9
|
variant: {
|
|
10
10
|
default: 'border-transparent bg-primary text-primary-foreground',
|
|
11
|
-
secondary: 'border-
|
|
11
|
+
secondary: 'border-af-surface-border/40 bg-af-surface/40 text-af-text-secondary',
|
|
12
12
|
destructive: 'border-transparent bg-destructive text-destructive-foreground',
|
|
13
13
|
outline: 'text-foreground',
|
|
14
|
-
success: 'border-
|
|
15
|
-
warning: 'border-
|
|
16
|
-
error: 'border-
|
|
14
|
+
success: 'border-af-status-success/20 bg-af-status-success/10 text-af-status-success',
|
|
15
|
+
warning: 'border-af-status-warning/20 bg-af-status-warning/10 text-af-status-warning',
|
|
16
|
+
error: 'border-af-status-error/20 bg-af-status-error/10 text-af-status-error',
|
|
17
17
|
},
|
|
18
18
|
},
|
|
19
19
|
defaultVariants: {
|
|
@@ -6,21 +6,21 @@ import { cva, type VariantProps } from 'class-variance-authority'
|
|
|
6
6
|
import { cn } from '../../lib/utils'
|
|
7
7
|
|
|
8
8
|
const buttonVariants = cva(
|
|
9
|
-
'inline-flex items-center justify-center whitespace-nowrap rounded-
|
|
9
|
+
'inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium font-body ring-offset-background transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
10
10
|
{
|
|
11
11
|
variants: {
|
|
12
12
|
variant: {
|
|
13
|
-
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
13
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90 glow-soft',
|
|
14
14
|
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
15
|
-
outline: 'border border-
|
|
16
|
-
secondary: 'bg-
|
|
17
|
-
ghost: 'hover:bg-
|
|
15
|
+
outline: 'border border-af-surface-border/50 bg-transparent hover:bg-af-surface/40 hover:text-accent-foreground',
|
|
16
|
+
secondary: 'bg-af-surface/60 text-secondary-foreground hover:bg-af-surface/80',
|
|
17
|
+
ghost: 'hover:bg-af-surface/40 hover:text-accent-foreground',
|
|
18
18
|
link: 'text-primary underline-offset-4 hover:underline',
|
|
19
19
|
},
|
|
20
20
|
size: {
|
|
21
21
|
default: 'h-9 px-4 py-2',
|
|
22
|
-
sm: 'h-8 rounded-
|
|
23
|
-
lg: 'h-10 rounded-
|
|
22
|
+
sm: 'h-8 rounded-lg px-3',
|
|
23
|
+
lg: 'h-10 rounded-lg px-8',
|
|
24
24
|
icon: 'h-9 w-9',
|
|
25
25
|
},
|
|
26
26
|
},
|
|
@@ -5,7 +5,10 @@ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElemen
|
|
|
5
5
|
({ className, ...props }, ref) => (
|
|
6
6
|
<div
|
|
7
7
|
ref={ref}
|
|
8
|
-
className={cn(
|
|
8
|
+
className={cn(
|
|
9
|
+
'rounded-xl border border-af-surface-border/40 bg-af-surface/40 text-card-foreground',
|
|
10
|
+
className
|
|
11
|
+
)}
|
|
9
12
|
{...props}
|
|
10
13
|
/>
|
|
11
14
|
)
|
|
@@ -14,35 +17,39 @@ Card.displayName = 'Card'
|
|
|
14
17
|
|
|
15
18
|
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
16
19
|
({ className, ...props }, ref) => (
|
|
17
|
-
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-
|
|
20
|
+
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
|
|
18
21
|
)
|
|
19
22
|
)
|
|
20
23
|
CardHeader.displayName = 'CardHeader'
|
|
21
24
|
|
|
22
25
|
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
|
23
26
|
({ className, ...props }, ref) => (
|
|
24
|
-
<h3
|
|
27
|
+
<h3
|
|
28
|
+
ref={ref}
|
|
29
|
+
className={cn('font-display text-sm font-semibold leading-none tracking-tight', className)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
25
32
|
)
|
|
26
33
|
)
|
|
27
34
|
CardTitle.displayName = 'CardTitle'
|
|
28
35
|
|
|
29
36
|
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
30
37
|
({ className, ...props }, ref) => (
|
|
31
|
-
<p ref={ref} className={cn('text-
|
|
38
|
+
<p ref={ref} className={cn('text-xs font-body text-muted-foreground', className)} {...props} />
|
|
32
39
|
)
|
|
33
40
|
)
|
|
34
41
|
CardDescription.displayName = 'CardDescription'
|
|
35
42
|
|
|
36
43
|
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
37
44
|
({ className, ...props }, ref) => (
|
|
38
|
-
<div ref={ref} className={cn('p-
|
|
45
|
+
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
|
39
46
|
)
|
|
40
47
|
)
|
|
41
48
|
CardContent.displayName = 'CardContent'
|
|
42
49
|
|
|
43
50
|
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
44
51
|
({ className, ...props }, ref) => (
|
|
45
|
-
<div ref={ref} className={cn('flex items-center p-
|
|
52
|
+
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
|
|
46
53
|
)
|
|
47
54
|
)
|
|
48
55
|
CardFooter.displayName = 'CardFooter'
|
|
@@ -3,7 +3,10 @@ import { cn } from '../../lib/utils'
|
|
|
3
3
|
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
4
4
|
return (
|
|
5
5
|
<div
|
|
6
|
-
className={cn(
|
|
6
|
+
className={cn(
|
|
7
|
+
'animate-pulse rounded-lg bg-af-surface/60',
|
|
8
|
+
className
|
|
9
|
+
)}
|
|
7
10
|
{...props}
|
|
8
11
|
/>
|
|
9
12
|
)
|