fix(dashboard): keep VPS creation progress reliable
Build and publish Docker image / build-and-publish (push) Successful in 35s

This commit is contained in:
wfalkowski committed 2026-10-20 19:03:15 +00:00
1 parent 179a8c274b
commit 008a3a4233
1 file changed
+111 -28
+111 -28
View File
@@ -502,7 +502,9 @@ function DashboardPage({ setIsLoginOpen, setIsRegisterOpen, onCurrencyChange, pr
visible: false,
phase: '',
progress: 0,
message: ''
message: '',
serverType: '',
state: 'active'
});
const [personalInfo, setPersonalInfo] = useState({
firstName: user?.first_name || '',
@@ -973,11 +975,21 @@ const handleWebSocketMessage = useCallback((data) => {
break;
case 'server_created':
// Hide progress notification when server creation completes
setServerCreationProgress(prev => ({ ...prev, visible: false }));
setServerCreationProgress({
visible: true,
phase: 'Provisioned',
progress: 100,
message: data.server?.type === 'vps'
? 'The VPS has been created. It may take a little longer to boot.'
: 'Your server has been created.',
serverType: data.server?.type || '',
state: 'complete'
});
if (data.server && data.server.id) {
setServers(prev => [...prev, data.server]);
setServers(prev => prev.some(server => server.id === data.server.id)
? prev.map(server => server.id === data.server.id ? data.server : server)
: [...prev, data.server]);
setIsCreatingServer(false);
} else if (data.serverId) {
sendMessageRef.current?.({
@@ -985,6 +997,7 @@ const handleWebSocketMessage = useCallback((data) => {
});
setIsCreatingServer(false);
}
setIsCreatingServer(false);
if (data.credits !== undefined) {
setUserCredits(data.credits);
@@ -1124,24 +1137,49 @@ const handleWebSocketMessage = useCallback((data) => {
break;
case 'server_creation_progress':
// Hide progress if no active creation found (after reconnection check)
if (data.phase === 'Unknown' && data.progress === 0 && data.message === 'No active server creation found') {
setServerCreationProgress(prev => ({ ...prev, visible: false }));
if (data.state === 'unknown' || data.phase === 'Unknown') {
setServerCreationProgress(prev => prev.visible && prev.state === 'active'
? {
...prev,
phase: 'Status unavailable',
message: 'The connection lost the latest update. Check your server list for the current status.',
state: 'unknown'
}
: prev);
break;
}
setServerCreationProgress({
setServerCreationProgress(prev => ({
...prev,
visible: true,
phase: data.phase || '',
progress: data.progress || 0,
message: data.message || 'Creating server...'
});
// Hide notification when progress reaches 100%
if (data.progress >= 100) {
setTimeout(() => {
setServerCreationProgress(prev => ({ ...prev, visible: false }));
}, 2000);
}
phase: data.phase || prev.phase,
progress: Math.max(prev.progress || 0, Math.min(100, Math.max(0, Number(data.progress) || 0))),
message: data.message || prev.message || 'Creating server…',
serverType: data.serverType || prev.serverType,
state: data.state || (Number(data.progress) >= 100 ? 'complete' : 'active')
}));
break;
case 'server_creation_failed':
setIsCreatingServer(false);
setServerCreationProgress(prev => ({
...prev,
visible: true,
phase: 'Failed',
message: data.error || 'Server creation failed. Check the error details and try again.',
state: 'failed'
}));
break;
case 'capacity_error':
setIsCreatingServer(false);
setServerCreationProgress(prev => ({
...prev,
visible: true,
phase: 'Capacity check',
message: data.error || 'There is not enough capacity for this server right now.',
state: 'failed'
}));
break;
case 'port_exposed':
@@ -1247,6 +1285,14 @@ const handleWebSocketMessage = useCallback((data) => {
}
}, [dashboardStatus, serverCreationProgress.visible, sendDashboardMessage]);
useEffect(() => {
if (serverCreationProgress.state !== 'complete') return undefined;
const timeoutId = setTimeout(() => {
setServerCreationProgress(prev => ({ ...prev, visible: false }));
}, 4000);
return () => clearTimeout(timeoutId);
}, [serverCreationProgress.state]);
// Request metrics when server is selected and periodically update
useEffect(() => {
if (!selectedServer || !sendDashboardMessage || dashboardStatus !== 'connected') {
@@ -1325,6 +1371,16 @@ const handleWebSocketMessage = useCallback((data) => {
const handleCreateServer = async (serverConfig) => {
setIsCreatingServer(true);
setServerCreationProgress({
visible: true,
phase: 'Preparing',
progress: 1,
message: serverConfig.type === 'vps'
? 'Checking capacity and preparing your VPS.'
: 'Checking capacity and preparing your server.',
serverType: serverConfig.type,
state: 'active'
});
sendDashboardMessage({
type: 'create_server',
name: serverConfig.name,
@@ -4458,7 +4514,7 @@ const handleWebSocketMessage = useCallback((data) => {
borderColor: colors.brightBlack
}}
>
<div className="p-4">
<div className="p-4" role="status" aria-live="polite" aria-atomic="true">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center space-x-3">
<div className="p-2 rounded-lg" style={{ backgroundColor: `${colors.blue}20` }}>
@@ -4466,32 +4522,56 @@ const handleWebSocketMessage = useCallback((data) => {
</div>
<div>
<h4 className="text-sm font-semibold" style={{ color: colors.brightWhite }}>
Server Creation in Progress
{serverCreationProgress.state === 'failed'
? 'Server creation failed'
: serverCreationProgress.state === 'complete'
? 'Server created'
: serverCreationProgress.state === 'unknown'
? 'Creation status unavailable'
: serverCreationProgress.serverType === 'vps'
? 'VPS creation'
: 'Server creation'}
</h4>
<p className="text-xs mt-0.5" style={{ color: colors.foreground }}>
{serverCreationProgress.message}
</p>
</div>
</div>
{serverCreationProgress.state !== 'active' && (
<button
onClick={() => setServerCreationProgress(prev => ({ ...prev, visible: false }))}
className="p-1 rounded-lg transition-all duration-200 hover:scale-105"
className="p-1 rounded-lg border"
style={{
backgroundColor: colors.brightBlack,
color: colors.foreground
borderColor: colors.foreground,
color: colors.brightWhite
}}
aria-label="Dismiss server creation status"
>
<X className="w-4 h-4" />
</button>
)}
</div>
{/* Progress Bar */}
<div className="w-full h-2 rounded-full overflow-hidden" style={{ backgroundColor: colors.brightBlack }}>
<div
className="h-full rounded-full transition-all duration-300 ease-out"
className="w-full h-2 border overflow-hidden"
style={{ backgroundColor: colors.brightBlack, borderColor: colors.foreground }}
role="progressbar"
aria-label="Server creation progress"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={Math.round(serverCreationProgress.progress)}
>
<div
className="h-full transition-[width] duration-500 ease-out"
style={{
width: `${serverCreationProgress.progress}%`,
backgroundColor: colors.blue
width: `${Math.min(100, Math.max(0, serverCreationProgress.progress))}%`,
backgroundColor: serverCreationProgress.state === 'failed'
? colors.red
: serverCreationProgress.state === 'complete'
? colors.green
: colors.blue
}}
/>
</div>
@@ -4501,8 +4581,11 @@ const handleWebSocketMessage = useCallback((data) => {
<span className="text-xs" style={{ color: colors.foreground }}>
{serverCreationProgress.phase}
</span>
<span className="text-xs font-medium" style={{ color: colors.blue }}>
{serverCreationProgress.progress.toFixed(1)}%
<span
className="text-xs font-bold tabular-nums"
style={{ color: serverCreationProgress.state === 'failed' ? colors.red : colors.blue }}
>
{Math.round(serverCreationProgress.progress)}%
</span>
</div>
</div>