"use client"; import { useMemo, useState } from "react"; import Link from "next/link"; import { SignInButton, SignedIn, SignedOut } from "@clerk/nextjs"; import { type ColumnDef, type SortingState, flexRender, getCoreRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table"; import { useQueryClient } from "@tanstack/react-query"; import { DashboardSidebar } from "@/components/organisms/DashboardSidebar"; import { DashboardShell } from "@/components/templates/DashboardShell"; import { Button, buttonVariants } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { apiRequest, useAuthedMutation, useAuthedQuery } from "@/lib/api-query"; type Gateway = { id: string; name: string; url: string; token?: string | null; main_session_key: string; workspace_root: string; skyll_enabled?: boolean; created_at: string; updated_at: string; }; const truncate = (value?: string | null, max = 24) => { if (!value) return "—"; if (value.length <= max) return value; return `${value.slice(0, max)}…`; }; const formatTimestamp = (value?: string | null) => { if (!value) return "—"; const date = new Date(`${value}${value.endsWith("Z") ? "" : "Z"}`); if (Number.isNaN(date.getTime())) return "—"; return date.toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); }; export default function GatewaysPage() { const queryClient = useQueryClient(); const [sorting, setSorting] = useState([ { id: "name", desc: false }, ]); const [deleteTarget, setDeleteTarget] = useState(null); const gatewaysQuery = useAuthedQuery( ["gateways"], "/api/v1/gateways", { refetchInterval: 30_000, refetchOnMount: "always", } ); const gateways = useMemo(() => gatewaysQuery.data ?? [], [gatewaysQuery.data]); const sortedGateways = useMemo(() => [...gateways], [gateways]); const deleteMutation = useAuthedMutation< void, Gateway, { previous?: Gateway[] } >( async (gateway, token) => apiRequest(`/api/v1/gateways/${gateway.id}`, { method: "DELETE", token, }), { onMutate: async (gateway) => { await queryClient.cancelQueries({ queryKey: ["gateways"] }); const previous = queryClient.getQueryData(["gateways"]); queryClient.setQueryData(["gateways"], (old = []) => old.filter((item) => item.id !== gateway.id) ); return { previous }; }, onError: (_error, _gateway, context) => { if (context?.previous) { queryClient.setQueryData(["gateways"], context.previous); } }, onSuccess: () => { setDeleteTarget(null); }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ["gateways"] }); }, } ); const handleDelete = () => { if (!deleteTarget) return; deleteMutation.mutate(deleteTarget); }; const columns = useMemo[]>( () => [ { accessorKey: "name", header: "Gateway", cell: ({ row }) => (

{row.original.name}

{truncate(row.original.url, 36)}

), }, { accessorKey: "main_session_key", header: "Main session", cell: ({ row }) => ( {truncate(row.original.main_session_key, 24)} ), }, { accessorKey: "workspace_root", header: "Workspace root", cell: ({ row }) => ( {truncate(row.original.workspace_root, 28)} ), }, { accessorKey: "skyll_enabled", header: "Skyll", cell: ({ row }) => ( {row.original.skyll_enabled ? "Enabled" : "Off"} ), }, { accessorKey: "updated_at", header: "Updated", cell: ({ row }) => ( {formatTimestamp(row.original.updated_at)} ), }, { id: "actions", header: "", cell: ({ row }) => (
Edit
), }, ], [] ); // eslint-disable-next-line react-hooks/incompatible-library const table = useReactTable({ data: sortedGateways, columns, state: { sorting }, onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), }); return (

Sign in to view gateways.

Gateways

Manage OpenClaw gateway connections used by boards

{gateways.length > 0 ? ( Create gateway ) : null}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( ))} ))} {gatewaysQuery.isLoading ? ( ) : table.getRowModel().rows.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( ))} )) ) : ( )}
{header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )}
Loading…
{flexRender( cell.column.columnDef.cell, cell.getContext() )}

No gateways yet

Create your first gateway to connect boards and start managing your OpenClaw connections.

Create your first gateway
{gatewaysQuery.error ? (

{gatewaysQuery.error.message}

) : null}
setDeleteTarget(null)}> Delete gateway? This removes the gateway connection from Mission Control. Boards using it will need a new gateway assigned. {deleteMutation.error ? (

{deleteMutation.error.message}

) : null}
); }