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

+63 -1
View File
@@ -8,7 +8,7 @@ export const dynamic = 'force-dynamic'
export const maxDuration = 300
const patchSchema = z.object({
action: z.enum(['suspend', 'reactivate', 'extend', 'retry']),
action: z.enum(['suspend', 'reactivate', 'extend', 'retry', 'redeploy', 'stop', 'start']),
extendDays: z.number().int().min(1).max(90).optional(),
})
@@ -143,6 +143,68 @@ export async function PATCH(req: Request, { params }: { params: Promise<{ id: st
return NextResponse.json({ ok: true, status: 'requested', message: 'Instance queued for re-provisioning' })
}
if (action === 'redeploy') {
if (!['demo_active', 'active', 'failed'].includes(instance.status)) {
return NextResponse.json({ error: `cannot redeploy instance in status ${instance.status}` }, { status: 400 })
}
if (dokploy && instance.dokployAppId) {
try {
await dokploy.redeployApplication(instance.dokployAppId)
} catch (e) {
await db.instanceEvent.create({
data: { instanceId: id, type: 'redeploy.error', step: 'dokploy', result: 'error', details: JSON.stringify({ error: e instanceof Error ? e.message : 'unknown' }) },
})
}
}
await db.instanceEvent.create({
data: { instanceId: id, type: 'redeploy', step: 'done', result: 'ok' },
})
return NextResponse.json({ ok: true, status: 'redeploying' })
}
if (action === 'stop') {
if (!['demo_active', 'active'].includes(instance.status)) {
return NextResponse.json({ error: `cannot stop instance in status ${instance.status}` }, { status: 400 })
}
if (dokploy && instance.dokployAppId) {
try {
await dokploy.stopApplication(instance.dokployAppId)
} catch (e) {
await db.instanceEvent.create({
data: { instanceId: id, type: 'stop.error', step: 'dokploy', result: 'error', details: JSON.stringify({ error: e instanceof Error ? e.message : 'unknown' }) },
})
}
}
await db.instanceEvent.create({
data: { instanceId: id, type: 'stop', step: 'done', result: 'ok' },
})
return NextResponse.json({ ok: true, status: 'stopped' })
}
if (action === 'start') {
if (!['grace', 'failed'].includes(instance.status)) {
return NextResponse.json({ error: `cannot start instance in status ${instance.status}` }, { status: 400 })
}
if (dokploy && instance.dokployAppId) {
try {
await dokploy.startApplication(instance.dokployAppId)
} catch (e) {
await db.instanceEvent.create({
data: { instanceId: id, type: 'start.error', step: 'dokploy', result: 'error', details: JSON.stringify({ error: e instanceof Error ? e.message : 'unknown' }) },
})
}
}
const newStatus = instance.demoExpiresAt && new Date(instance.demoExpiresAt) > new Date() ? 'demo_active' : 'active'
await db.instance.update({
where: { id },
data: { status: newStatus, graceEndsAt: null },
})
await db.instanceEvent.create({
data: { instanceId: id, type: 'start', step: 'done', result: 'ok', details: JSON.stringify({ status: newStatus }) },
})
return NextResponse.json({ ok: true, status: newStatus })
}
return NextResponse.json({ error: 'unknown action' }, { status: 400 })
} catch (err) {
const msg = err instanceof Error ? err.message : 'internal server error'