|
1 | 1 | 'use client' |
2 | 2 |
|
3 | | -import { PanelTopOpen, Sparkles } from 'lucide-react' |
| 3 | +import { AppWindow, PanelTopOpen, Sparkles, TerminalSquare } from 'lucide-react' |
| 4 | +import { InstanceTerminalWorkspace } from '@/components/instances/instance-terminal-workspace' |
| 5 | +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' |
| 6 | +import { buttonVariants } from '@/components/ui/button' |
4 | 7 | import type { Instance } from '@/types/instance' |
5 | 8 |
|
6 | 9 | export function InstanceDashboard({ instance }: { instance: Instance }) { |
7 | | - if (instance.status !== 'running' || !instance.ip_address) { |
8 | | - return ( |
9 | | - <div className="flex flex-col items-center justify-center rounded-2xl border border-border bg-card p-12 text-center"> |
10 | | - <p className="text-sm text-muted-foreground"> |
11 | | - {instance.status === 'provisioning' |
12 | | - ? 'Dashboard will be available once provisioning completes...' |
13 | | - : 'Instance is not running. Start it to access the dashboard.'} |
14 | | - </p> |
15 | | - </div> |
16 | | - ) |
17 | | - } |
18 | | - |
19 | | - const baseUrl = instance.dashboard_url ?? `http://${instance.ip_address}` |
20 | | - const dashboardUrl = instance.gateway_token |
21 | | - ? `${baseUrl}/#token=${instance.gateway_token}` |
22 | | - : baseUrl |
| 10 | + const baseUrl = instance.dashboard_url ?? (instance.ip_address ? `http://${instance.ip_address}` : null) |
| 11 | + const dashboardUrl = baseUrl |
| 12 | + ? instance.gateway_token |
| 13 | + ? `${baseUrl}/#token=${instance.gateway_token}` |
| 14 | + : baseUrl |
| 15 | + : null |
23 | 16 |
|
24 | 17 | return ( |
25 | 18 | <section className="flex min-h-[calc(100dvh-12rem)] flex-col gap-4"> |
26 | | - <div className="flex flex-col gap-3 rounded-2xl border border-border/70 bg-card/70 p-4 backdrop-blur sm:flex-row sm:items-center sm:justify-between"> |
27 | | - <div className="space-y-1"> |
28 | | - <div className="inline-flex items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-muted-foreground"> |
29 | | - <Sparkles className="h-3.5 w-3.5 text-primary" /> |
30 | | - Embedded dashboard |
31 | | - </div> |
32 | | - <div> |
33 | | - <h2 className="text-lg font-semibold tracking-tight">OpenClaw Control UI</h2> |
34 | | - <p className="text-sm text-muted-foreground"> |
35 | | - Full-size embedded view with a quick escape hatch when you want the native tab. |
36 | | - </p> |
| 19 | + <div className="relative overflow-hidden rounded-[1.75rem] border border-border/70 bg-card/80 p-5 backdrop-blur"> |
| 20 | + <div className="absolute inset-x-0 top-0 h-28 bg-[radial-gradient(circle_at_top_left,rgba(16,185,129,0.14),transparent_52%),radial-gradient(circle_at_top_right,rgba(59,130,246,0.12),transparent_45%)]" /> |
| 21 | + <div className="relative flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between"> |
| 22 | + <div className="space-y-3"> |
| 23 | + <div className="inline-flex items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-muted-foreground"> |
| 24 | + <Sparkles className="h-3.5 w-3.5 text-primary" /> |
| 25 | + Embedded workspace |
| 26 | + </div> |
| 27 | + <div className="space-y-1"> |
| 28 | + <h2 className="text-xl font-semibold tracking-tight">Instance dashboard</h2> |
| 29 | + <p className="max-w-2xl text-sm text-muted-foreground"> |
| 30 | + The terminal workspace is the reliable default here, and the raw Control UI is still one click away when you need it. |
| 31 | + </p> |
| 32 | + </div> |
37 | 33 | </div> |
| 34 | + |
| 35 | + {dashboardUrl && ( |
| 36 | + <a |
| 37 | + href={dashboardUrl} |
| 38 | + target="_blank" |
| 39 | + rel="noopener noreferrer" |
| 40 | + className={buttonVariants({ variant: 'outline', size: 'sm' })} |
| 41 | + > |
| 42 | + <PanelTopOpen className="h-4 w-4" /> |
| 43 | + Open Control UI |
| 44 | + </a> |
| 45 | + )} |
38 | 46 | </div> |
39 | | - <a |
40 | | - href={dashboardUrl} |
41 | | - target="_blank" |
42 | | - rel="noopener noreferrer" |
43 | | - className="inline-flex items-center justify-center gap-2 rounded-lg border border-border bg-background px-3 py-2 text-sm font-medium transition-colors hover:bg-accent sm:self-start" |
44 | | - > |
45 | | - <PanelTopOpen className="h-4 w-4" /> |
46 | | - Open in new tab |
47 | | - </a> |
48 | 47 | </div> |
49 | 48 |
|
50 | | - <div className="relative flex min-h-0 flex-1 overflow-hidden rounded-[1.5rem] border border-border/80 bg-card shadow-sm"> |
51 | | - <div className="pointer-events-none absolute inset-x-0 top-0 z-10 h-16 bg-gradient-to-b from-background/10 to-transparent" /> |
52 | | - <iframe |
53 | | - src={dashboardUrl} |
54 | | - className="h-full min-h-[calc(100dvh-18rem)] w-full flex-1 bg-background" |
55 | | - title="OpenClaw Control UI" |
56 | | - allow="clipboard-read; clipboard-write" |
57 | | - /> |
58 | | - </div> |
| 49 | + <Tabs defaultValue="terminal" className="gap-4"> |
| 50 | + <TabsList |
| 51 | + variant="line" |
| 52 | + className="h-auto w-fit rounded-2xl border border-border/70 bg-card/70 p-1" |
| 53 | + > |
| 54 | + <TabsTrigger value="terminal" className="px-3 py-2"> |
| 55 | + <TerminalSquare className="h-4 w-4" /> |
| 56 | + Terminal workspace |
| 57 | + </TabsTrigger> |
| 58 | + {dashboardUrl && ( |
| 59 | + <TabsTrigger value="control" className="px-3 py-2"> |
| 60 | + <AppWindow className="h-4 w-4" /> |
| 61 | + Control UI |
| 62 | + </TabsTrigger> |
| 63 | + )} |
| 64 | + </TabsList> |
| 65 | + |
| 66 | + <TabsContent value="terminal"> |
| 67 | + <InstanceTerminalWorkspace |
| 68 | + instance={instance} |
| 69 | + showHero={false} |
| 70 | + showSummaryCards={false} |
| 71 | + /> |
| 72 | + </TabsContent> |
| 73 | + |
| 74 | + {dashboardUrl && ( |
| 75 | + <TabsContent value="control"> |
| 76 | + <div className="space-y-4"> |
| 77 | + <div className="flex flex-col gap-3 rounded-2xl border border-border/70 bg-card/70 p-4 backdrop-blur sm:flex-row sm:items-center sm:justify-between"> |
| 78 | + <div className="space-y-1"> |
| 79 | + <div className="inline-flex items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-muted-foreground"> |
| 80 | + <AppWindow className="h-3.5 w-3.5 text-primary" /> |
| 81 | + Raw instance UI |
| 82 | + </div> |
| 83 | + <div> |
| 84 | + <h3 className="text-lg font-semibold tracking-tight">OpenClaw Control UI</h3> |
| 85 | + <p className="text-sm text-muted-foreground"> |
| 86 | + This is the app that runs inside the instance itself. If its frontend assets are missing, the terminal workspace tab remains fully usable. |
| 87 | + </p> |
| 88 | + </div> |
| 89 | + </div> |
| 90 | + <a |
| 91 | + href={dashboardUrl} |
| 92 | + target="_blank" |
| 93 | + rel="noopener noreferrer" |
| 94 | + className={buttonVariants({ variant: 'outline', size: 'sm' })} |
| 95 | + > |
| 96 | + <PanelTopOpen className="h-4 w-4" /> |
| 97 | + Open in new tab |
| 98 | + </a> |
| 99 | + </div> |
| 100 | + |
| 101 | + <div className="relative flex min-h-0 flex-1 overflow-hidden rounded-[1.5rem] border border-border/80 bg-card shadow-sm"> |
| 102 | + <div className="pointer-events-none absolute inset-x-0 top-0 z-10 h-16 bg-gradient-to-b from-background/10 to-transparent" /> |
| 103 | + <iframe |
| 104 | + src={dashboardUrl} |
| 105 | + className="h-full min-h-[calc(100dvh-20rem)] w-full flex-1 bg-background" |
| 106 | + title="OpenClaw Control UI" |
| 107 | + allow="clipboard-read; clipboard-write" |
| 108 | + /> |
| 109 | + </div> |
| 110 | + </div> |
| 111 | + </TabsContent> |
| 112 | + )} |
| 113 | + </Tabs> |
59 | 114 | </section> |
60 | 115 | ) |
61 | 116 | } |
0 commit comments