diff --git a/src/app/(app)/claw/components/ClawDashboard.tsx b/src/app/(app)/claw/components/ClawDashboard.tsx index 8b81798f4e..345835c7f9 100644 --- a/src/app/(app)/claw/components/ClawDashboard.tsx +++ b/src/app/(app)/claw/components/ClawDashboard.tsx @@ -1,7 +1,7 @@ 'use client'; import type { KiloClawDashboardStatus } from '@/lib/kiloclaw/types'; -import { useKiloClawMutations } from '@/hooks/useKiloClaw'; +import { useKiloClawGatewayStatus, useKiloClawMutations } from '@/hooks/useKiloClaw'; import { Card, CardContent } from '@/components/ui/card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { useGatewayUrl } from '../hooks/useGatewayUrl'; @@ -27,6 +27,12 @@ export function ClawDashboard({ status }: { status: KiloClawDashboardStatus | un const mutations = useKiloClawMutations(); const gatewayUrl = useGatewayUrl(status); const instanceStatus = hasPopulatedStatus(status) ? status : null; + const isRunning = instanceStatus?.status === 'running'; + const { + data: gatewayStatus, + isLoading: gatewayLoading, + error: gatewayError, + } = useKiloClawGatewayStatus(isRunning); return (
- Manage power state and gateway lifecycle. -
+Manage power state and gateway lifecycle.
++ Gateway status is available when the machine is running. +
+ ); + } + + if (gatewayLoading) { + return ( ++ Failed to load gateway status: {gatewayError.message} +
+ ); + } -State
+Uptime
+{formatUptime(gatewayStatus.uptime)}
+Restarts
+{gatewayStatus.restarts}
+Last Exit
++ {gatewayStatus.lastExit ? formatLastExit(gatewayStatus.lastExit) : '—'} +
+Provisioned
+{formatTs(status.provisionedAt)}
diff --git a/src/app/(app)/claw/components/changelog-data.ts b/src/app/(app)/claw/components/changelog-data.ts index 9cba7e39fd..64872a8b58 100644 --- a/src/app/(app)/claw/components/changelog-data.ts +++ b/src/app/(app)/claw/components/changelog-data.ts @@ -10,6 +10,13 @@ export type ChangelogEntry = { // Newest entries first. Developers add new entries to the top of this array. export const CHANGELOG_ENTRIES: ChangelogEntry[] = [ + { + date: '2026-02-23', + description: + 'Redesigned dashboard: live gateway process status, added ability to restart OpenClaw gateway.', + category: 'feature', + deployHint: null, + }, { date: '2026-02-23', description: 'Deploy OpenClaw 2026.2.22. Added device pairing support to the dashboard.', diff --git a/src/app/(app)/claw/components/claw.types.ts b/src/app/(app)/claw/components/claw.types.ts index cf00891214..14ea19a639 100644 --- a/src/app/(app)/claw/components/claw.types.ts +++ b/src/app/(app)/claw/components/claw.types.ts @@ -34,11 +34,11 @@ export const CLAW_STATUS_BADGE: Record< { label: string; className: string } > = { running: { - label: 'Running', + label: 'Machine Online', className: 'border-emerald-500/30 bg-emerald-500/15 text-emerald-400', }, stopped: { - label: 'Stopped', + label: 'Machine Stopped', className: 'border-red-500/30 bg-red-500/15 text-red-400', }, provisioned: { diff --git a/src/hooks/useKiloClaw.ts b/src/hooks/useKiloClaw.ts index 09754b1930..a3a9ace985 100644 --- a/src/hooks/useKiloClaw.ts +++ b/src/hooks/useKiloClaw.ts @@ -61,6 +61,16 @@ export function useRefreshDevicePairing() { }; } +export function useKiloClawGatewayStatus(enabled: boolean) { + const trpc = useTRPC(); + return useQuery( + trpc.kiloclaw.gatewayStatus.queryOptions(undefined, { + enabled, + refetchInterval: enabled ? 30_000 : false, + }) + ); +} + export function useKiloClawMutations() { const trpc = useTRPC(); const queryClient = useQueryClient(); @@ -93,7 +103,24 @@ export function useKiloClawMutations() { }) ), restartGateway: useMutation( - trpc.kiloclaw.restartGateway.mutationOptions({ onSuccess: invalidateStatus }) + trpc.kiloclaw.restartGateway.mutationOptions({ + onSuccess: async () => { + await invalidateStatus(); + await queryClient.invalidateQueries({ + queryKey: trpc.kiloclaw.gatewayStatus.queryKey(), + }); + }, + }) + ), + restartOpenClaw: useMutation( + trpc.kiloclaw.restartOpenClaw.mutationOptions({ + onSuccess: async () => { + await invalidateStatus(); + await queryClient.invalidateQueries({ + queryKey: trpc.kiloclaw.gatewayStatus.queryKey(), + }); + }, + }) ), approvePairingRequest: useMutation( trpc.kiloclaw.approvePairingRequest.mutationOptions({ diff --git a/src/routers/kiloclaw-router.ts b/src/routers/kiloclaw-router.ts index d3f1387dd1..5449fbbd0d 100644 --- a/src/routers/kiloclaw-router.ts +++ b/src/routers/kiloclaw-router.ts @@ -287,6 +287,16 @@ export const kiloclawRouter = createTRPCRouter({ return client.approveDevicePairingRequest(ctx.user.id, input.requestId); }), + gatewayStatus: kiloclawProcedure.query(async ({ ctx }) => { + const client = new KiloClawInternalClient(); + return client.getGatewayStatus(ctx.user.id); + }), + + restartOpenClaw: kiloclawProcedure.mutation(async ({ ctx }) => { + const client = new KiloClawInternalClient(); + return client.restartGatewayProcess(ctx.user.id); + }), + runDoctor: kiloclawProcedure.mutation(async ({ ctx }) => { const client = new KiloClawInternalClient(); return client.runDoctor(ctx.user.id);