Merge branch 'dev' of ssh://git.commumedia.org:2222/alex/headquarter into dev

This commit is contained in:
Fusion
2026-05-24 21:21:47 +02:00
2 changed files with 56 additions and 1 deletions
+37 -1
View File
@@ -43,6 +43,9 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
const [profileSelectInstanceId, setProfileSelectInstanceId] = useState<string | null>(null);
const [selectedProfileForAction, setSelectedProfileForAction] = useState("");
// Per-instance busy state for actions
const [busyInstanceId, setBusyInstanceId] = useState<string | null>(null);
const loadInstances = useCallback(async () => {
setLoading(true);
try {
@@ -104,6 +107,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
}, [projectId]);
const handleStart = async (instanceId: string, configProfileId?: string) => {
setBusyInstanceId(instanceId);
try {
await startInstance(projectId, repoId, instanceId, configProfileId);
setProfileSelectInstanceId(null);
@@ -111,20 +115,26 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
await loadInstances();
} catch {
setError("Failed to start instance");
} finally {
setBusyInstanceId(null);
}
};
const handleStop = async (instanceId: string) => {
setBusyInstanceId(instanceId);
try {
await stopInstance(projectId, repoId, instanceId);
setStopConfirmId(null);
await loadInstances();
} catch {
setError("Failed to stop instance");
} finally {
setBusyInstanceId(null);
}
};
const handleRestart = async (instanceId: string, configProfileId?: string) => {
setBusyInstanceId(instanceId);
try {
await restartInstance(projectId, repoId, instanceId, configProfileId);
setProfileSelectInstanceId(null);
@@ -132,26 +142,34 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
await loadInstances();
} catch {
setError("Failed to restart instance");
} finally {
setBusyInstanceId(null);
}
};
const handleDelete = async (instanceId: string) => {
if (!confirm("Are you sure you want to delete this instance?")) return;
setBusyInstanceId(instanceId);
try {
await deleteInstance(projectId, repoId, instanceId);
// Update state immediately instead of reloading
setInstances(prev => prev.filter(i => i.id !== instanceId));
} catch {
setError("Failed to delete instance");
} finally {
setBusyInstanceId(null);
}
};
const handleRecreateTunnel = async (instanceId: string) => {
setBusyInstanceId(instanceId);
try {
await recreateInstanceTunnel(projectId, repoId, instanceId);
await loadInstances();
} catch {
setError("Failed to recreate tunnel");
} finally {
setBusyInstanceId(null);
}
};
@@ -202,7 +220,13 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
) : (
<div className="instance-grid">
{instances.map((instance) => (
<div key={instance.id} className="instance-card">
<div key={instance.id} className={`instance-card ${busyInstanceId === instance.id ? "busy" : ""}`}>
{busyInstanceId === instance.id && (
<div className="instance-busy-overlay">
<Icon name="loading" size="md" />
<span>Working...</span>
</div>
)}
<div className="instance-info">
<div className="instance-name">{instance.display_name}</div>
<div className="instance-meta">
@@ -244,6 +268,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
onClick={() => void handleRecreateTunnel(instance.id)}
type="button"
title="Recreate tunnel"
disabled={busyInstanceId === instance.id}
>
<Icon name="refresh" size="sm" />
Fix Tunnel
@@ -256,6 +281,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
className="secondary-button small"
onClick={() => navigate(`/instances/${instance.id}/terminal`)}
type="button"
disabled={busyInstanceId === instance.id}
>
<Icon name="terminal" size="sm" />
Terminal
@@ -280,6 +306,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
className="primary-button small"
onClick={() => void handleStart(instance.id, selectedProfileForAction || undefined)}
type="button"
disabled={busyInstanceId === instance.id}
>
<Icon name="play" size="sm" />
Start
@@ -291,6 +318,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
setSelectedProfileForAction("");
}}
type="button"
disabled={busyInstanceId === instance.id}
>
Cancel
</button>
@@ -307,6 +335,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
setSelectedProfileForAction(instance.selected_config_profile_id || "");
}}
type="button"
disabled={busyInstanceId === instance.id}
>
<Icon name="play" size="sm" />
Start
@@ -323,6 +352,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
className="ghost-button small danger-text"
onClick={() => void handleStop(instance.id)}
type="button"
disabled={busyInstanceId === instance.id}
>
Yes
</button>
@@ -330,6 +360,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
className="ghost-button small"
onClick={() => setStopConfirmId(null)}
type="button"
disabled={busyInstanceId === instance.id}
>
No
</button>
@@ -339,6 +370,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
className="ghost-button small"
onClick={() => setStopConfirmId(instance.id)}
type="button"
disabled={busyInstanceId === instance.id}
>
<Icon name="stop" size="sm" />
</button>
@@ -360,6 +392,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
className="primary-button small"
onClick={() => void handleRestart(instance.id, selectedProfileForAction || undefined)}
type="button"
disabled={busyInstanceId === instance.id}
>
<Icon name="refresh" size="sm" />
Restart
@@ -371,6 +404,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
setSelectedProfileForAction("");
}}
type="button"
disabled={busyInstanceId === instance.id}
>
Cancel
</button>
@@ -387,6 +421,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
setSelectedProfileForAction(instance.selected_config_profile_id || "");
}}
type="button"
disabled={busyInstanceId === instance.id}
>
<Icon name="refresh" size="sm" />
</button>
@@ -397,6 +432,7 @@ export const InstanceList = ({ projectId, repoId, projectName, repoName, toolTyp
className="ghost-button small danger-text"
onClick={() => void handleDelete(instance.id)}
type="button"
disabled={busyInstanceId === instance.id}
>
<Icon name="delete" size="sm" />
</button>
+19
View File
@@ -2449,6 +2449,7 @@ a.nav-item,
background: var(--bg);
border: 1px solid var(--border);
border-radius: 10px;
position: relative;
}
.instance-info {
@@ -2481,6 +2482,24 @@ a.nav-item,
align-items: center;
}
.instance-card.busy {
opacity: 0.7;
}
.instance-busy-overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
background: rgba(var(--bg-rgb, 255, 255, 255), 0.8);
border-radius: 10px;
z-index: 1;
font-size: var(--text-sm);
color: var(--muted);
}
/* ============================================
Terminal Styles
============================================ */