From 1bd51ecb5de0a54760ef9c199d1f7f43d39fd33c Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 11 Apr 2026 12:39:34 +0200 Subject: [PATCH] Add possibility for admin to suspend/delete --- .../api/admin/tenants/[name]/delete/route.ts | 39 +++ .../api/admin/tenants/[name]/suspend/route.ts | 42 ++++ src/components/admin/admin-panel.tsx | 222 ++++++++++++++---- src/messages/de.json | 95 ++++---- src/messages/en.json | 95 ++++---- src/messages/fr.json | 95 ++++---- src/messages/it.json | 95 ++++---- src/types/index.ts | 15 +- 8 files changed, 480 insertions(+), 218 deletions(-) create mode 100644 src/app/api/admin/tenants/[name]/delete/route.ts create mode 100644 src/app/api/admin/tenants/[name]/suspend/route.ts diff --git a/src/app/api/admin/tenants/[name]/delete/route.ts b/src/app/api/admin/tenants/[name]/delete/route.ts new file mode 100644 index 0000000..4c94bd9 --- /dev/null +++ b/src/app/api/admin/tenants/[name]/delete/route.ts @@ -0,0 +1,39 @@ +import { NextResponse } from "next/server"; +import { requirePlatformRole } from "@/lib/session"; +import { getTenant, deleteTenant } from "@/lib/k8s"; + +/** + * POST /api/admin/tenants/[name]/delete + * Delete a PiecedTenant CR. The operator handles cleanup + * (namespace, vault, litellm team, etc.). + */ +export async function POST( + _request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + await requirePlatformRole(); + } catch { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + + const { name } = await params; + + const tenant = await getTenant(name); + if (!tenant) { + return NextResponse.json({ error: "Tenant not found" }, { status: 404 }); + } + + try { + await deleteTenant(name); + return NextResponse.json({ + message: "Tenant deletion initiated. The operator will clean up all resources.", + }); + } catch (e: any) { + console.error("Failed to delete tenant:", e); + return NextResponse.json( + { error: `Failed to delete tenant: ${e.message}` }, + { status: 500 } + ); + } +} diff --git a/src/app/api/admin/tenants/[name]/suspend/route.ts b/src/app/api/admin/tenants/[name]/suspend/route.ts new file mode 100644 index 0000000..13cffd2 --- /dev/null +++ b/src/app/api/admin/tenants/[name]/suspend/route.ts @@ -0,0 +1,42 @@ +import { NextResponse } from "next/server"; +import { requirePlatformRole } from "@/lib/session"; +import { getTenant, patchTenantSpec } from "@/lib/k8s"; + +/** + * POST /api/admin/tenants/[name]/suspend + * Toggle suspend on a PiecedTenant CR. + * Body: { suspend: true } or { suspend: false } + */ +export async function POST( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + await requirePlatformRole(); + } catch { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + + const { name } = await params; + const body = await request.json().catch(() => ({})); + const suspend = body.suspend === true; + + const tenant = await getTenant(name); + if (!tenant) { + return NextResponse.json({ error: "Tenant not found" }, { status: 404 }); + } + + try { + const updated = await patchTenantSpec(name, { suspend }); + return NextResponse.json({ + message: suspend ? "Tenant suspended." : "Tenant resumed.", + tenant: updated, + }); + } catch (e: any) { + console.error("Failed to update tenant suspend state:", e); + return NextResponse.json( + { error: `Failed to update tenant: ${e.message}` }, + { status: 500 } + ); + } +} diff --git a/src/components/admin/admin-panel.tsx b/src/components/admin/admin-panel.tsx index 5fb7de2..029588f 100644 --- a/src/components/admin/admin-panel.tsx +++ b/src/components/admin/admin-panel.tsx @@ -16,14 +16,24 @@ interface AdminPanelProps { export function AdminPanel({ initialTenants }: AdminPanelProps) { const t = useTranslations("admin"); const [tab, setTab] = useState("requests"); + + // Requests state const [requests, setRequests] = useState([]); const [filter, setFilter] = useState("all"); - const [loading, setLoading] = useState(true); + const [loadingRequests, setLoadingRequests] = useState(true); const [actionLoading, setActionLoading] = useState(null); const [rejectModal, setRejectModal] = useState(null); const [rejectNotes, setRejectNotes] = useState(""); + + // Tenants state + const [tenants, setTenants] = useState(initialTenants); + const [loadingTenants, setLoadingTenants] = useState(false); + const [deleteModal, setDeleteModal] = useState(null); + + // Shared const [error, setError] = useState(""); + // ─── Requests fetching ─── const fetchRequests = useCallback(async () => { try { const url = @@ -37,17 +47,39 @@ export function AdminPanel({ initialTenants }: AdminPanelProps) { } catch (e: any) { setError(e.message); } finally { - setLoading(false); + setLoadingRequests(false); } }, [filter]); useEffect(() => { if (tab === "requests") { - setLoading(true); + setLoadingRequests(true); fetchRequests(); } }, [tab, filter, fetchRequests]); + // ─── Tenants fetching ─── + const fetchTenants = useCallback(async () => { + setLoadingTenants(true); + try { + const res = await fetch("/api/tenants"); + if (!res.ok) throw new Error("Failed to fetch tenants"); + const data = await res.json(); + setTenants(data); + } catch (e: any) { + setError(e.message); + } finally { + setLoadingTenants(false); + } + }, []); + + useEffect(() => { + if (tab === "tenants") { + fetchTenants(); + } + }, [tab, fetchTenants]); + + // ─── Request actions ─── const handleApprove = async (id: string) => { setActionLoading(id); setError(""); @@ -90,6 +122,48 @@ export function AdminPanel({ initialTenants }: AdminPanelProps) { } }; + // ─── Tenant actions ─── + const handleSuspend = async (name: string, suspend: boolean) => { + setActionLoading(name); + setError(""); + try { + const res = await fetch(`/api/admin/tenants/${name}/suspend`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ suspend }), + }); + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error || "Suspend failed"); + } + await fetchTenants(); + } catch (e: any) { + setError(e.message); + } finally { + setActionLoading(null); + } + }; + + const handleDelete = async (name: string) => { + setActionLoading(name); + setError(""); + try { + const res = await fetch(`/api/admin/tenants/${name}/delete`, { + method: "POST", + }); + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error || "Delete failed"); + } + setDeleteModal(null); + await fetchTenants(); + } catch (e: any) { + setError(e.message); + } finally { + setActionLoading(null); + } + }; + const FILTERS: RequestFilter[] = [ "all", "pending", @@ -132,7 +206,7 @@ export function AdminPanel({ initialTenants }: AdminPanelProps) { > {t("allTenants")} - {initialTenants.length} + {tenants.length} {tab === "tenants" && (
@@ -172,7 +246,7 @@ export function AdminPanel({ initialTenants }: AdminPanelProps) { ))}
- {loading ? ( + {loadingRequests ? (

{t("loadingRequests")}

@@ -266,7 +340,8 @@ export function AdminPanel({ initialTenants }: AdminPanelProps) { )} {(req.status === "provisioning" || - req.status === "approved") && + req.status === "approved" || + req.status === "active") && req.tenantName && ( + {/* Summary cards */}
t.status?.phase === "Running") - .length + tenants.filter( + (t) => t.status?.phase === "Running" || t.status?.phase === "Ready" + ).length } color="text-emerald-400" /> - t.status?.phase === "Provisioning" || - t.status?.phase === "Pending" - ).length - } + label={t("suspended")} + value={tenants.filter((t) => t.spec.suspend).length} color="text-amber-400" /> t.status?.phase === "Error").length + tenants.filter((t) => t.status?.phase === "Error").length } color="text-red-400" />
- {initialTenants.length === 0 ? ( + {loadingTenants ? ( +
+
+

{t("loadingTenants")}

+
+ ) : tenants.length === 0 ? (

{t("noTenants")}

@@ -347,60 +423,94 @@ export function AdminPanel({ initialTenants }: AdminPanelProps) { - - - - - - - {initialTenants.map((tenant) => ( + {tenants.map((tenant) => ( - - - - - - ))} @@ -450,6 +560,38 @@ export function AdminPanel({ initialTenants }: AdminPanelProps) { )} + + {/* ───── DELETE MODAL ───── */} + {deleteModal && ( +
+
+

+ {t("deleteTitle")} +

+

+ {t("deleteWarning")} +

+

+ {deleteModal} +

+
+ + +
+
+
+ )} ); } diff --git a/src/messages/de.json b/src/messages/de.json index 6d8b8f4..d4ca1a3 100644 --- a/src/messages/de.json +++ b/src/messages/de.json @@ -88,49 +88,6 @@ "noInstanceDescription": "Richten Sie Ihre KI-Assistenten-Instanz ein, um mit PieCed IT zu starten.", "manage": "Instanz & Pakete verwalten" }, - "admin": { - "title": "Plattform-Admin", - "allTenants": "Tenants", - "noTenants": "Noch keine Tenants bereitgestellt.", - "noAccess": "Unzureichende Berechtigungen für diese Ansicht.", - "name": "Name", - "displayName": "Anzeigename", - "phase": "Phase", - "packages": "Pakete", - "created": "Erstellt", - "manage": "Verwalten", - "pendingRequests": "Offene Anträge", - "approve": "Genehmigen", - "reject": "Ablehnen", - "company": "Firma", - "contact": "Kontakt", - "status": "Status", - "submitted": "Eingereicht", - "approveConfirm": "Diesen Antrag genehmigen und Bereitstellung starten?", - "rejectConfirm": "Diesen Antrag ablehnen?", - "subtitle": "Onboarding-Anfragen und Mandanten-Lebenszyklus verwalten", - "requests": "Anfragen", - "reApprove": "Erneut genehmigen", - "agentName": "Agent", - "actions": "Aktionen", - "noRequests": "Keine Anfragen gefunden.", - "loadingRequests": "Anfragen werden geladen…", - "rejectTitle": "Anfrage ablehnen", - "adminNotesLabel": "Notizen (optional)", - "adminNotesPlaceholder": "Grund der Ablehnung…", - "cancelAction": "Abbrechen", - "confirmReject": "Ablehnen", - "viewTenant": "Anzeigen", - "filter_all": "Alle", - "filter_pending": "Ausstehend", - "filter_provisioning": "Bereitstellung", - "filter_approved": "Genehmigt", - "filter_rejected": "Abgelehnt", - "totalTenants": "Gesamt", - "running": "Aktiv", - "provisioning": "Bereitstellung", - "errors": "Fehler" - }, "tenantDetail": { "agent": "Agent", "packages": "Pakete", @@ -200,5 +157,57 @@ "documentProcessing": { "description": "Aktivieren Sie Dokumentenverarbeitung, Zusammenfassung und Extraktion." } + }, + "admin": { + "title": "Plattform-Admin", + "subtitle": "Onboarding-Anfragen und Mandanten-Lebenszyklus verwalten", + "allTenants": "Mandanten", + "noTenants": "Noch keine Mandanten bereitgestellt.", + "noAccess": "Unzureichende Berechtigungen für diese Ansicht.", + "name": "Name", + "displayName": "Anzeigename", + "phase": "Phase", + "packages": "Pakete", + "created": "Erstellt", + "manage": "Verwalten", + "requests": "Anfragen", + "pendingRequests": "Offene Anträge", + "approve": "Genehmigen", + "reject": "Ablehnen", + "reApprove": "Erneut genehmigen", + "company": "Firma", + "contact": "Kontakt", + "agentName": "Agent", + "status": "Status", + "submitted": "Eingereicht", + "actions": "Aktionen", + "noRequests": "Keine Anfragen gefunden.", + "loadingRequests": "Anfragen werden geladen…", + "approveConfirm": "Diesen Antrag genehmigen und Bereitstellung starten?", + "rejectConfirm": "Diesen Antrag ablehnen?", + "rejectTitle": "Anfrage ablehnen", + "adminNotesLabel": "Notizen (optional)", + "adminNotesPlaceholder": "Grund der Ablehnung…", + "cancelAction": "Abbrechen", + "confirmReject": "Ablehnen", + "viewTenant": "Anzeigen", + "filter_all": "Alle", + "filter_pending": "Ausstehend", + "filter_provisioning": "Bereitstellung", + "filter_approved": "Genehmigt", + "filter_rejected": "Abgelehnt", + "totalTenants": "Gesamt", + "running": "Aktiv", + "provisioning": "Bereitstellung", + "errors": "Fehler", + "suspend": "Suspendieren", + "resume": "Fortsetzen", + "suspended": "Suspendiert", + "suspendedBadge": "SUSPENDIERT", + "deleteTenant": "Löschen", + "deleteTitle": "Mandant löschen", + "deleteWarning": "Dies löscht den Mandanten, seinen Namespace, Secrets und alle zugehörigen Daten unwiderruflich.", + "confirmDelete": "Endgültig löschen", + "loadingTenants": "Mandanten werden geladen…" } } diff --git a/src/messages/en.json b/src/messages/en.json index 9f981e3..8cbe27d 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -88,49 +88,6 @@ "noInstanceDescription": "Set up your AI assistant instance to get started with PieCed IT.", "manage": "Manage instance & packages" }, - "admin": { - "title": "Platform Admin", - "subtitle": "Manage onboarding requests and tenant lifecycle", - "allTenants": "Tenants", - "noTenants": "No tenants provisioned yet.", - "noAccess": "Insufficient permissions for this view.", - "name": "Name", - "displayName": "Display Name", - "phase": "Phase", - "packages": "Packages", - "created": "Created", - "manage": "Manage", - "requests": "Requests", - "pendingRequests": "Pending Requests", - "approve": "Approve", - "reject": "Reject", - "reApprove": "Re-approve", - "company": "Company", - "contact": "Contact", - "agentName": "Agent", - "status": "Status", - "submitted": "Submitted", - "actions": "Actions", - "noRequests": "No requests found.", - "loadingRequests": "Loading requests…", - "approveConfirm": "Approve this request and start provisioning?", - "rejectConfirm": "Reject this request?", - "rejectTitle": "Reject request", - "adminNotesLabel": "Notes (optional)", - "adminNotesPlaceholder": "Reason for rejection…", - "cancelAction": "Cancel", - "confirmReject": "Reject", - "viewTenant": "View", - "filter_all": "All", - "filter_pending": "Pending", - "filter_provisioning": "Provisioning", - "filter_approved": "Approved", - "filter_rejected": "Rejected", - "totalTenants": "Total", - "running": "Running", - "provisioning": "Provisioning", - "errors": "Errors" - }, "tenantDetail": { "agent": "Agent", "packages": "Packages", @@ -200,5 +157,57 @@ "documentProcessing": { "description": "Enable document parsing, summarization, and extraction." } + }, + "admin": { + "title": "Platform Admin", + "subtitle": "Manage onboarding requests and tenant lifecycle", + "allTenants": "Tenants", + "noTenants": "No tenants provisioned yet.", + "noAccess": "Insufficient permissions for this view.", + "name": "Name", + "displayName": "Display Name", + "phase": "Phase", + "packages": "Packages", + "created": "Created", + "manage": "Manage", + "requests": "Requests", + "pendingRequests": "Pending Requests", + "approve": "Approve", + "reject": "Reject", + "reApprove": "Re-approve", + "company": "Company", + "contact": "Contact", + "agentName": "Agent", + "status": "Status", + "submitted": "Submitted", + "actions": "Actions", + "noRequests": "No requests found.", + "loadingRequests": "Loading requests…", + "approveConfirm": "Approve this request and start provisioning?", + "rejectConfirm": "Reject this request?", + "rejectTitle": "Reject request", + "adminNotesLabel": "Notes (optional)", + "adminNotesPlaceholder": "Reason for rejection…", + "cancelAction": "Cancel", + "confirmReject": "Reject", + "viewTenant": "View", + "filter_all": "All", + "filter_pending": "Pending", + "filter_provisioning": "Provisioning", + "filter_approved": "Approved", + "filter_rejected": "Rejected", + "totalTenants": "Total", + "running": "Running", + "provisioning": "Provisioning", + "errors": "Errors", + "suspend": "Suspend", + "resume": "Resume", + "suspended": "Suspended", + "suspendedBadge": "SUSPENDED", + "deleteTenant": "Delete", + "deleteTitle": "Delete tenant", + "deleteWarning": "This will permanently delete the tenant, its namespace, secrets, and all associated data. This action cannot be undone.", + "confirmDelete": "Delete permanently", + "loadingTenants": "Loading tenants…" } } diff --git a/src/messages/fr.json b/src/messages/fr.json index 4eae5ce..8650e07 100644 --- a/src/messages/fr.json +++ b/src/messages/fr.json @@ -88,49 +88,6 @@ "noInstanceDescription": "Configurez votre instance d'assistant IA pour commencer avec PieCed IT.", "manage": "Gérer l'instance et les paquets" }, - "admin": { - "title": "Admin plateforme", - "allTenants": "Tenants", - "noTenants": "Aucun tenant provisionné.", - "noAccess": "Permissions insuffisantes pour cette vue.", - "name": "Nom", - "displayName": "Nom d'affichage", - "phase": "Phase", - "packages": "Paquets", - "created": "Créé", - "manage": "Gérer", - "pendingRequests": "Demandes en attente", - "approve": "Approuver", - "reject": "Refuser", - "company": "Entreprise", - "contact": "Contact", - "status": "Statut", - "submitted": "Envoyé", - "approveConfirm": "Approuver cette demande et lancer la mise en service ?", - "rejectConfirm": "Refuser cette demande ?", - "subtitle": "Gérer les demandes d'intégration et le cycle de vie des locataires", - "requests": "Demandes", - "reApprove": "Ré-approuver", - "agentName": "Agent", - "actions": "Actions", - "noRequests": "Aucune demande trouvée.", - "loadingRequests": "Chargement des demandes…", - "rejectTitle": "Rejeter la demande", - "adminNotesLabel": "Notes (optionnel)", - "adminNotesPlaceholder": "Raison du rejet…", - "cancelAction": "Annuler", - "confirmReject": "Rejeter", - "viewTenant": "Voir", - "filter_all": "Tous", - "filter_pending": "En attente", - "filter_provisioning": "Provisionnement", - "filter_approved": "Approuvé", - "filter_rejected": "Rejeté", - "totalTenants": "Total", - "running": "Actif", - "provisioning": "Provisionnement", - "errors": "Erreurs" - }, "tenantDetail": { "agent": "Agent", "packages": "Paquets", @@ -200,5 +157,57 @@ "documentProcessing": { "description": "Activez l'analyse, le résumé et l'extraction de documents." } + }, + "admin": { + "title": "Admin plateforme", + "subtitle": "Gérer les demandes d'intégration et le cycle de vie des locataires", + "allTenants": "Locataires", + "noTenants": "Aucun locataire provisionné.", + "noAccess": "Permissions insuffisantes pour cette vue.", + "name": "Nom", + "displayName": "Nom d'affichage", + "phase": "Phase", + "packages": "Paquets", + "created": "Créé", + "manage": "Gérer", + "requests": "Demandes", + "pendingRequests": "Demandes en attente", + "approve": "Approuver", + "reject": "Rejeter", + "reApprove": "Ré-approuver", + "company": "Entreprise", + "contact": "Contact", + "agentName": "Agent", + "status": "Statut", + "submitted": "Soumis", + "actions": "Actions", + "noRequests": "Aucune demande trouvée.", + "loadingRequests": "Chargement des demandes…", + "approveConfirm": "Approuver cette demande et lancer le provisionnement ?", + "rejectConfirm": "Rejeter cette demande ?", + "rejectTitle": "Rejeter la demande", + "adminNotesLabel": "Notes (optionnel)", + "adminNotesPlaceholder": "Raison du rejet…", + "cancelAction": "Annuler", + "confirmReject": "Rejeter", + "viewTenant": "Voir", + "filter_all": "Tous", + "filter_pending": "En attente", + "filter_provisioning": "Provisionnement", + "filter_approved": "Approuvé", + "filter_rejected": "Rejeté", + "totalTenants": "Total", + "running": "Actif", + "provisioning": "Provisionnement", + "errors": "Erreurs", + "suspend": "Suspendre", + "resume": "Reprendre", + "suspended": "Suspendu", + "suspendedBadge": "SUSPENDU", + "deleteTenant": "Supprimer", + "deleteTitle": "Supprimer le locataire", + "deleteWarning": "Cela supprimera définitivement le locataire, son namespace, ses secrets et toutes les données associées. Cette action est irréversible.", + "confirmDelete": "Supprimer définitivement", + "loadingTenants": "Chargement des locataires…" } } diff --git a/src/messages/it.json b/src/messages/it.json index ca7926e..6157009 100644 --- a/src/messages/it.json +++ b/src/messages/it.json @@ -88,49 +88,6 @@ "noInstanceDescription": "Configura la tua istanza di assistente IA per iniziare con PieCed IT.", "manage": "Gestisci istanza e pacchetti" }, - "admin": { - "title": "Admin piattaforma", - "allTenants": "Tenant", - "noTenants": "Nessun tenant ancora attivato.", - "noAccess": "Permessi insufficienti per questa vista.", - "name": "Nome", - "displayName": "Nome visualizzato", - "phase": "Fase", - "packages": "Pacchetti", - "created": "Creato", - "manage": "Gestisci", - "pendingRequests": "Richieste in sospeso", - "approve": "Approva", - "reject": "Rifiuta", - "company": "Azienda", - "contact": "Contatto", - "status": "Stato", - "submitted": "Inviato", - "approveConfirm": "Approvare questa richiesta e avviare l'attivazione?", - "rejectConfirm": "Rifiutare questa richiesta?", - "subtitle": "Gestire le richieste di onboarding e il ciclo di vita dei tenant", - "requests": "Richieste", - "reApprove": "Ri-approva", - "agentName": "Agente", - "actions": "Azioni", - "noRequests": "Nessuna richiesta trovata.", - "loadingRequests": "Caricamento richieste…", - "rejectTitle": "Rifiuta richiesta", - "adminNotesLabel": "Note (opzionale)", - "adminNotesPlaceholder": "Motivo del rifiuto…", - "cancelAction": "Annulla", - "confirmReject": "Rifiuta", - "viewTenant": "Vedi", - "filter_all": "Tutti", - "filter_pending": "In attesa", - "filter_provisioning": "Provisioning", - "filter_approved": "Approvato", - "filter_rejected": "Rifiutato", - "totalTenants": "Totale", - "running": "Attivo", - "provisioning": "Provisioning", - "errors": "Errori" - }, "tenantDetail": { "agent": "Agente", "packages": "Pacchetti", @@ -200,5 +157,57 @@ "documentProcessing": { "description": "Attiva analisi, riepilogo ed estrazione di documenti." } + }, + "admin": { + "title": "Admin piattaforma", + "subtitle": "Gestire le richieste di onboarding e il ciclo di vita dei tenant", + "allTenants": "Tenant", + "noTenants": "Nessun tenant provisionato.", + "noAccess": "Permessi insufficienti per questa vista.", + "name": "Nome", + "displayName": "Nome visualizzato", + "phase": "Fase", + "packages": "Pacchetti", + "created": "Creato", + "manage": "Gestisci", + "requests": "Richieste", + "pendingRequests": "Richieste in attesa", + "approve": "Approva", + "reject": "Rifiuta", + "reApprove": "Ri-approva", + "company": "Azienda", + "contact": "Contatto", + "agentName": "Agente", + "status": "Stato", + "submitted": "Inviato", + "actions": "Azioni", + "noRequests": "Nessuna richiesta trovata.", + "loadingRequests": "Caricamento richieste…", + "approveConfirm": "Approvare questa richiesta e avviare il provisioning?", + "rejectConfirm": "Rifiutare questa richiesta?", + "rejectTitle": "Rifiuta richiesta", + "adminNotesLabel": "Note (opzionale)", + "adminNotesPlaceholder": "Motivo del rifiuto…", + "cancelAction": "Annulla", + "confirmReject": "Rifiuta", + "viewTenant": "Vedi", + "filter_all": "Tutti", + "filter_pending": "In attenta", + "filter_provisioning": "Provisioning", + "filter_approved": "Approvato", + "filter_rejected": "Rifiutato", + "totalTenants": "Totale", + "running": "Attivo", + "provisioning": "Provisioning", + "errors": "Errori", + "suspend": "Sospendi", + "resume": "Riprendi", + "suspended": "Sospeso", + "suspendedBadge": "SOSPESO", + "deleteTenant": "Elimina", + "deleteTitle": "Elimina tenant", + "deleteWarning": "Questo eliminerà permanentemente il tenant, il suo namespace, i secrets e tutti i dati associati. Questa azione non può essere annullata.", + "confirmDelete": "Elimina definitivamente", + "loadingTenants": "Caricamento tenant…" } } diff --git a/src/types/index.ts b/src/types/index.ts index 1fd7c0f..4fe42d4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -29,13 +29,16 @@ export interface PiecedTenantSpec { plan?: string; packages?: string[]; workspaceFiles?: Record; + suspend?: boolean; } export interface PiecedTenantStatus { - phase: "Pending" | "Provisioning" | "Running" | "Error" | "Deleting"; + phase: "Pending" | "Provisioning" | "Running" | "Ready" | "Error" | "Deleting"; message?: string; observedGeneration?: number; litellmTeamId?: string; + tenantNamespace?: string; + enabledPackages?: string[]; conditions?: Array<{ type: string; status: string; @@ -87,11 +90,11 @@ export interface BillingAddress { } export type TenantRequestStatus = - | "pending" // Submitted, awaiting admin approval - | "approved" // Admin approved, provisioning will start - | "provisioning" // PiecedTenant CR created, operator reconciling - | "active" // Tenant running - | "rejected"; // Admin rejected + | "pending" // Submitted, awaiting admin approval + | "approved" // Admin approved, provisioning will start + | "provisioning" // PiecedTenant CR created, operator reconciling + | "active" // Tenant running + | "rejected"; // Admin rejected export interface TenantRequest { id: string;
+ {t("name")} + {t("displayName")} + {t("phase")} + {t("packages")} + {t("created")} + {t("actions")}
+ {tenant.metadata.name} - {tenant.spec.displayName} + + {tenant.spec.displayName} + {tenant.spec.suspend && ( + + {t("suspendedBadge")} + + )} + + {tenant.spec.packages?.join(", ") || "—"} + {tenant.metadata.creationTimestamp ? new Date( tenant.metadata.creationTimestamp ).toLocaleDateString() : "—"} - - {t("manage")} - + +
+ + {t("manage")} + + + +