feat: finish Pi and workspace management updates
This commit is contained in:
@@ -66,6 +66,10 @@ export function WorkspaceManager({ open, onClose }: { open: boolean; onClose: ()
|
||||
const [secretValues, setSecretValues] = useState<Record<string, string>>({});
|
||||
const [notice, setNotice] = useState<string>();
|
||||
const [diagnostics, setDiagnostics] = useState<string[]>([]);
|
||||
const [validationNotice, setValidationNotice] = useState<string>();
|
||||
const [validationDiagnostics, setValidationDiagnostics] = useState<string[]>([]);
|
||||
const [connectionNotice, setConnectionNotice] = useState<string>();
|
||||
const [connectionDiagnostics, setConnectionDiagnostics] = useState<string[]>([]);
|
||||
const [busyAction, setBusyAction] = useState<string>();
|
||||
|
||||
const statusQuery = useQuery({
|
||||
@@ -97,6 +101,15 @@ export function WorkspaceManager({ open, onClose }: { open: boolean; onClose: ()
|
||||
const clearMessages = () => {
|
||||
setNotice(undefined);
|
||||
setDiagnostics([]);
|
||||
setValidationNotice(undefined);
|
||||
setValidationDiagnostics([]);
|
||||
setConnectionNotice(undefined);
|
||||
setConnectionDiagnostics([]);
|
||||
};
|
||||
|
||||
const clearGlobalMessages = () => {
|
||||
setNotice(undefined);
|
||||
setDiagnostics([]);
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
@@ -139,12 +152,14 @@ export function WorkspaceManager({ open, onClose }: { open: boolean; onClose: ()
|
||||
async function validateSource() {
|
||||
if (!detailQuery.data) return;
|
||||
setBusyAction("validate");
|
||||
clearMessages();
|
||||
clearGlobalMessages();
|
||||
setValidationNotice(undefined);
|
||||
setValidationDiagnostics([]);
|
||||
try {
|
||||
await validateWorkspace(detailQuery.data.workspace);
|
||||
setNotice("Workspace source is valid.");
|
||||
setValidationNotice("Workspace source is valid.");
|
||||
} catch (error) {
|
||||
setDiagnostics([publicError(error, "workspace_invalid: Workspace validation could not be completed")]);
|
||||
setValidationDiagnostics([publicError(error, "workspace_invalid: Workspace validation could not be completed")]);
|
||||
} finally {
|
||||
setBusyAction(undefined);
|
||||
}
|
||||
@@ -153,17 +168,23 @@ export function WorkspaceManager({ open, onClose }: { open: boolean; onClose: ()
|
||||
async function testConnections() {
|
||||
if (!selectedId) return;
|
||||
setBusyAction("test");
|
||||
clearMessages();
|
||||
clearGlobalMessages();
|
||||
setConnectionNotice(undefined);
|
||||
setConnectionDiagnostics([]);
|
||||
try {
|
||||
const result = await testWorkspace(selectedId);
|
||||
setDiagnostics(result.diagnostics.map(({ code, message }) => `${code}: ${message}`));
|
||||
if (result.diagnostics.length === 0) {
|
||||
setNotice(result.activatable
|
||||
? "Workspace connections are valid."
|
||||
const issues = result.diagnostics.filter(({ level }) => level !== "info");
|
||||
const informational = result.diagnostics.find(({ level }) => level === "info");
|
||||
setConnectionDiagnostics(issues.map(({ code, message }) => `${code}: ${message}`));
|
||||
if (issues.length === 0) {
|
||||
setConnectionNotice(result.activatable
|
||||
? informational
|
||||
? `${informational.code}: ${informational.message}`
|
||||
: "Workspace connections are valid."
|
||||
: "Workspace connection test completed.");
|
||||
}
|
||||
} catch (error) {
|
||||
setDiagnostics([publicError(error, "connector_unavailable: Workspace connections could not be tested")]);
|
||||
setConnectionDiagnostics([publicError(error, "connector_unavailable: Workspace connections could not be tested")]);
|
||||
} finally {
|
||||
setBusyAction(undefined);
|
||||
}
|
||||
@@ -293,7 +314,7 @@ export function WorkspaceManager({ open, onClose }: { open: boolean; onClose: ()
|
||||
<h2 className="font-heading text-xl font-semibold">How workspaces reach ThothII</h2>
|
||||
</div>
|
||||
<ol className="grid list-decimal gap-3 pl-5 text-sm leading-6 text-muted-foreground">
|
||||
<li><span className="font-medium text-foreground">Create a local workspace</span> by choosing any local directory you prefer and setting up the workspace there. It must contain <code>workspace.yaml</code> and every required subdirectory, including any versioned Evidence files.</li>
|
||||
<li><span className="font-medium text-foreground">Create a workspace repository</span> in any local directory you choose. Add one directory for each workspace you want ThothII to manage. At the repository root, <code>thoth-workspaces.yaml</code> lists those workspaces; each workspace directory contains its own <code>workspace.yaml</code>, which declares the database connection, the Evidence sources, and the ThothII vector-database collection used during the process.</li>
|
||||
<li>Publish that source by committing and pushing it to a repository hosted by a Git server such as GitHub, GitLab, or Gitea.</li>
|
||||
<li>The repository address, branch, and read-only Git credentials are configured during ThothII installation. This installation reads <span className="font-medium text-foreground">{repositoryLabel}</span> on branch <span className="font-mono text-foreground">{statusQuery.data?.branch ?? "main"}</span>.</li>
|
||||
<li>ThothII fetches the configured branch into its managed read-only checkout, validates the complete candidate revision, and activates it only when validation succeeds. It never edits, commits, pushes, or publishes workspace source.</li>
|
||||
@@ -337,21 +358,56 @@ export function WorkspaceManager({ open, onClose }: { open: boolean; onClose: ()
|
||||
<dl className="grid gap-3 rounded-lg border border-border bg-muted/20 p-4 text-sm sm:grid-cols-2">
|
||||
<div><dt className="text-xs text-muted-foreground">Source file</dt><dd className="font-mono">{selectedSummary.file}</dd></div>
|
||||
<div><dt className="text-xs text-muted-foreground">Active revision</dt><dd className="truncate font-mono">{detailQuery.data.revision.commit}</dd></div>
|
||||
<div><dt className="text-xs text-muted-foreground">Data warehouse</dt><dd>{detailQuery.data.workspace.dwh.engine} · {detailQuery.data.workspace.dwh.database}/{detailQuery.data.workspace.dwh.schema}</dd></div>
|
||||
<div>
|
||||
<dt className="text-xs text-muted-foreground">Database</dt>
|
||||
<dd className="grid gap-0.5 font-mono text-xs">
|
||||
<span>engine: {detailQuery.data.workspace.dwh.engine}</span>
|
||||
<span>database: {detailQuery.data.workspace.dwh.database}</span>
|
||||
<span>schema: {detailQuery.data.workspace.dwh.schema}</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div><dt className="text-xs text-muted-foreground">Runtime status</dt><dd>{stateLabel(runtime.configurationState)}</dd></div>
|
||||
</dl>
|
||||
|
||||
<div className="grid gap-3 lg:grid-cols-2">
|
||||
<div className="rounded-lg border border-border p-4">
|
||||
<div data-testid="workspace-validation-card" className="rounded-lg border border-border p-4">
|
||||
<h4 className="font-heading font-semibold">Validate workspace source</h4>
|
||||
<p className="mt-1 min-h-12 text-sm leading-5 text-muted-foreground">Checks workspace.yaml and the required workspace directories against the supported workspace schema. No source file is changed.</p>
|
||||
{validationNotice && (
|
||||
<p role="status" className="mt-3 rounded-md border border-emerald-500/30 bg-emerald-500/10 px-3 py-2 text-sm text-emerald-700">
|
||||
<CheckCircle2 className="mr-1 inline size-4 text-emerald-600" />{validationNotice}
|
||||
</p>
|
||||
)}
|
||||
{validationDiagnostics.length > 0 && (
|
||||
<div role="alert" aria-live="polite" className="mt-3 grid gap-1 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm">
|
||||
{validationDiagnostics.map((diagnostic) => (
|
||||
<p key={diagnostic} className="flex items-start gap-2">
|
||||
<AlertCircle className="mt-0.5 size-4 shrink-0 text-amber-700" />{diagnostic}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Button className="mt-3" size="sm" variant="outline" disabled={busyAction === "validate"} onClick={() => { void validateSource(); }}>
|
||||
<ClipboardCheck />Validate workspace source
|
||||
</Button>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border p-4">
|
||||
<div data-testid="workspace-connection-card" className="rounded-lg border border-border p-4">
|
||||
<h4 className="font-heading font-semibold">Test workspace connections</h4>
|
||||
<p className="mt-1 min-h-12 text-sm leading-5 text-muted-foreground">Uses temporary decrypted credentials to verify the configured data warehouse and Evidence source. Temporary files are deleted after the test.</p>
|
||||
{connectionNotice && (
|
||||
<p role="status" className="mt-3 rounded-md border border-emerald-500/30 bg-emerald-500/10 px-3 py-2 text-sm text-emerald-700">
|
||||
<CheckCircle2 className="mr-1 inline size-4 text-emerald-600" />{connectionNotice}
|
||||
</p>
|
||||
)}
|
||||
{connectionDiagnostics.length > 0 && (
|
||||
<div role="alert" aria-live="polite" className="mt-3 grid gap-1 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm">
|
||||
{connectionDiagnostics.map((diagnostic) => (
|
||||
<p key={diagnostic} className="flex items-start gap-2">
|
||||
<AlertCircle className="mt-0.5 size-4 shrink-0 text-amber-700" />{diagnostic}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Button className="mt-3" size="sm" variant="outline" disabled={busyAction === "test"} onClick={() => { void testConnections(); }}>
|
||||
<FlaskConical />Test workspace connections
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user