feat: implement metadata catalog database management
This commit is contained in:
@@ -32,7 +32,7 @@ import {
|
||||
import type { SessionScope, SessionSummary } from "../api/types";
|
||||
import { useAuthGeneration, useAuthUser } from "../auth/authState";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { CSSProperties } from "react";
|
||||
import { captureAuthOperation, isAuthOperationCurrent, StaleAuthOperationError, type AuthOperationGuard } from "../auth/authOperation";
|
||||
|
||||
@@ -104,8 +104,8 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
const canReadAllSessions = permissions.includes("session.read_all");
|
||||
const canManageWorkspace = permissions.includes("workspace.manage");
|
||||
const canManageWorkspaceSecrets = permissions.includes("workspace.secrets.manage");
|
||||
const canManageDatabase = permissions.includes("database.manage");
|
||||
const canManagePi = permissions.includes("pi.manage");
|
||||
const isAdmin = authenticatedUser?.isAdmin === true;
|
||||
const authGeneration = useAuthGeneration();
|
||||
const { data: sessions = [] } = useQuery<SessionSummary[]>({
|
||||
queryKey: ["sessions", sessionScope],
|
||||
@@ -123,6 +123,10 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const [showActivity, setShowActivity] = useState(false);
|
||||
const [activeSurface, setActiveSurface] = useState<ActiveSurface>("core");
|
||||
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 [activeOpen, setActiveOpen] = useState(true);
|
||||
@@ -197,7 +201,20 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
setSelectedSessionIds(selected ? new Set(sessions.map((session) => session.id)) : new Set());
|
||||
}
|
||||
|
||||
function canLeaveDatabaseManagement(): boolean {
|
||||
if (activeSurface !== "database-management") return true;
|
||||
if (databaseNavigationRef.current.busy) {
|
||||
toast.info("Wait for the database operation to finish before leaving this page");
|
||||
return false;
|
||||
}
|
||||
if (databaseNavigationRef.current.dirty) {
|
||||
return window.confirm("Discard unsaved database changes and leave database management?");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function openPanel(id: string) {
|
||||
if (!canLeaveDatabaseManagement()) return;
|
||||
const s = sessions.find((x) => x.id === id);
|
||||
if (!s) return;
|
||||
setActiveSurface("core");
|
||||
@@ -207,7 +224,7 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
// completed sessions keep the read-only documents panel (with its explicit Resume),
|
||||
// so a mere click never spawns a runtime.
|
||||
if (s.active && s.status === "open" && !s.archived && !isForeignSession(s)) {
|
||||
void doResume(id);
|
||||
void doResume(id, true);
|
||||
return;
|
||||
}
|
||||
setPanelSession(s); setShowActivity(false);
|
||||
@@ -219,7 +236,8 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
return next;
|
||||
});
|
||||
}
|
||||
async function doResume(id: string) {
|
||||
async function doResume(id: string, databaseExitApproved = false) {
|
||||
if (!databaseExitApproved && !canLeaveDatabaseManagement()) return;
|
||||
setActiveSurface("core");
|
||||
const guard = captureAuthOperation({ sessionId: id, disposalEpoch: operationEpochRef.current });
|
||||
if (!guard) return;
|
||||
@@ -502,6 +520,7 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
}, [lastSystemEvent]);
|
||||
|
||||
function startNewSession() {
|
||||
if (!canLeaveDatabaseManagement()) return;
|
||||
setActiveSurface("core");
|
||||
invalidateResumeIntent();
|
||||
newSessionOperationRef.current = null;
|
||||
@@ -566,6 +585,7 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
if (!canLeaveDatabaseManagement()) return;
|
||||
await logoutUser();
|
||||
}
|
||||
|
||||
@@ -619,7 +639,13 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
|
||||
{/* Conversation column */}
|
||||
<div data-testid="conversation-column" className="flex min-w-0 flex-1 flex-col">
|
||||
{activeSurface === "database-management" && <DatabaseManagementPage />}
|
||||
{activeSurface === "database-management" && (
|
||||
<DatabaseManagementPage
|
||||
canManage={canManageDatabase}
|
||||
canManageSecrets={canManageWorkspaceSecrets}
|
||||
onNavigationStateChange={updateDatabaseNavigationState}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
aria-hidden={activeSurface !== "core"}
|
||||
className={activeSurface === "core" ? "contents" : "hidden"}
|
||||
@@ -738,16 +764,19 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
>
|
||||
New session
|
||||
</Button>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => setWorkspaceManagerOpen(true)}
|
||||
>
|
||||
Workspace management
|
||||
</Button>
|
||||
onClick={() => {
|
||||
if (!canLeaveDatabaseManagement()) return;
|
||||
setActiveSurface("core");
|
||||
setWorkspaceManagerOpen(true);
|
||||
}}
|
||||
>
|
||||
Workspace management
|
||||
</Button>
|
||||
{canManageDatabase && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -757,14 +786,17 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
>
|
||||
Database management
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{isAdmin && canManagePi && (
|
||||
{canManagePi && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => setPiManagementOpen(true)}
|
||||
onClick={() => {
|
||||
if (!canLeaveDatabaseManagement()) return;
|
||||
setActiveSurface("core");
|
||||
setPiManagementOpen(true);
|
||||
}}
|
||||
>
|
||||
Pi management
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user