Frontend adjustments
This commit is contained in:
@@ -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 */}
|
||||||
|
|||||||
@@ -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 */}
|
||||||
@@ -96,4 +102,4 @@ export default async function TenantDetailPage({
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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 */}
|
||||||
@@ -182,4 +191,4 @@ function StatCard({ label, value, accent }: { label: string; value: string; acce
|
|||||||
<div className={`font-display text-lg font-semibold tabular-nums ${accent ? "text-accent" : "text-text-primary"}`}>{value}</div>
|
<div className={`font-display text-lg font-semibold tabular-nums ${accent ? "text-accent" : "text-text-primary"}`}>{value}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user