Skip to content

Commit 0c9a78b

Browse files
tryfix
1 parent d3d507d commit 0c9a78b

3 files changed

Lines changed: 63 additions & 74 deletions

File tree

src/app/api/health/[instanceId]/route.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ import { logInstanceEvent } from '@/lib/control-plane'
77

88
const INSTANCE_DOMAIN = process.env.INSTANCE_DOMAIN ?? 'agentcomputers.app'
99

10+
async function isDashboardReachable(url: string): Promise<boolean> {
11+
try {
12+
await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(4000), redirect: 'follow' })
13+
return true
14+
} catch {
15+
return false
16+
}
17+
}
18+
1019
export async function GET(
1120
_req: Request,
1221
{ params }: { params: Promise<{ instanceId: string }> }
@@ -40,16 +49,28 @@ export async function GET(
4049
updates.provisioned_at = new Date().toISOString()
4150
}
4251

52+
let dashboardReachable = false
53+
if (instance.dashboard_url) {
54+
dashboardReachable = await isDashboardReachable(instance.dashboard_url)
55+
if (!dashboardReachable) {
56+
updates.dashboard_url = null
57+
}
58+
}
59+
4360
if (!instance.dashboard_url && instance.ip_address && health.status === 'healthy' && isDnsConfigured()) {
4461
try {
4562
const dns = await createDnsRecord(instance.slug, instance.ip_address)
4663
if (dns.success) {
47-
updates.dashboard_url = `https://${instance.slug}.${INSTANCE_DOMAIN}`
48-
await logInstanceEvent(instanceId, 'dns_created', {
49-
record_id: dns.id,
50-
hostname: `${instance.slug}.${INSTANCE_DOMAIN}`,
51-
source: 'health_check_retry',
52-
})
64+
const candidateUrl = `https://${instance.slug}.${INSTANCE_DOMAIN}`
65+
const reachable = await isDashboardReachable(candidateUrl)
66+
if (reachable) {
67+
updates.dashboard_url = candidateUrl
68+
await logInstanceEvent(instanceId, 'dns_created', {
69+
record_id: dns.id,
70+
hostname: `${instance.slug}.${INSTANCE_DOMAIN}`,
71+
source: 'health_check_retry',
72+
})
73+
}
5374
}
5475
} catch (err) {
5576
console.error('DNS retry during health check failed:', err)

src/components/instances/instance-dashboard.tsx

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,34 @@
11
'use client'
22

33
import { ExternalLink, LoaderCircle } from 'lucide-react'
4-
import { useEffect, useRef, useState } from 'react'
4+
import { useState } from 'react'
55
import { buttonVariants } from '@/components/ui/button'
6+
import { usePolling } from '@/hooks/use-polling'
67
import type { Instance } from '@/types/instance'
78

8-
const LOAD_TIMEOUT_MS = 8000
9-
109
function buildDashboardUrl(base: string, token: string | null): string {
1110
return token ? `${base}/#token=${token}` : base
1211
}
1312

14-
export function InstanceDashboard({ instance }: { instance: Instance }) {
15-
const httpsBase = instance.dashboard_url ?? null
16-
const ipBase = instance.ip_address ? `http://${instance.ip_address}` : null
17-
const [useFallback, setUseFallback] = useState(false)
18-
const [loaded, setLoaded] = useState(false)
19-
const timerRef = useRef<ReturnType<typeof setTimeout>>(null)
20-
21-
const activeBase = useFallback ? (ipBase ?? httpsBase) : (httpsBase ?? ipBase)
22-
const dashboardUrl = activeBase
23-
? buildDashboardUrl(activeBase, instance.gateway_token)
24-
: null
13+
export function InstanceDashboard({ instance: initial }: { instance: Instance }) {
14+
const { data: polledInstance } = usePolling<{ instance: Instance }>(
15+
`/api/instances/${initial.id}`,
16+
10000,
17+
)
18+
const instance = polledInstance?.instance ?? initial
2519

26-
useEffect(() => {
27-
return () => { if (timerRef.current) clearTimeout(timerRef.current) }
28-
}, [])
20+
usePolling<unknown>(
21+
instance.ip_address ? `/api/health/${instance.id}` : null,
22+
15000,
23+
)
2924

30-
function startLoadTimer() {
31-
if (timerRef.current) clearTimeout(timerRef.current)
32-
if (!useFallback && httpsBase && ipBase) {
33-
timerRef.current = setTimeout(() => {
34-
setUseFallback(true)
35-
setLoaded(false)
36-
}, LOAD_TIMEOUT_MS)
37-
}
38-
}
25+
const baseUrl = instance.dashboard_url
26+
?? (instance.ip_address ? `http://${instance.ip_address}` : null)
27+
const dashboardUrl = baseUrl
28+
? buildDashboardUrl(baseUrl, instance.gateway_token)
29+
: null
3930

40-
function handleLoad() {
41-
if (timerRef.current) clearTimeout(timerRef.current)
42-
setLoaded(true)
43-
}
31+
const [loaded, setLoaded] = useState(false)
4432

4533
if (!dashboardUrl) {
4634
return (
@@ -85,8 +73,7 @@ export function InstanceDashboard({ instance }: { instance: Instance }) {
8573
style={{ colorScheme: 'dark', background: 'var(--background, #0a0a0a)' }}
8674
title="OpenClaw Dashboard"
8775
allow="clipboard-read; clipboard-write"
88-
ref={(el) => { if (el) startLoadTimer() }}
89-
onLoad={handleLoad}
76+
onLoad={() => setLoaded(true)}
9077
/>
9178
</div>
9279
</div>

src/components/instances/instance-terminal-workspace.tsx

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
'use client'
22

3-
import { useEffect, useRef, useState } from 'react'
3+
import { useState } from 'react'
44
import { ExternalLink, LoaderCircle, Plus, RefreshCw, TerminalSquare, X } from 'lucide-react'
55
import { cn } from '@/lib/utils'
66
import { getInstanceTerminalConnection } from '@/lib/terminal'
7+
import { usePolling } from '@/hooks/use-polling'
78
import type { Instance } from '@/types/instance'
89

9-
const LOAD_TIMEOUT_MS = 8000
10-
1110
interface InstanceTerminalWorkspaceProps {
1211
instance: Instance
1312
className?: string
@@ -24,26 +23,25 @@ function createSession(index: number): TerminalSession {
2423
}
2524

2625
export function InstanceTerminalWorkspace({
27-
instance,
26+
instance: initial,
2827
className,
2928
}: InstanceTerminalWorkspaceProps) {
29+
const { data: polledInstance } = usePolling<{ instance: Instance }>(
30+
`/api/instances/${initial.id}`,
31+
10000,
32+
)
33+
const instance = polledInstance?.instance ?? initial
34+
35+
usePolling<unknown>(
36+
instance.ip_address ? `/api/health/${instance.id}` : null,
37+
15000,
38+
)
39+
3040
const connection = getInstanceTerminalConnection(instance)
3141
const [sessions, setSessions] = useState<TerminalSession[]>([createSession(1)])
3242
const [activeSessionId, setActiveSessionId] = useState('terminal-1')
3343
const [nextSessionNumber, setNextSessionNumber] = useState(2)
3444
const [loadedSessions, setLoadedSessions] = useState<Record<string, boolean>>({})
35-
const [useFallback, setUseFallback] = useState(false)
36-
const loadTimers = useRef<Record<string, ReturnType<typeof setTimeout>>>({})
37-
38-
const terminalUrl = useFallback && connection.directUrl
39-
? connection.directUrl
40-
: connection.preferredUrl
41-
42-
useEffect(() => {
43-
return () => {
44-
Object.values(loadTimers.current).forEach(clearTimeout)
45-
}
46-
}, [])
4745

4846
if (instance.status !== 'running') {
4947
return (
@@ -63,7 +61,7 @@ export function InstanceTerminalWorkspace({
6361
)
6462
}
6563

66-
if (!terminalUrl) {
64+
if (!connection.preferredUrl) {
6765
return (
6866
<div className="flex min-h-[28rem] items-center justify-center rounded-xl border bg-card p-8 text-center">
6967
<div className="max-w-sm space-y-3">
@@ -77,24 +75,9 @@ export function InstanceTerminalWorkspace({
7775
)
7876
}
7977

78+
const terminalUrl = connection.preferredUrl
8079
const activeSession = sessions.find((s) => s.id === activeSessionId) ?? sessions[0]
8180

82-
function startLoadTimer(sessionId: string) {
83-
clearTimeout(loadTimers.current[sessionId])
84-
if (!useFallback && connection.directUrl && connection.mode === 'proxied') {
85-
loadTimers.current[sessionId] = setTimeout(() => {
86-
setUseFallback(true)
87-
setLoadedSessions({})
88-
setSessions((prev) => prev.map((s) => ({ ...s, version: s.version + 1 })))
89-
}, LOAD_TIMEOUT_MS)
90-
}
91-
}
92-
93-
function handleLoad(sessionId: string) {
94-
clearTimeout(loadTimers.current[sessionId])
95-
setLoadedSessions((prev) => ({ ...prev, [sessionId]: true }))
96-
}
97-
9881
function createNewSession() {
9982
const session = createSession(nextSessionNumber)
10083
setSessions((prev) => [...prev, session])
@@ -112,7 +95,6 @@ export function InstanceTerminalWorkspace({
11295

11396
function closeSession(sessionId: string) {
11497
if (sessions.length === 1) return
115-
clearTimeout(loadTimers.current[sessionId])
11698
const idx = sessions.findIndex((s) => s.id === sessionId)
11799
const remaining = sessions.filter((s) => s.id !== sessionId)
118100
setSessions(remaining)
@@ -222,8 +204,7 @@ export function InstanceTerminalWorkspace({
222204
className="block h-full w-full"
223205
style={{ colorScheme: 'dark', background: '#0a0a0a' }}
224206
allow="clipboard-read; clipboard-write; fullscreen"
225-
ref={(el) => { if (el && isActive) startLoadTimer(session.id) }}
226-
onLoad={() => handleLoad(session.id)}
207+
onLoad={() => setLoadedSessions((prev) => ({ ...prev, [session.id]: true }))}
227208
/>
228209
</div>
229210
)

0 commit comments

Comments
 (0)