106 lines
3.4 KiB
TypeScript
106 lines
3.4 KiB
TypeScript
import { getSessionUser } from "@/lib/session";
|
|
import { getTranslations } from "next-intl/server";
|
|
import { redirect, notFound } from "next/navigation";
|
|
import { getTenant } from "@/lib/k8s";
|
|
import { StatusBadge } from "@/components/ui/status-badge";
|
|
import { UsageDisplay } from "@/components/dashboard/usage-display";
|
|
import { PackageList } from "@/components/packages/package-list";
|
|
import { WorkspaceEditor } from "@/components/packages/workspace-editor";
|
|
import { ChannelUsers } from "@/components/channel-users/channel-users";
|
|
|
|
const CHANNEL_PACKAGES = ["telegram", "discord", "email"];
|
|
|
|
export default async function TenantDetailPage({
|
|
params,
|
|
}: {
|
|
params: Promise<{ name: string; locale: string }>;
|
|
}) {
|
|
const user = await getSessionUser();
|
|
if (!user) redirect("/login");
|
|
|
|
const { name } = await params;
|
|
const t = await getTranslations("tenantDetail");
|
|
|
|
const tenant = await getTenant(name);
|
|
if (!tenant) notFound();
|
|
|
|
// Scope check
|
|
if (
|
|
!user.isPlatform &&
|
|
tenant.metadata.labels?.["pieced.ch/zitadel-org-id"] !== user.orgId
|
|
) {
|
|
notFound();
|
|
}
|
|
|
|
const enabledPackages = tenant.spec.packages || [];
|
|
const workspaceFiles = tenant.spec.workspaceFiles || {};
|
|
const enabledChannels = enabledPackages.filter((pkg) =>
|
|
CHANNEL_PACKAGES.includes(pkg)
|
|
);
|
|
const channelUsers = tenant.spec.channelUsers || {};
|
|
|
|
// Admins inspecting another tenant's usage: pass teamId explicitly.
|
|
// Customers viewing their own: no teamId, backend resolves from session.
|
|
const usageTeamId = user.isPlatform
|
|
? tenant.status?.litellmTeamId || undefined
|
|
: undefined;
|
|
|
|
return (
|
|
<div>
|
|
{/* Header */}
|
|
<div className="mb-8 animate-in">
|
|
<div className="flex items-center gap-4">
|
|
<h1 className="font-display text-2xl font-semibold accent-rule">
|
|
{tenant.spec.displayName || name}
|
|
</h1>
|
|
<StatusBadge phase={tenant.status?.phase ?? "Pending"} />
|
|
</div>
|
|
{tenant.spec.agentName && (
|
|
<p className="text-sm text-text-secondary mt-3">
|
|
{t("agent")}: {tenant.spec.agentName}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Usage */}
|
|
<section className="mb-8 animate-in animate-in-delay-1">
|
|
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
|
|
{t("usage")}
|
|
</h2>
|
|
<UsageDisplay teamId={usageTeamId} />
|
|
</section>
|
|
|
|
{/* Packages */}
|
|
<section className="mb-8 animate-in animate-in-delay-2">
|
|
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
|
|
{t("packages")}
|
|
</h2>
|
|
<PackageList
|
|
tenantName={name}
|
|
enabledPackages={enabledPackages}
|
|
conditions={tenant.status?.conditions}
|
|
/>
|
|
</section>
|
|
|
|
{/* Channel Users (authorized users per channel) */}
|
|
{enabledChannels.length > 0 && (
|
|
<section className="mb-8 animate-in animate-in-delay-3">
|
|
<ChannelUsers
|
|
tenantName={name}
|
|
enabledChannels={enabledChannels}
|
|
initialChannelUsers={channelUsers}
|
|
/>
|
|
</section>
|
|
)}
|
|
|
|
{/* Workspace files */}
|
|
<section className="animate-in animate-in-delay-4">
|
|
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
|
|
{t("workspaceFiles")}
|
|
</h2>
|
|
<WorkspaceEditor tenantName={name} files={workspaceFiles} />
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|