feat: complete catalog fleet management workflow
This commit is contained in:
@@ -2,6 +2,7 @@ import type { RefObject, ReactNode } from "react";
|
||||
import {
|
||||
ArrowLeft,
|
||||
Database,
|
||||
History,
|
||||
RefreshCw,
|
||||
Save,
|
||||
TestTube2,
|
||||
@@ -140,12 +141,17 @@ interface DatabaseFormProps {
|
||||
onSync: (scope: CatalogSyncScope) => void;
|
||||
onOpenSync: () => void;
|
||||
activeSyncRun?: CatalogSyncRun;
|
||||
presentation?: "page" | "drawer";
|
||||
}
|
||||
|
||||
function formTitle(mode: DatabaseFormMode, workspaceLocked: boolean): string {
|
||||
export function databaseFormTitle(
|
||||
mode: DatabaseFormMode,
|
||||
workspaceLocked: boolean,
|
||||
presentation: "page" | "drawer" = "page",
|
||||
): string {
|
||||
if (mode === "add") return workspaceLocked ? "Edit database" : "Add database";
|
||||
if (mode === "edit") return "Edit database";
|
||||
if (mode === "delete") return "Delete database";
|
||||
if (mode === "delete") return presentation === "drawer" ? "Remove configuration" : "Delete database";
|
||||
return "Database details";
|
||||
}
|
||||
|
||||
@@ -180,8 +186,9 @@ export function DatabaseForm({
|
||||
onSync,
|
||||
onOpenSync,
|
||||
activeSyncRun,
|
||||
presentation = "page",
|
||||
}: DatabaseFormProps) {
|
||||
const title = formTitle(mode, workspaceLocked);
|
||||
const title = databaseFormTitle(mode, workspaceLocked, presentation);
|
||||
const readOnly = mode === "view" || mode === "delete";
|
||||
const editable = mode === "add" || mode === "edit";
|
||||
const busy = busyAction !== null;
|
||||
@@ -212,7 +219,7 @@ export function DatabaseForm({
|
||||
{row.workspaceAvailable
|
||||
? "The repository workspace remains available and will return to the list as Not configured. "
|
||||
: "This orphaned catalog row will disappear from the list. "}
|
||||
Secret references associated with this configuration are removed too.
|
||||
Secret references associated with this configuration are removed too. The source database is not modified.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -425,13 +432,50 @@ export function DatabaseForm({
|
||||
) : null}
|
||||
{mode === "delete" ? (
|
||||
<Button type="button" variant="destructive" disabled={!canManage || !row.configured || !row.id || busy || stale} onClick={onDelete}>
|
||||
<Trash2 /> {busyAction === "delete" ? "Deleting…" : "Delete database"}
|
||||
<Trash2 /> {busyAction === "delete" ? "Removing…" : presentation === "drawer" ? "Remove configuration" : "Delete database"}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
if (presentation === "drawer") {
|
||||
return (
|
||||
<section aria-label={`${title} form`} className="thot-fleet-drawer-form">
|
||||
<h2 ref={headingRef} tabIndex={-1} className="sr-only">{title}</h2>
|
||||
<div className="thot-fleet-drawer-form__context">
|
||||
<div className="min-w-0">
|
||||
<p className="thot-label">Workspace</p>
|
||||
<p className="mt-1 truncate text-sm font-semibold">{row.workspaceName}</p>
|
||||
<p className="mt-0.5 truncate font-mono text-xs text-muted-foreground">{row.workspaceId}</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center justify-end gap-2">
|
||||
<Database className="size-4 text-muted-foreground" aria-hidden="true" />
|
||||
<span className="rounded-md border border-border bg-muted px-2.5 py-1 text-xs font-semibold text-muted-foreground">
|
||||
{statusLabel(row)}
|
||||
</span>
|
||||
{mode === "view" && row.configured ? (
|
||||
<Button type="button" variant="outline" size="sm" className="thot-fleet-nav-action thot-fleet-nav-action--below" data-tooltip="Synchronization history" onClick={onOpenSync}>
|
||||
{activeSyncRun ? <RefreshCw className="animate-spin" /> : <History />} Sync history
|
||||
</Button>
|
||||
) : null}
|
||||
{mode === "view" ? (
|
||||
<DatabaseSyncMenu
|
||||
disabled={!canManage || !bindingReady || busy || Boolean(activeSyncRun)}
|
||||
disabledReason={!bindingReady ? "Test the current database binding before synchronizing the schema" : undefined}
|
||||
onSelect={onSync}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{row.lastTestedAt ? (
|
||||
<p className="mb-4 text-xs text-muted-foreground">Last tested {new Date(row.lastTestedAt).toLocaleString()}</p>
|
||||
) : null}
|
||||
{form}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section aria-label={`${title} form`} className="min-h-0 flex-1 overflow-y-auto bg-background">
|
||||
<div className="mx-auto w-full max-w-[900px] px-4 py-5 sm:px-6 sm:py-6">
|
||||
|
||||
Reference in New Issue
Block a user