All the UI fixes for now

This commit is contained in:
2026-04-11 17:21:52 +02:00
parent 1bd51ecb5d
commit c67259ebe0
15 changed files with 565 additions and 112 deletions

View File

@@ -3,9 +3,11 @@ import { getSessionUser } from "@/lib/session";
import {
createTenantRequest,
getTenantRequestByOrgId,
deleteTenantRequest,
} from "@/lib/db";
import { getTenant, listTenants } from "@/lib/k8s";
import { sendAdminNotificationEmail } from "@/lib/email";
import { encryptSecrets } from "@/lib/crypto";
import type { OnboardingInput } from "@/types";
import { z } from "zod";
@@ -13,6 +15,9 @@ const onboardingSchema = z.object({
agentName: z.string().min(1).max(50),
soulMd: z.string().max(10_000).optional(),
packages: z.array(z.string()).optional(),
packageSecrets: z
.record(z.string(), z.record(z.string(), z.string()))
.optional(),
billingAddress: z.object({
company: z.string().optional(),
street: z.string().optional(),
@@ -54,7 +59,7 @@ export async function GET() {
// Check if there's a pending request
const request = await getTenantRequestByOrgId(user.orgId);
if (!request) {
if (!request || request.status === "deleted") {
return NextResponse.json({ state: "no_request" });
}
@@ -88,7 +93,11 @@ export async function GET() {
* POST /api/onboarding
* Submit the onboarding wizard. Creates a tenant_request with status "pending".
* The actual PiecedTenant CR is NOT created yet — admin approval required.
* Sends a notification email to the admin.
*
* If packageSecrets are provided (for packages requiring credentials like
* Telegram, Discord, Email), they are encrypted with AES-256-GCM and stored
* as a BYTEA blob. They are decrypted only during admin approval to write
* to OpenBao.
*/
export async function POST(request: Request) {
const user = await getSessionUser();
@@ -97,13 +106,18 @@ export async function POST(request: Request) {
// Check for existing request
const existing = await getTenantRequestByOrgId(user.orgId);
if (existing) {
if (existing && existing.status !== "deleted") {
return NextResponse.json(
{ error: "Onboarding request already submitted.", request: existing },
{ status: 409 }
);
}
// If previous request was deleted, remove it so a fresh one can be created
if (existing && existing.status === "deleted") {
await deleteTenantRequest(existing.id);
}
// Check for existing tenant
const allTenants = await listTenants();
const myTenant = allTenants.find(
@@ -125,7 +139,21 @@ export async function POST(request: Request) {
);
}
const input: OnboardingInput = parsed.data;
const input: OnboardingInput & { packageSecrets?: Record<string, Record<string, string>> } = parsed.data;
// Encrypt package secrets if provided
let encryptedSecrets: Buffer | undefined;
if (input.packageSecrets && Object.keys(input.packageSecrets).length > 0) {
try {
encryptedSecrets = await encryptSecrets(input.packageSecrets);
} catch (e: any) {
console.error("Failed to encrypt package secrets:", e);
return NextResponse.json(
{ error: "Failed to secure credentials. Please try again." },
{ status: 500 }
);
}
}
const tenantRequest = await createTenantRequest({
zitadelOrgId: user.orgId,
@@ -138,6 +166,7 @@ export async function POST(request: Request) {
packages: input.packages ?? [],
billingAddress: input.billingAddress,
billingNotes: input.billingNotes,
encryptedSecrets,
});
// Notify admin about the new request