feat: add board group models and update related interfaces
This commit is contained in:
@@ -67,7 +67,7 @@ export default function EditGatewayPage() {
|
||||
"idle" | "checking" | "success" | "error"
|
||||
>("idle");
|
||||
const [gatewayCheckMessage, setGatewayCheckMessage] = useState<string | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -156,7 +156,7 @@ export default function EditGatewayPage() {
|
||||
} catch (err) {
|
||||
setGatewayCheckStatus("error");
|
||||
setGatewayCheckMessage(
|
||||
err instanceof Error ? err.message : "Unable to reach gateway."
|
||||
err instanceof Error ? err.message : "Unable to reach gateway.",
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -343,7 +343,6 @@ export default function EditGatewayPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{errorMessage ? (
|
||||
<p className="text-sm text-red-500">{errorMessage}</p>
|
||||
) : null}
|
||||
|
||||
@@ -92,7 +92,9 @@ export default function GatewayDetailPage() {
|
||||
|
||||
const agents = useMemo(
|
||||
() =>
|
||||
agentsQuery.data?.status === 200 ? agentsQuery.data.data.items ?? [] : [],
|
||||
agentsQuery.data?.status === 200
|
||||
? (agentsQuery.data.data.items ?? [])
|
||||
: [],
|
||||
[agentsQuery.data],
|
||||
);
|
||||
|
||||
@@ -102,7 +104,7 @@ export default function GatewayDetailPage() {
|
||||
|
||||
const title = useMemo(
|
||||
() => (gateway?.name ? gateway.name : "Gateway"),
|
||||
[gateway?.name]
|
||||
[gateway?.name],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -111,7 +113,10 @@ export default function GatewayDetailPage() {
|
||||
<div className="col-span-2 flex min-h-[calc(100vh-64px)] items-center justify-center bg-slate-50 p-10 text-center">
|
||||
<div className="rounded-xl border border-slate-200 bg-white px-8 py-6 shadow-sm">
|
||||
<p className="text-sm text-slate-600">Sign in to view a gateway.</p>
|
||||
<SignInButton mode="modal" forceRedirectUrl={`/gateways/${gatewayId}`}>
|
||||
<SignInButton
|
||||
mode="modal"
|
||||
forceRedirectUrl={`/gateways/${gatewayId}`}
|
||||
>
|
||||
<Button className="mt-4">Sign in</Button>
|
||||
</SignInButton>
|
||||
</div>
|
||||
@@ -138,7 +143,9 @@ export default function GatewayDetailPage() {
|
||||
Back to gateways
|
||||
</Button>
|
||||
{gatewayId ? (
|
||||
<Button onClick={() => router.push(`/gateways/${gatewayId}/edit`)}>
|
||||
<Button
|
||||
onClick={() => router.push(`/gateways/${gatewayId}/edit`)}
|
||||
>
|
||||
Edit gateway
|
||||
</Button>
|
||||
) : null}
|
||||
@@ -184,13 +191,17 @@ export default function GatewayDetailPage() {
|
||||
</div>
|
||||
<div className="mt-4 space-y-3 text-sm text-slate-700">
|
||||
<div>
|
||||
<p className="text-xs uppercase text-slate-400">Gateway URL</p>
|
||||
<p className="text-xs uppercase text-slate-400">
|
||||
Gateway URL
|
||||
</p>
|
||||
<p className="mt-1 text-sm font-medium text-slate-900">
|
||||
{gateway.url}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs uppercase text-slate-400">Token</p>
|
||||
<p className="text-xs uppercase text-slate-400">
|
||||
Token
|
||||
</p>
|
||||
<p className="mt-1 text-sm font-medium text-slate-900">
|
||||
{maskToken(gateway.token)}
|
||||
</p>
|
||||
@@ -212,20 +223,26 @@ export default function GatewayDetailPage() {
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs uppercase text-slate-400">Workspace root</p>
|
||||
<p className="text-xs uppercase text-slate-400">
|
||||
Workspace root
|
||||
</p>
|
||||
<p className="mt-1 text-sm font-medium text-slate-900">
|
||||
{gateway.workspace_root}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<p className="text-xs uppercase text-slate-400">Created</p>
|
||||
<p className="text-xs uppercase text-slate-400">
|
||||
Created
|
||||
</p>
|
||||
<p className="mt-1 text-sm font-medium text-slate-900">
|
||||
{formatTimestamp(gateway.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs uppercase text-slate-400">Updated</p>
|
||||
<p className="text-xs uppercase text-slate-400">
|
||||
Updated
|
||||
</p>
|
||||
<p className="mt-1 text-sm font-medium text-slate-900">
|
||||
{formatTimestamp(gateway.updated_at)}
|
||||
</p>
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function NewGatewayPage() {
|
||||
const [gatewayUrl, setGatewayUrl] = useState("");
|
||||
const [gatewayToken, setGatewayToken] = useState("");
|
||||
const [mainSessionKey, setMainSessionKey] = useState(
|
||||
DEFAULT_MAIN_SESSION_KEY
|
||||
DEFAULT_MAIN_SESSION_KEY,
|
||||
);
|
||||
const [workspaceRoot, setWorkspaceRoot] = useState(DEFAULT_WORKSPACE_ROOT);
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function NewGatewayPage() {
|
||||
"idle" | "checking" | "success" | "error"
|
||||
>("idle");
|
||||
const [gatewayCheckMessage, setGatewayCheckMessage] = useState<string | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -121,7 +121,7 @@ export default function NewGatewayPage() {
|
||||
} catch (err) {
|
||||
setGatewayCheckStatus("error");
|
||||
setGatewayCheckMessage(
|
||||
err instanceof Error ? err.message : "Unable to reach gateway."
|
||||
err instanceof Error ? err.message : "Unable to reach gateway.",
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -167,7 +167,9 @@ export default function NewGatewayPage() {
|
||||
<SignedOut>
|
||||
<div className="col-span-2 flex min-h-[calc(100vh-64px)] items-center justify-center bg-slate-50 p-10 text-center">
|
||||
<div className="rounded-xl border border-slate-200 bg-white px-8 py-6 shadow-sm">
|
||||
<p className="text-sm text-slate-600">Sign in to create a gateway.</p>
|
||||
<p className="text-sm text-slate-600">
|
||||
Sign in to create a gateway.
|
||||
</p>
|
||||
<SignInButton mode="modal" forceRedirectUrl="/gateways/new">
|
||||
<Button className="mt-4">Sign in</Button>
|
||||
</SignInButton>
|
||||
@@ -302,7 +304,6 @@ export default function NewGatewayPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{error ? <p className="text-sm text-red-500">{error}</p> : null}
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
|
||||
@@ -78,9 +78,9 @@ export default function GatewaysPage() {
|
||||
const gateways = useMemo(
|
||||
() =>
|
||||
gatewaysQuery.data?.status === 200
|
||||
? gatewaysQuery.data.data.items ?? []
|
||||
? (gatewaysQuery.data.data.items ?? [])
|
||||
: [],
|
||||
[gatewaysQuery.data]
|
||||
[gatewaysQuery.data],
|
||||
);
|
||||
const sortedGateways = useMemo(() => [...gateways], [gateways]);
|
||||
|
||||
@@ -93,20 +93,25 @@ export default function GatewaysPage() {
|
||||
onMutate: async ({ gatewayId }) => {
|
||||
await queryClient.cancelQueries({ queryKey: gatewaysKey });
|
||||
const previous =
|
||||
queryClient.getQueryData<listGatewaysApiV1GatewaysGetResponse>(gatewaysKey);
|
||||
queryClient.getQueryData<listGatewaysApiV1GatewaysGetResponse>(
|
||||
gatewaysKey,
|
||||
);
|
||||
if (previous && previous.status === 200) {
|
||||
const nextItems = previous.data.items.filter(
|
||||
(gateway) => gateway.id !== gatewayId
|
||||
(gateway) => gateway.id !== gatewayId,
|
||||
);
|
||||
const removedCount = previous.data.items.length - nextItems.length;
|
||||
queryClient.setQueryData<listGatewaysApiV1GatewaysGetResponse>(gatewaysKey, {
|
||||
...previous,
|
||||
data: {
|
||||
...previous.data,
|
||||
items: nextItems,
|
||||
total: Math.max(0, previous.data.total - removedCount),
|
||||
queryClient.setQueryData<listGatewaysApiV1GatewaysGetResponse>(
|
||||
gatewaysKey,
|
||||
{
|
||||
...previous,
|
||||
data: {
|
||||
...previous.data,
|
||||
items: nextItems,
|
||||
total: Math.max(0, previous.data.total - removedCount),
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
}
|
||||
return { previous };
|
||||
},
|
||||
@@ -123,7 +128,7 @@ export default function GatewaysPage() {
|
||||
},
|
||||
},
|
||||
},
|
||||
queryClient
|
||||
queryClient,
|
||||
);
|
||||
|
||||
const handleDelete = () => {
|
||||
@@ -137,10 +142,7 @@ export default function GatewaysPage() {
|
||||
accessorKey: "name",
|
||||
header: "Gateway",
|
||||
cell: ({ row }) => (
|
||||
<Link
|
||||
href={`/gateways/${row.original.id}`}
|
||||
className="group block"
|
||||
>
|
||||
<Link href={`/gateways/${row.original.id}`} className="group block">
|
||||
<p className="text-sm font-medium text-slate-900 group-hover:text-blue-600">
|
||||
{row.original.name}
|
||||
</p>
|
||||
@@ -181,25 +183,25 @@ export default function GatewaysPage() {
|
||||
id: "actions",
|
||||
header: "",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Link
|
||||
href={`/gateways/${row.original.id}/edit`}
|
||||
className={buttonVariants({ variant: "ghost", size: "sm" })}
|
||||
>
|
||||
Edit
|
||||
</Link>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setDeleteTarget(row.original)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Link
|
||||
href={`/gateways/${row.original.id}/edit`}
|
||||
className={buttonVariants({ variant: "ghost", size: "sm" })}
|
||||
>
|
||||
Edit
|
||||
</Link>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setDeleteTarget(row.original)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
// eslint-disable-next-line react-hooks/incompatible-library
|
||||
@@ -238,17 +240,20 @@ export default function GatewaysPage() {
|
||||
Manage OpenClaw gateway connections used by boards
|
||||
</p>
|
||||
</div>
|
||||
{gateways.length > 0 ? (
|
||||
<Link
|
||||
href="/gateways/new"
|
||||
className={buttonVariants({ size: "md", variant: "primary" })}
|
||||
>
|
||||
Create gateway
|
||||
</Link>
|
||||
) : null}
|
||||
{gateways.length > 0 ? (
|
||||
<Link
|
||||
href="/gateways/new"
|
||||
className={buttonVariants({
|
||||
size: "md",
|
||||
variant: "primary",
|
||||
})}
|
||||
>
|
||||
Create gateway
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-8">
|
||||
<div className="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
|
||||
@@ -263,7 +268,7 @@ export default function GatewaysPage() {
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
header.getContext(),
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
@@ -274,7 +279,9 @@ export default function GatewaysPage() {
|
||||
{gatewaysQuery.isLoading ? (
|
||||
<tr>
|
||||
<td colSpan={columns.length} className="px-6 py-8">
|
||||
<span className="text-sm text-slate-500">Loading…</span>
|
||||
<span className="text-sm text-slate-500">
|
||||
Loading…
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
) : table.getRowModel().rows.length ? (
|
||||
@@ -284,7 +291,7 @@ export default function GatewaysPage() {
|
||||
<td key={cell.id} className="px-6 py-4">
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
cell.getContext(),
|
||||
)}
|
||||
</td>
|
||||
))}
|
||||
@@ -324,7 +331,10 @@ export default function GatewaysPage() {
|
||||
</p>
|
||||
<Link
|
||||
href="/gateways/new"
|
||||
className={buttonVariants({ size: "md", variant: "primary" })}
|
||||
className={buttonVariants({
|
||||
size: "md",
|
||||
variant: "primary",
|
||||
})}
|
||||
>
|
||||
Create your first gateway
|
||||
</Link>
|
||||
@@ -342,12 +352,14 @@ export default function GatewaysPage() {
|
||||
{gatewaysQuery.error.message}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</SignedIn>
|
||||
|
||||
<Dialog open={Boolean(deleteTarget)} onOpenChange={() => setDeleteTarget(null)}>
|
||||
<Dialog
|
||||
open={Boolean(deleteTarget)}
|
||||
onOpenChange={() => setDeleteTarget(null)}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete gateway?</DialogTitle>
|
||||
|
||||
Reference in New Issue
Block a user