All the UI fixes for now
This commit is contained in:
@@ -141,7 +141,11 @@ export default async function DashboardPage() {
|
||||
// No tenant → check for existing request, show onboarding flow
|
||||
if (!myTenant) {
|
||||
const existingRequest = await getTenantRequestByOrgId(user.orgId);
|
||||
const initialState = existingRequest?.status ?? "no_request";
|
||||
// Treat "deleted" as no request — customer can re-onboard
|
||||
const initialState =
|
||||
!existingRequest || existingRequest.status === "deleted"
|
||||
? "no_request"
|
||||
: existingRequest.status;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { requirePlatformRole } from "@/lib/session";
|
||||
import { getTenantRequestById, updateTenantRequestStatus } from "@/lib/db";
|
||||
import { getTenantRequestById, updateTenantRequestStatus, clearEncryptedSecrets } from "@/lib/db";
|
||||
import { createTenant } from "@/lib/k8s";
|
||||
import { sendApprovalEmail } from "@/lib/email";
|
||||
import { decryptSecrets } from "@/lib/crypto";
|
||||
import { writePackageSecrets } from "@/lib/openbao";
|
||||
|
||||
/**
|
||||
* POST /api/admin/requests/[id]/approve
|
||||
* Approve a tenant request: create the PiecedTenant CR, update status, notify customer.
|
||||
* Approve a tenant request:
|
||||
* 1. Decrypt stored package secrets (if any)
|
||||
* 2. Write each package's secrets to OpenBao at secret/data/tenants/{tenant-name}/{package}
|
||||
* 3. Null the encrypted_secrets column
|
||||
* 4. Create PiecedTenant CR
|
||||
* 5. Update request status, notify customer.
|
||||
* Also supports re-approving a previously rejected request (clears admin notes).
|
||||
*/
|
||||
export async function POST(
|
||||
@@ -48,7 +55,17 @@ export async function POST(
|
||||
.slice(0, 63) || `tenant-${tenantRequest.id.slice(0, 8)}`;
|
||||
|
||||
try {
|
||||
// Create the PiecedTenant CR
|
||||
// Step 1: Decrypt and write package secrets to OpenBao (if collected during wizard)
|
||||
if (tenantRequest.encryptedSecrets) {
|
||||
const secrets = await decryptSecrets(tenantRequest.encryptedSecrets);
|
||||
for (const [packageId, pkgSecrets] of Object.entries(secrets)) {
|
||||
await writePackageSecrets(`tenant-${tenantName}`, packageId, pkgSecrets);
|
||||
}
|
||||
// Step 2: Null the encrypted column — secrets are now safely in OpenBao
|
||||
await clearEncryptedSecrets(id);
|
||||
}
|
||||
|
||||
// Step 3: Create the PiecedTenant CR
|
||||
await createTenant(
|
||||
tenantName,
|
||||
{
|
||||
@@ -64,14 +81,14 @@ export async function POST(
|
||||
}
|
||||
);
|
||||
|
||||
// Update request status — clear admin notes on re-approval
|
||||
// Step 4: Update request status — clear admin notes on re-approval
|
||||
const updated = await updateTenantRequestStatus(id, "provisioning", {
|
||||
adminNotes: isReApproval ? null : adminNotes,
|
||||
tenantName,
|
||||
clearAdminNotes: isReApproval,
|
||||
});
|
||||
|
||||
// Notify customer
|
||||
// Step 5: Notify customer
|
||||
await sendApprovalEmail(
|
||||
tenantRequest.contactEmail,
|
||||
tenantRequest.contactName,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { requirePlatformRole } from "@/lib/session";
|
||||
import { getTenant, deleteTenant } from "@/lib/k8s";
|
||||
import { markTenantRequestDeletedByTenantName } from "@/lib/db";
|
||||
|
||||
/**
|
||||
* POST /api/admin/tenants/[name]/delete
|
||||
* Delete a PiecedTenant CR. The operator handles cleanup
|
||||
* (namespace, vault, litellm team, etc.).
|
||||
* Also marks the associated tenant_request as "deleted" so the
|
||||
* customer can re-submit the onboarding wizard.
|
||||
*/
|
||||
export async function POST(
|
||||
_request: Request,
|
||||
@@ -26,6 +29,13 @@ export async function POST(
|
||||
|
||||
try {
|
||||
await deleteTenant(name);
|
||||
|
||||
// Mark the associated tenant_request as "deleted" so the customer
|
||||
// sees the wizard again instead of a stale "active" status
|
||||
await markTenantRequestDeletedByTenantName(name).catch((e) =>
|
||||
console.error("Failed to update tenant request after delete:", e)
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
message: "Tenant deletion initiated. The operator will clean up all resources.",
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -60,7 +60,8 @@ export async function POST(
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
await writePackageSecrets(name, packageId, secrets);
|
||||
// Use tenant-{name} to match the operator's vault path convention
|
||||
await writePackageSecrets(`tenant-${name}`, packageId, secrets);
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (e: any) {
|
||||
console.error("Secret write error:", e.message);
|
||||
|
||||
Reference in New Issue
Block a user