feat: implement tool-session progress panel and live list updates
- Add SessionOperationsContext + SessionProgressPanel for global, non-blocking lifecycle progress (create/start/stop/restart/delete/ recreate-tunnel) driven by SSE events. - Promote SessionsContext to authoritative shared session state with refresh, addOrUpdateSession, and removeSession helpers. - Wire AppShell, DashboardPage, SessionsPage, useInstanceActions, ToolStarter, and InstanceList into shared state so lists update immediately after create/delete without manual refresh. - Remove legacy blocking overlays from CreateSessionForm, SessionCard, and InstanceList; keep disabled states and inline spinners only. - Update DashboardPage tests to wrap with SessionsProvider and SessionOperationsProvider. - Add .cache/ to .gitignore. Quality gates: npm run typecheck, npm run lint, npm test -- --run (82 passed).
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
} from "../../../api/config-profiles";
|
||||
import { listSSHKeys, type SSHKey } from "../../../api/ssh-keys";
|
||||
import { useEventContext } from "../../../state/events";
|
||||
import { useSessions } from "../../../state/sessions";
|
||||
|
||||
const API_BASE_URL =
|
||||
import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
|
||||
@@ -37,6 +38,7 @@ export const InstanceList = ({
|
||||
toolTypes,
|
||||
}: InstanceListProps) => {
|
||||
const navigate = useNavigate();
|
||||
const { refreshSessions } = useSessions();
|
||||
const [instances, setInstances] = useState<ToolInstance[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showCreate, setShowCreate] = useState(false);
|
||||
@@ -108,6 +110,7 @@ export const InstanceList = ({
|
||||
const handleCreateSuccess = async () => {
|
||||
setShowCreate(false);
|
||||
await loadInstances();
|
||||
await refreshSessions();
|
||||
};
|
||||
|
||||
const loadConfigProfiles = useCallback(
|
||||
@@ -196,6 +199,7 @@ export const InstanceList = ({
|
||||
await deleteInstance(projectId, repoId, instanceId);
|
||||
// Update state immediately instead of reloading
|
||||
setInstances((prev) => prev.filter((i) => i.id !== instanceId));
|
||||
await refreshSessions();
|
||||
} catch {
|
||||
setError("Failed to delete instance");
|
||||
} finally {
|
||||
@@ -245,15 +249,7 @@ export const InstanceList = ({
|
||||
) : (
|
||||
<div className="instance-grid">
|
||||
{instances.map((instance) => (
|
||||
<div
|
||||
key={instance.id}
|
||||
className={`instance-card ${busyInstanceId === instance.id ? "busy" : ""}`}
|
||||
>
|
||||
{busyInstanceId === instance.id && (
|
||||
<div className="instance-busy-overlay">
|
||||
<Icon name="loading" size="md" />
|
||||
</div>
|
||||
)}
|
||||
<div key={instance.id} className="instance-card">
|
||||
<div className="instance-info">
|
||||
<div className="instance-name">{instance.display_name}</div>
|
||||
<div className="instance-meta">
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
type ConfigProfile,
|
||||
} from "../../../api/config-profiles";
|
||||
import { listSSHKeys, type SSHKey } from "../../../api/ssh-keys";
|
||||
import { useSessions } from "../../../state/sessions";
|
||||
import { useSessionOperations } from "../../../state/session-operations";
|
||||
import type { Workspace } from "../../../types/workspace";
|
||||
import type { ToolInstance } from "../../../api/sessions";
|
||||
|
||||
@@ -22,6 +24,8 @@ export function ToolStarter({
|
||||
onStarted,
|
||||
onCancel,
|
||||
}: ToolStarterProps) {
|
||||
const { addOrUpdateSession } = useSessions();
|
||||
const { startOperation } = useSessionOperations();
|
||||
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
|
||||
const [toolTypesLoading, setToolTypesLoading] = useState(true);
|
||||
const [toolTypesError, setToolTypesError] = useState<string | null>(null);
|
||||
@@ -141,6 +145,21 @@ export function ToolStarter({
|
||||
selectedProfileId || undefined,
|
||||
selectedSshKeyIds.length > 0 ? selectedSshKeyIds : undefined,
|
||||
);
|
||||
addOrUpdateSession({
|
||||
id: instance.id,
|
||||
display_name: instance.display_name,
|
||||
tool_type_name: instance.tool_type_name,
|
||||
tool_icon: "code",
|
||||
tool_type_interfaces: instance.tool_type_interfaces || [],
|
||||
repository_name: workspace.repo_name,
|
||||
repository_id: workspace.repo_id,
|
||||
project_name: workspace.project_name,
|
||||
project_id: workspace.project_id,
|
||||
workspace_name: workspace.name,
|
||||
status: instance.status || "pending",
|
||||
url: instance.url || null,
|
||||
});
|
||||
startOperation("create", instance.id, instance.display_name);
|
||||
onStarted(instance);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to start tool");
|
||||
@@ -153,6 +172,9 @@ export function ToolStarter({
|
||||
displayName,
|
||||
workspace,
|
||||
onStarted,
|
||||
toolTypes,
|
||||
addOrUpdateSession,
|
||||
startOperation,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user