fix(dashboard): keep VPS creation progress reliable
Build and publish Docker image / build-and-publish (push) Successful in 35s
Build and publish Docker image / build-and-publish (push) Successful in 35s
This commit is contained in:
1 parent
179a8c274b
commit
008a3a4233
1 file changed
+119
-36
+119
-36
@@ -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>
|
||||
<button
|
||||
onClick={() => setServerCreationProgress(prev => ({ ...prev, visible: false }))}
|
||||
className="p-1 rounded-lg transition-all duration-200 hover:scale-105"
|
||||
style={{
|
||||
backgroundColor: colors.brightBlack,
|
||||
color: colors.foreground
|
||||
}}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
{serverCreationProgress.state !== 'active' && (
|
||||
<button
|
||||
onClick={() => setServerCreationProgress(prev => ({ ...prev, visible: false }))}
|
||||
className="p-1 rounded-lg border"
|
||||
style={{
|
||||
backgroundColor: colors.brightBlack,
|
||||
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="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 rounded-full transition-all duration-300 ease-out"
|
||||
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>
|
||||
|
||||
Reference in new issue
Block a user