feat: complete catalog fleet management workflow

This commit is contained in:
Codex
2026-08-31 15:58:43 +02:00
parent 866aee4249
commit 9c697dc062
56 changed files with 6692 additions and 611 deletions
+77 -31
View File
@@ -5,6 +5,7 @@ import { CentralStatus } from "./CentralStatus";
import { ModelActivityPanel } from "./ModelActivityPanel";
import { WorkspaceManager } from "./WorkspaceManager";
import { PiManagement } from "./PiManagement";
import { WorkAreaPanelHost } from "./WorkAreaPanel";
import { useActivityPanelResize } from "./useActivityPanelResize";
import { useSessionPanelResize } from "./useSessionPanelResize";
import { NavSessions } from "./NavSessions";
@@ -41,6 +42,35 @@ interface AppShellProps {
}
type ActiveSurface = "core" | "database-management";
type ActiveManagementPanel = "workspace" | "pi" | null;
type DatabaseManagementPresentation = "legacy" | "fleet";
interface DatabaseManagementPresentationEnvironment {
isDevelopment: boolean;
mode: string;
legacyFlag?: string | boolean;
search: string;
}
/**
* Fleet Ledger is the release-safe default. The old surface can only be
* requested explicitly from a development server or a staging build, so an
* accidentally retained flag can never switch the production UI back.
*/
export function resolveDatabaseManagementPresentation({
isDevelopment,
mode,
legacyFlag,
search,
}: DatabaseManagementPresentationEnvironment): DatabaseManagementPresentation {
const legacyAllowed = isDevelopment || mode.trim().toLowerCase() === "staging";
if (!legacyAllowed) return "fleet";
const legacyRequestedByQuery = new URLSearchParams(search).get("db-ui")?.toLowerCase() === "legacy";
const legacyRequestedByEnvironment = String(legacyFlag ?? "").trim().toLowerCase() === "true";
return legacyRequestedByQuery || legacyRequestedByEnvironment ? "legacy" : "fleet";
}
export function AppShell({ canLogout }: AppShellProps) {
const authenticatedUser = useAuthUser();
@@ -123,12 +153,17 @@ export function AppShell({ canLogout }: AppShellProps) {
const queryClient = useQueryClient();
const [showActivity, setShowActivity] = useState(false);
const [activeSurface, setActiveSurface] = useState<ActiveSurface>("core");
const databaseManagementPresentation = resolveDatabaseManagementPresentation({
isDevelopment: import.meta.env.DEV,
mode: import.meta.env.MODE,
legacyFlag: import.meta.env.VITE_DB_MANAGEMENT_LEGACY,
search: window.location.search,
});
const databaseNavigationRef = useRef({ dirty: false, busy: false });
const updateDatabaseNavigationState = useCallback((state: { dirty: boolean; busy: boolean }) => {
databaseNavigationRef.current = state;
}, []);
const [workspaceManagerOpen, setWorkspaceManagerOpen] = useState(false);
const [piManagementOpen, setPiManagementOpen] = useState(false);
const [activeManagementPanel, setActiveManagementPanel] = useState<ActiveManagementPanel>(null);
const [activeOpen, setActiveOpen] = useState(true);
const [archiveOpen, setArchiveOpen] = useState(false);
const [renameTarget, setRenameTarget] = useState<SessionSummary | null>(null);
@@ -218,6 +253,7 @@ export function AppShell({ canLogout }: AppShellProps) {
const s = sessions.find((x) => x.id === id);
if (!s) return;
setActiveSurface("core");
setActiveManagementPanel(null);
// A session with a live Pi runtime opens straight into its live view: doResume
// reconnects to the already-active runtime and replays its pending gate, so an
// in-progress session never shows an empty screen that reads as "stopped". Cold or
@@ -239,6 +275,7 @@ export function AppShell({ canLogout }: AppShellProps) {
async function doResume(id: string, databaseExitApproved = false) {
if (!databaseExitApproved && !canLeaveDatabaseManagement()) return;
setActiveSurface("core");
setActiveManagementPanel(null);
const guard = captureAuthOperation({ sessionId: id, disposalEpoch: operationEpochRef.current });
if (!guard) return;
const token = ++resumeInvocationRef.current;
@@ -522,6 +559,7 @@ export function AppShell({ canLogout }: AppShellProps) {
function startNewSession() {
if (!canLeaveDatabaseManagement()) return;
setActiveSurface("core");
setActiveManagementPanel(null);
invalidateResumeIntent();
newSessionOperationRef.current = null;
resetSession();
@@ -623,7 +661,7 @@ export function AppShell({ canLogout }: AppShellProps) {
</div>
</div>
)}
{showActivity && <ModelActivityPanel desktopSplit={desktopSplit} onClose={() => setShowActivity(false)} onOpenWorkspaceManager={() => setWorkspaceManagerOpen(true)} hidden={activeSurface !== "core"} />}
{showActivity && <ModelActivityPanel desktopSplit={desktopSplit} onClose={() => setShowActivity(false)} onOpenWorkspaceManager={() => setActiveManagementPanel("workspace")} hidden={activeSurface !== "core"} />}
{activeSurface === "core" && showActivity && desktopSplit && (
<div className="relative w-0 shrink-0">
<div
@@ -638,12 +676,13 @@ export function AppShell({ canLogout }: AppShellProps) {
)}
{/* Conversation column */}
<div data-testid="conversation-column" className="flex min-w-0 flex-1 flex-col">
<WorkAreaPanelHost data-testid="conversation-column" className="flex min-w-0 flex-1 flex-col">
{activeSurface === "database-management" && (
<DatabaseManagementPage
canManage={canManageDatabase}
canManageSecrets={canManageWorkspaceSecrets}
onNavigationStateChange={updateDatabaseNavigationState}
presentation={databaseManagementPresentation}
/>
)}
<div
@@ -727,11 +766,23 @@ export function AppShell({ canLogout }: AppShellProps) {
</div>
</div>
</div>
</div>
<WorkspaceManager
open={activeManagementPanel === "workspace"}
onClose={() => setActiveManagementPanel(null)}
canManageWorkspace={canManageWorkspace}
canManageSecrets={canManageWorkspaceSecrets}
/>
{canManagePi && (
<PiManagement
open={activeManagementPanel === "pi"}
onClose={() => setActiveManagementPanel(null)}
/>
)}
</WorkAreaPanelHost>
{/* Right session rail */}
{!showActivity && (
<aside className="flex w-[15vw] shrink-0 flex-col border-l border-border bg-sidebar">
{(activeSurface === "database-management" || !showActivity) && (
<aside aria-label="Session navigation" className="flex w-[15vw] shrink-0 flex-col bg-sidebar">
<div className="relative px-4 pb-3 pt-5 text-center">
<h1 className="font-heading text-xl font-semibold leading-none tracking-tight text-foreground">
Thoth<span className="text-primary">II</span>
@@ -767,25 +818,28 @@ export function AppShell({ canLogout }: AppShellProps) {
<Button
variant="outline"
size="sm"
className="w-full"
onClick={() => {
if (!canLeaveDatabaseManagement()) return;
setActiveSurface("core");
setWorkspaceManagerOpen(true);
}}
className="w-full"
onClick={() => {
if (!canLeaveDatabaseManagement()) return;
setActiveSurface("core");
setActiveManagementPanel("workspace");
}}
>
Workspace management
</Button>
{canManageDatabase && (
<Button
variant="outline"
size="sm"
className="w-full"
aria-current={activeSurface === "database-management" ? "page" : undefined}
onClick={() => setActiveSurface("database-management")}
>
Database management
</Button>
<Button
variant={activeSurface === "database-management" ? "secondary" : "outline"}
size="sm"
className="w-full"
aria-current={activeSurface === "database-management" ? "page" : undefined}
onClick={() => {
setActiveManagementPanel(null);
setActiveSurface("database-management");
}}
>
Database management
</Button>
)}
{canManagePi && (
<Button
@@ -795,7 +849,7 @@ export function AppShell({ canLogout }: AppShellProps) {
onClick={() => {
if (!canLeaveDatabaseManagement()) return;
setActiveSurface("core");
setPiManagementOpen(true);
setActiveManagementPanel("pi");
}}
>
Pi management
@@ -951,14 +1005,6 @@ export function AppShell({ canLogout }: AppShellProps) {
</div>
</aside>
)}
<WorkspaceManager
open={workspaceManagerOpen}
onClose={() => setWorkspaceManagerOpen(false)}
canManageWorkspace={canManageWorkspace}
canManageSecrets={canManageWorkspaceSecrets}
/>
{canManagePi && <PiManagement open={piManagementOpen} onClose={() => setPiManagementOpen(false)} />}
<Toaster />
<StopConfirmDialog