fix: instance detail crash + add play/stop/redeploy/remove actions

Fixes:
1. useFetch: add credentials:'same-origin' — cookie wasn't being sent
   with API calls, causing 401 → React error boundary → 'Etwas ist schiefgelaufen'
2. Instance detail Sheet now loads correctly (cookie sent with request)

New instance actions (UI + API):
- Redeploy: triggers Dokploy application.redeploy (rebuilds from latest commit)
- Stop: triggers Dokploy application.stop (pauses container)
- Start: triggers Dokploy application.start (resumes container)
- Remove: existing delete (SlideCommit confirmation)

Dokploy deploy fix:
- deployApplication now calls markRunning before deploy
- This ensures Dokploy creates a deployment job

ActionsRow redesigned:
- demo_active/active: [Redeploy] [Stop] [Demo +7d] [Remove]
- grace: [Start] [Remove]
- failed: [Retry] [Remove]
- provisioning/deleting/deleted: no actions
This commit is contained in:
Z User committed 2026-10-29 14:07:51 +00:00
1 parent 89adb63f5e
commit 590310ca95
4 files changed
+114 -44

No files matched your search

+42 -42
View File
@@ -12,6 +12,8 @@ import {
ExternalLink,
Ban,
Play,
Pause,
Square,
RotateCcw,
CalendarPlus,
Trash2,
@@ -20,6 +22,7 @@ import {
XCircle,
CircleDashed,
Network,
RefreshCw,
} from 'lucide-react'
import { useFetch } from '@/hooks/use-fetch'
import { Card } from '@/components/ui/card'
@@ -554,7 +557,7 @@ function InstanceDetailSheet({
}, [open])
const handleAction = async (
action: 'suspend' | 'reactivate' | 'extend' | 'retry',
action: 'suspend' | 'reactivate' | 'extend' | 'retry' | 'redeploy' | 'stop' | 'start',
label: string,
extendDays?: number
) => {
@@ -904,9 +907,12 @@ function InstanceDetailSheet({
status={status}
busyAction={busyAction}
onSuspend={() => handleAction('suspend', 'Suspendieren')}
onReactivate={() => handleAction('reactivate', 'Reaktivieren')}
onReactivate={() => handleAction('reactivate', 'Starten')}
onExtend={() => handleAction('extend', 'Demo verlängern', 7)}
onRetry={() => handleAction('retry', 'Provisioning wiederholen')}
onRedeploy={() => handleAction('redeploy', 'Redeploy')}
onStop={() => handleAction('stop', 'Stop')}
onStart={() => handleAction('start', 'Start')}
onDelete={() => setConfirmingDelete(true)}
/>
{['demo_active', 'active'].includes(status) && (
@@ -1042,6 +1048,9 @@ function ActionsRow({
onReactivate,
onExtend,
onRetry,
onRedeploy,
onStop,
onStart,
onDelete,
}: {
status: string
@@ -1050,6 +1059,9 @@ function ActionsRow({
onReactivate: () => void
onExtend: () => void
onRetry: () => void
onRedeploy: () => void
onStop: () => void
onStart: () => void
onDelete: () => void
}) {
const busy = busyAction !== null
@@ -1079,43 +1091,30 @@ function ActionsRow({
)
}
if (status === 'demo_active') {
if (status === 'demo_active' || status === 'active') {
return (
<div className="grid grid-cols-1 gap-2 sm:grid-cols-3">
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
{btn({
onClick: onRedeploy,
label: 'Redeploy',
icon: RefreshCw,
loading: busyAction === 'redeploy',
})}
{btn({
onClick: onStop,
label: 'Stop',
icon: Square,
loading: busyAction === 'stop',
})}
{status === 'demo_active' && btn({
onClick: onExtend,
label: 'Demo +7 d',
label: 'Demo +7d',
icon: CalendarPlus,
loading: busyAction === 'extend',
})}
{btn({
onClick: onSuspend,
label: 'Suspendieren',
icon: Ban,
loading: busyAction === 'suspend',
})}
{btn({
onClick: onDelete,
label: 'Löschen',
icon: Trash2,
variant: 'destructive',
})}
</div>
)
}
if (status === 'active') {
return (
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
{btn({
onClick: onSuspend,
label: 'Suspendieren',
icon: Ban,
loading: busyAction === 'suspend',
})}
{btn({
onClick: onDelete,
label: 'Löschen',
label: 'Remove',
icon: Trash2,
variant: 'destructive',
})}
@@ -1125,16 +1124,16 @@ function ActionsRow({
if (status === 'grace') {
return (
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
<div className="grid grid-cols-2 gap-2">
{btn({
onClick: onReactivate,
label: 'Reaktivieren',
label: 'Start',
icon: Play,
loading: busyAction === 'reactivate',
})}
{btn({
onClick: onDelete,
label: 'Sofort löschen',
label: 'Remove',
icon: Trash2,
variant: 'destructive',
})}
@@ -1144,23 +1143,24 @@ function ActionsRow({
if (status === 'failed') {
return (
<div className="grid grid-cols-1 gap-2">
<div className="grid grid-cols-2 gap-2">
{btn({
onClick: onRetry,
label: 'Provisioning wiederholen',
label: 'Retry',
icon: RotateCcw,
loading: busyAction === 'retry',
})}
{btn({
onClick: onDelete,
label: 'Remove',
icon: Trash2,
variant: 'destructive',
})}
</div>
)
}
// provisioning / deleting / deleted / requested → no actions
return (
<p className="text-xs text-muted-foreground">
Für diesen Status sind keine Aktionen verfügbar.
</p>
)
return null
}
// ---------------------------------------------------------------------------