Frontend adjustments

This commit is contained in:
2026-04-14 20:45:58 +02:00
parent f0eca1959b
commit f550b3400f
5 changed files with 41 additions and 19 deletions

View File

@@ -169,7 +169,6 @@ export default async function DashboardPage() {
} }
const tenantName = myTenant.metadata.name; const tenantName = myTenant.metadata.name;
const teamId = myTenant.status?.litellmTeamId || tenantName;
return ( return (
<div> <div>
@@ -209,12 +208,12 @@ export default async function DashboardPage() {
</Card> </Card>
</div> </div>
{/* Usage */} {/* Usage — no teamId passed, backend resolves from session */}
<div className="mb-6 animate-in animate-in-delay-2"> <div className="mb-6 animate-in animate-in-delay-2">
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3"> <h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
{t("usage")} {t("usage")}
</h2> </h2>
<UsageDisplay teamId={myTenant.status?.litellmTeamId || teamId} /> <UsageDisplay />
</div> </div>
{/* Link to tenant detail */} {/* Link to tenant detail */}

View File

@@ -39,6 +39,12 @@ export default async function TenantDetailPage({
); );
const channelUsers = tenant.spec.channelUsers || {}; 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 ( return (
<div> <div>
{/* Header */} {/* Header */}
@@ -61,7 +67,7 @@ export default async function TenantDetailPage({
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3"> <h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
{t("usage")} {t("usage")}
</h2> </h2>
<UsageDisplay teamId={tenant.status?.litellmTeamId || name} /> <UsageDisplay teamId={usageTeamId} />
</section> </section>
{/* Packages */} {/* Packages */}

View File

@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { getSessionUser } from "@/lib/session"; import { getSessionUser } from "@/lib/session";
import { getPackageDef } from "@/lib/packages";
import { import {
getDefaultSoulMd, getDefaultSoulMd,
getDefaultAgentsMd, getDefaultAgentsMd,
@@ -7,9 +8,12 @@ import {
} from "@/lib/workspace-defaults"; } from "@/lib/workspace-defaults";
/** /**
* GET /api/workspace-defaults?orgName=...&packages=telegram,web-search * GET /api/workspace-defaults?packages=telegram,web-search
* Returns default content for SOUL.md, AGENTS.md, and TOOLS.md. * Returns default content for SOUL.md, AGENTS.md, and TOOLS.md.
* Used by the onboarding wizard to pre-fill textareas. * Used by the onboarding wizard to pre-fill textareas.
*
* orgName is always resolved from the authenticated session — never
* accepted as a query parameter.
*/ */
export async function GET(req: NextRequest) { export async function GET(req: NextRequest) {
const user = await getSessionUser(); const user = await getSessionUser();
@@ -17,10 +21,13 @@ export async function GET(req: NextRequest) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
} }
const orgName = // Always use the session org name — not a client-supplied parameter
req.nextUrl.searchParams.get("orgName") || user.orgName || "Your Company"; const orgName = user.orgName || "Your Company";
const packagesParam = req.nextUrl.searchParams.get("packages") || ""; const packagesParam = req.nextUrl.searchParams.get("packages") || "";
const packages = packagesParam ? packagesParam.split(",").filter(Boolean) : []; const packages = packagesParam
? packagesParam.split(",").filter((id) => id && getPackageDef(id))
: [];
const [soulMd, agentsMd, toolsMd] = await Promise.all([ const [soulMd, agentsMd, toolsMd] = await Promise.all([
getDefaultSoulMd(orgName), getDefaultSoulMd(orgName),

View File

@@ -91,7 +91,13 @@ function UsageChart({ data }: { data: DailyUsage[] }) {
); );
} }
export function UsageDisplay({ teamId }: { teamId: string | null }) { /**
* Usage display widget.
*
* - Customers: don't pass teamId — the backend resolves it from the session.
* - Admins inspecting a specific tenant: pass teamId to override.
*/
export function UsageDisplay({ teamId }: { teamId?: string | null }) {
const t = useTranslations("usage"); const t = useTranslations("usage");
const [month, setMonth] = useState(getCurrentMonth); const [month, setMonth] = useState(getCurrentMonth);
const [data, setData] = useState<UsageData | null>(null); const [data, setData] = useState<UsageData | null>(null);
@@ -101,10 +107,15 @@ export function UsageDisplay({ teamId }: { teamId: string | null }) {
const isCurrentMonth = month === getCurrentMonth(); const isCurrentMonth = month === getCurrentMonth();
const fetchUsage = useCallback(() => { const fetchUsage = useCallback(() => {
if (!teamId) { setLoading(false); return; }
setLoading(true); setLoading(true);
setError(null); setError(null);
fetch(`/api/usage?teamId=${encodeURIComponent(teamId)}&month=${month}`)
const params = new URLSearchParams({ month });
if (teamId) {
params.set("teamId", teamId);
}
fetch(`/api/usage?${params}`)
.then((res) => { if (!res.ok) throw new Error(`${res.status}`); return res.json(); }) .then((res) => { if (!res.ok) throw new Error(`${res.status}`); return res.json(); })
.then(setData) .then(setData)
.catch((e) => setError(e.message)) .catch((e) => setError(e.message))
@@ -113,8 +124,6 @@ export function UsageDisplay({ teamId }: { teamId: string | null }) {
useEffect(() => { fetchUsage(); }, [fetchUsage]); useEffect(() => { fetchUsage(); }, [fetchUsage]);
if (!teamId) return null;
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{/* Month selector */} {/* Month selector */}

View File

@@ -90,7 +90,7 @@ export function OnboardingWizard({ orgName, onComplete }: WizardProps) {
// Fetch DB-stored defaults on mount // Fetch DB-stored defaults on mount
useEffect(() => { useEffect(() => {
fetch(`/api/workspace-defaults?orgName=${encodeURIComponent(orgName)}`) fetch("/api/workspace-defaults")
.then((r) => (r.ok ? r.json() : null)) .then((r) => (r.ok ? r.json() : null))
.then((data) => { .then((data) => {
if (data) { if (data) {
@@ -106,7 +106,8 @@ export function OnboardingWizard({ orgName, onComplete }: WizardProps) {
.catch(() => { .catch(() => {
/* use inline fallbacks */ /* use inline fallbacks */
}); });
}, [orgName]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Re-fetch TOOLS.md preview when packages change // Re-fetch TOOLS.md preview when packages change
const packagesKey = config.packages.sort().join(","); const packagesKey = config.packages.sort().join(",");
@@ -115,14 +116,14 @@ export function OnboardingWizard({ orgName, onComplete }: WizardProps) {
if (prevPackagesKey.current === packagesKey && defaultsLoaded) return; if (prevPackagesKey.current === packagesKey && defaultsLoaded) return;
prevPackagesKey.current = packagesKey; prevPackagesKey.current = packagesKey;
fetch( fetch(
`/api/workspace-defaults?orgName=${encodeURIComponent(orgName)}&packages=${encodeURIComponent(packagesKey)}` `/api/workspace-defaults?packages=${encodeURIComponent(packagesKey)}`
) )
.then((r) => (r.ok ? r.json() : null)) .then((r) => (r.ok ? r.json() : null))
.then((data) => { .then((data) => {
if (data?.toolsMd) setToolsMdPreview(data.toolsMd); if (data?.toolsMd) setToolsMdPreview(data.toolsMd);
}) })
.catch(() => {}); .catch(() => {});
}, [packagesKey, orgName, defaultsLoaded]); }, [packagesKey, defaultsLoaded]);
const stepIndex = STEPS.indexOf(step); const stepIndex = STEPS.indexOf(step);