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
116 lines
3.9 KiB
TypeScript
Executable File
116 lines
3.9 KiB
TypeScript
Executable File
import { NextResponse } from 'next/server'
|
|
import { db } from '@/lib/db'
|
|
import { z } from 'zod'
|
|
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
const patchSchema = z.object({
|
|
displayName: z.string().optional(),
|
|
description: z.string().optional(),
|
|
isPublic: z.boolean().optional(),
|
|
isActive: z.boolean().optional(),
|
|
envTemplate: z.string().optional(),
|
|
// Assign to a specific company (or remove assignment)
|
|
assignToCompany: z.string().optional(),
|
|
unassignFromCompany: z.string().optional(),
|
|
})
|
|
|
|
// PATCH /api/aos/repos/[id] — update repo settings + assign/unassign
|
|
export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
try {
|
|
const { id } = await params
|
|
const parsed = patchSchema.safeParse(await req.json())
|
|
if (!parsed.success) {
|
|
return NextResponse.json({ error: 'validation failed', details: parsed.error.flatten() }, { status: 400 })
|
|
}
|
|
const { displayName, description, isPublic, isActive, envTemplate, assignToCompany, unassignFromCompany } = parsed.data
|
|
|
|
const repo = await db.allowedRepo.findUnique({ where: { id } })
|
|
if (!repo) {
|
|
return NextResponse.json({ error: 'repo not found' }, { status: 404 })
|
|
}
|
|
|
|
// Update fields
|
|
const updateData: Record<string, unknown> = {}
|
|
if (displayName !== undefined) updateData.displayName = displayName
|
|
if (description !== undefined) updateData.description = description
|
|
if (isPublic !== undefined) updateData.isPublic = isPublic
|
|
if (isActive !== undefined) updateData.isActive = isActive
|
|
if (envTemplate !== undefined) updateData.envTemplate = envTemplate
|
|
|
|
if (Object.keys(updateData).length > 0) {
|
|
await db.allowedRepo.update({ where: { id }, data: updateData })
|
|
}
|
|
|
|
// Assign to company
|
|
if (assignToCompany) {
|
|
await db.repoAssignment.upsert({
|
|
where: { allowedRepoId_companyId: { allowedRepoId: id, companyId: assignToCompany } },
|
|
create: { allowedRepoId: id, companyId: assignToCompany },
|
|
update: {},
|
|
})
|
|
}
|
|
|
|
// Unassign from company
|
|
if (unassignFromCompany) {
|
|
await db.repoAssignment.deleteMany({
|
|
where: { allowedRepoId: id, companyId: unassignFromCompany },
|
|
})
|
|
}
|
|
|
|
await db.auditLog.create({
|
|
data: {
|
|
actor: 'admin:ui',
|
|
action: 'repo.update',
|
|
entity: 'AllowedRepo',
|
|
entityId: id,
|
|
after: JSON.stringify({ updateData, assignToCompany, unassignFromCompany }),
|
|
hash: 'sha256:' + Math.random().toString(36).slice(2),
|
|
},
|
|
})
|
|
|
|
return NextResponse.json({ ok: true })
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : 'internal server error'
|
|
return NextResponse.json({ error: msg }, { status: 500 })
|
|
}
|
|
}
|
|
|
|
// DELETE /api/aos/repos/[id] — remove repo from allowed list
|
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
try {
|
|
const { id } = await params
|
|
const repo = await db.allowedRepo.findUnique({
|
|
where: { id },
|
|
include: { _count: { select: { instances: true } } },
|
|
})
|
|
if (!repo) {
|
|
return NextResponse.json({ error: 'repo not found' }, { status: 404 })
|
|
}
|
|
if (repo._count.instances > 0) {
|
|
return NextResponse.json({
|
|
error: 'cannot delete repo with active instances',
|
|
instances: repo._count.instances,
|
|
}, { status: 409 })
|
|
}
|
|
|
|
await db.allowedRepo.delete({ where: { id } })
|
|
|
|
await db.auditLog.create({
|
|
data: {
|
|
actor: 'admin:ui',
|
|
action: 'repo.delete',
|
|
entity: 'AllowedRepo',
|
|
entityId: id,
|
|
after: JSON.stringify({ giteaOwner: repo.giteaOwner, giteaRepo: repo.giteaRepo }),
|
|
hash: 'sha256:' + Math.random().toString(36).slice(2),
|
|
},
|
|
})
|
|
|
|
return NextResponse.json({ ok: true })
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : 'internal server error'
|
|
return NextResponse.json({ error: msg }, { status: 500 })
|
|
}
|
|
}
|