feat: add loading indicators for long-running operations
Add loading overlay to sessions list during create, stop, delete, and recreate tunnel operations. Show progress messages like 'Creating instance...' and 'Starting container...' during creation. Dim the sessions grid while operations are in progress to prevent user confusion and accidental duplicate actions.
This commit is contained in:
@@ -38,6 +38,7 @@ export const SessionsPage = () => {
|
||||
const [displayName, setDisplayName] = useState("");
|
||||
const [createStatus, setCreateStatus] = useState<CreateStatus>("idle");
|
||||
const [createError, setCreateError] = useState<string | null>(null);
|
||||
const [createProgress, setCreateProgress] = useState("");
|
||||
|
||||
const [cloneMode, setCloneMode] = useState<"mount" | "clone">("mount");
|
||||
const [branch, setBranch] = useState("main");
|
||||
@@ -60,6 +61,8 @@ export const SessionsPage = () => {
|
||||
}>>({});
|
||||
const [recreatingId, setRecreatingId] = useState<string | null>(null);
|
||||
const [expandedProbeId, setExpandedProbeId] = useState<string | null>(null);
|
||||
const [loadingSessionId, setLoadingSessionId] = useState<string | null>(null);
|
||||
const [loadingAction, setLoadingAction] = useState<string>("");
|
||||
|
||||
const loadSessions = useCallback(async () => {
|
||||
setStatus("loading");
|
||||
@@ -206,6 +209,7 @@ export const SessionsPage = () => {
|
||||
}
|
||||
|
||||
setCreateStatus("creating");
|
||||
setCreateProgress("Creating instance...");
|
||||
try {
|
||||
const instance = await createInstance(
|
||||
selectedProject,
|
||||
@@ -216,11 +220,13 @@ export const SessionsPage = () => {
|
||||
cloneMode === "clone" ? branch : undefined
|
||||
);
|
||||
|
||||
setCreateProgress("Starting container...");
|
||||
// Auto-start the instance
|
||||
await startInstance(selectedProject, selectedRepo, instance.id);
|
||||
|
||||
await updateUserConfig({ last_session_id: instance.id });
|
||||
setCreateStatus("idle");
|
||||
setCreateProgress("");
|
||||
setSelectedProject("");
|
||||
setSelectedRepo("");
|
||||
setSelectedToolType("");
|
||||
@@ -230,6 +236,7 @@ export const SessionsPage = () => {
|
||||
await loadSessions();
|
||||
} catch (error) {
|
||||
setCreateStatus("error");
|
||||
setCreateProgress("");
|
||||
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||
const message = axiosError.response?.data?.detail;
|
||||
setCreateError(
|
||||
@@ -239,16 +246,23 @@ export const SessionsPage = () => {
|
||||
};
|
||||
|
||||
const handleStop = async (sessionId: string, projectId: string, repoId: string) => {
|
||||
setLoadingSessionId(sessionId);
|
||||
setLoadingAction("Stopping...");
|
||||
try {
|
||||
await stopInstance(projectId, repoId, sessionId);
|
||||
setStopConfirmId(null);
|
||||
await loadSessions();
|
||||
} catch {
|
||||
setStopConfirmId(null);
|
||||
} finally {
|
||||
setLoadingSessionId(null);
|
||||
setLoadingAction("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (sessionId: string, projectId: string, repoId: string, force = false) => {
|
||||
setLoadingSessionId(sessionId);
|
||||
setLoadingAction("Deleting...");
|
||||
try {
|
||||
await deleteInstance(projectId, repoId, sessionId, force);
|
||||
setDeleteConfirmId(null);
|
||||
@@ -268,11 +282,15 @@ export const SessionsPage = () => {
|
||||
}
|
||||
}
|
||||
setDeleteConfirmId(null);
|
||||
} finally {
|
||||
setLoadingSessionId(null);
|
||||
setLoadingAction("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleRecreateTunnel = async (session: Session) => {
|
||||
setRecreatingId(session.id);
|
||||
setLoadingSessionId(session.id);
|
||||
setLoadingAction("Recreating tunnel...");
|
||||
try {
|
||||
await recreateInstanceTunnel(
|
||||
session.project_id,
|
||||
@@ -284,7 +302,8 @@ export const SessionsPage = () => {
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setRecreatingId(null);
|
||||
setLoadingSessionId(null);
|
||||
setLoadingAction("");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -379,7 +398,23 @@ export const SessionsPage = () => {
|
||||
{activeSessions.length === 0 ? (
|
||||
<p className="muted">No active sessions</p>
|
||||
) : (
|
||||
<div className="sessions-grid">
|
||||
<div className={`sessions-grid ${createStatus === "creating" || loadingSessionId ? "dimmed" : ""}`}>
|
||||
{createStatus === "creating" && (
|
||||
<div className="loading-overlay">
|
||||
<div className="loading-content">
|
||||
<Icon name="loading" size="lg" />
|
||||
<p>{createProgress || "Creating session..."}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{loadingSessionId && (
|
||||
<div className="loading-overlay">
|
||||
<div className="loading-content">
|
||||
<Icon name="loading" size="lg" />
|
||||
<p>{loadingAction || "Processing..."}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activeSessions.map((session) => (
|
||||
<div className="card session-card" key={session.id}>
|
||||
<div className="session-info">
|
||||
|
||||
Reference in New Issue
Block a user