feat(web): W3-1 shared console routes, nav, api/sse/query-keys and shared components
- routes.ts registers all workspaces under /console (+ detail routes, engine, audit, login) - api.ts: cookie session first (credentials include, CSRF header), Bearer only with stored key, 401 -> /login - sse.ts: shared EventSource with ?after=seq catch-up, backoff reconnect, per-type subscribe - shared components: data-table, filter-bar, detail-sheet, timeline, json-view, confirm-dialog, form-field, kv-list, stat-tile, error/loading states; ui tabs and textarea Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
a24bda952b
commit
e5e42b1f0d
32 files changed
+1286
-71
No files matched your search
@@ -0,0 +1,85 @@
|
||||
import { LoaderCircle } from "lucide-react";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "~/components/ui/alert-dialog.tsx";
|
||||
import { Button } from "~/components/ui/button.tsx";
|
||||
|
||||
/**
|
||||
* 确认弹窗。onConfirm 可返回 Promise:执行中按钮禁用并转圈,resolve 后自动关闭,reject 时保持打开
|
||||
* (错误提示由调用方 toast)。受控(open/onOpenChange)或非受控(trigger)两种用法。
|
||||
* <ConfirmDialog trigger={<Button variant="destructive">删除</Button>} title="删除实例?"
|
||||
* description="…" confirmLabel="删除" destructive onConfirm={() => del.mutateAsync(id)} />
|
||||
*/
|
||||
export function ConfirmDialog({
|
||||
trigger,
|
||||
open: openProp,
|
||||
onOpenChange,
|
||||
title,
|
||||
description,
|
||||
confirmLabel = "确认",
|
||||
cancelLabel = "取消",
|
||||
destructive = false,
|
||||
onConfirm,
|
||||
children,
|
||||
}: {
|
||||
trigger?: ReactNode;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
confirmLabel?: string;
|
||||
cancelLabel?: string;
|
||||
destructive?: boolean;
|
||||
onConfirm: () => unknown | Promise<unknown>;
|
||||
/** 描述下方的额外内容(输入确认等) */
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
const [inner, setInner] = useState(false);
|
||||
const [pending, setPending] = useState(false);
|
||||
const open = openProp ?? inner;
|
||||
const setOpen = (o: boolean) => {
|
||||
if (pending) return;
|
||||
setInner(o);
|
||||
onOpenChange?.(o);
|
||||
};
|
||||
|
||||
const run = async () => {
|
||||
setPending(true);
|
||||
try {
|
||||
await onConfirm();
|
||||
setPending(false);
|
||||
setInner(false);
|
||||
onOpenChange?.(false);
|
||||
} catch {
|
||||
setPending(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||
{trigger ? <AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger> : null}
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{title}</AlertDialogTitle>
|
||||
{description ? <AlertDialogDescription>{description}</AlertDialogDescription> : null}
|
||||
</AlertDialogHeader>
|
||||
{children}
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={pending}>{cancelLabel}</AlertDialogCancel>
|
||||
<Button variant={destructive ? "destructive" : "default"} onClick={run} disabled={pending}>
|
||||
{pending ? <LoaderCircle className="animate-spin" /> : null}
|
||||
{confirmLabel}
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user