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 16:29:28 +00:00
1 parent 590310ca95
commit 32a8b01845
65 files changed
-103

No files matched your search

Regular → Executable
View File
File mode changed.
View File
File mode changed.
Regular → Executable
View File
File mode changed.
File mode changed.
Regular → Executable
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
Regular → Executable
View File
File mode changed.
-103
View File
@@ -1,103 +0,0 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { storeFile, detectContentType } from '@/lib/storage'
import { z } from 'zod'
import { checkRateLimit } from '@/lib/rate-limit'
export const dynamic = 'force-dynamic'
export const maxDuration = 30
// POST /api/aos/belege/upload
// Upload a receipt file with content-addressed storage (PRD §5.5 + §5.7 Belege).
// Accepts multipart/form-data with file + metadata.
export async function POST(req: Request) {
try {
const rateLimited = checkRateLimit(req, 'public')
if (rateLimited) return rateLimited
const formData = await req.formData()
const file = formData.get('file') as File | null
const type = formData.get('type') as string | null
const vendor = formData.get('vendor') as string | null
const invoiceNumber = formData.get('invoiceNumber') as string | null
const amountCentsStr = formData.get('amountCents') as string | null
const bookingDateStr = formData.get('bookingDate') as string | null
if (!file) {
return NextResponse.json({ error: 'file required (multipart/form-data, field name: file)' }, { status: 400 })
}
// Read file content
const arrayBuffer = await file.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
// Detect content type from magic bytes
const detectedType = detectContentType(buffer)
const fileName = file.name
const fileType = file.type || detectedType
const fileSizeBytes = buffer.length
// Store file (content-addressed, dedup, atomic write)
const stored = await storeFile(buffer, fileType)
// Check for duplicate (same hash = same file already registered as Beleg)
const existing = await db.beleg.findUnique({ where: { fileHash: stored.hash } })
if (existing) {
return NextResponse.json({
beleg: existing,
duplicate: true,
message: 'File already registered (same SHA-256 hash)',
}, { status: 409 })
}
// Create Beleg record
const count = await db.beleg.count()
const number = `BEL-2026-${String(count + 1).padStart(4, '0')}`
const belegType = type || 'sonstiges'
// GoBD retention: 8 years for invoices, 6 for business letters
const retentionUntil = new Date()
retentionUntil.setFullYear(retentionUntil.getFullYear() + (belegType.includes('rechnung') ? 8 : 6))
const beleg = await db.beleg.create({
data: {
number,
type: belegType,
fileHash: stored.hash,
fileName,
fileType,
fileSizeBytes,
vendor: vendor ?? null,
invoiceNumber: invoiceNumber ?? null,
amountCents: amountCentsStr ? parseInt(amountCentsStr) : null,
bookingDate: bookingDateStr ? new Date(bookingDateStr) : null,
retentionUntil,
},
})
await db.auditLog.create({
data: {
actor: 'nora@nordlicht.digital',
action: 'beleg.upload',
entity: 'Beleg',
entityId: beleg.id,
after: JSON.stringify({ number, type: belegType, fileName, hash: stored.hash.slice(0, 16) + '…', sizeBytes: fileSizeBytes }),
hash: 'sha256:' + Math.random().toString(36).slice(2),
},
})
return NextResponse.json({
beleg,
storage: {
hash: stored.hash,
sizeBytes: stored.size,
contentType: stored.contentType,
detectedType,
deduped: stored.size !== fileSizeBytes, // if size differs, existing file was returned
},
})
} catch (err) {
const msg = err instanceof Error ? err.message : 'internal server error'
return NextResponse.json({ error: msg }, { status: 500 })
}
}
Regular → Executable
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
Regular → Executable
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
Regular → Executable
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.