first commit
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { Building2 } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export function BusinessUnitsView() {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
testIdPrefix="business-units-governance"
|
||||
icon={<Building2 className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock business units & advanced governance"
|
||||
description="Manage users, business units with our enterprise-grade governance. This feature is part of the Bifrost enterprise license."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/advanced-governance"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import TeamsTable from "@/app/workspace/governance/views/teamsTable";
|
||||
import FullPageLoader from "@/components/fullPageLoader";
|
||||
import { useDebouncedValue } from "@/hooks/useDebounce";
|
||||
import { getErrorMessage, useGetCustomersQuery, useGetTeamsQuery, useGetVirtualKeysQuery } from "@/lib/store";
|
||||
import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
const POLLING_INTERVAL = 5000;
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
export function TeamsView() {
|
||||
const hasVirtualKeysAccess = useRbac(RbacResource.VirtualKeys, RbacOperation.View);
|
||||
const hasCustomersAccess = useRbac(RbacResource.Customers, RbacOperation.View);
|
||||
const hasTeamsAccess = useRbac(RbacResource.Teams, RbacOperation.View);
|
||||
const shownErrorsRef = useRef(new Set<string>());
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
const [offset, setOffset] = useState(0);
|
||||
const debouncedSearch = useDebouncedValue(search, 300);
|
||||
|
||||
useEffect(() => {
|
||||
setOffset(0);
|
||||
}, [debouncedSearch]);
|
||||
|
||||
const {
|
||||
data: virtualKeysData,
|
||||
error: vkError,
|
||||
isLoading: vkLoading,
|
||||
} = useGetVirtualKeysQuery(undefined, {
|
||||
skip: !hasVirtualKeysAccess,
|
||||
pollingInterval: POLLING_INTERVAL,
|
||||
});
|
||||
const {
|
||||
data: customersData,
|
||||
error: customersError,
|
||||
isLoading: customersLoading,
|
||||
} = useGetCustomersQuery(undefined, {
|
||||
skip: !hasCustomersAccess,
|
||||
pollingInterval: POLLING_INTERVAL,
|
||||
});
|
||||
const {
|
||||
data: teamsData,
|
||||
error: teamsError,
|
||||
isLoading: teamsLoading,
|
||||
} = useGetTeamsQuery(
|
||||
{
|
||||
limit: PAGE_SIZE,
|
||||
offset,
|
||||
search: debouncedSearch || undefined,
|
||||
},
|
||||
{
|
||||
skip: !hasTeamsAccess,
|
||||
pollingInterval: POLLING_INTERVAL,
|
||||
},
|
||||
);
|
||||
|
||||
const teamsTotal = teamsData?.total_count ?? 0;
|
||||
|
||||
// Snap offset back when total shrinks past current page (e.g. delete last item on last page)
|
||||
useEffect(() => {
|
||||
if (!teamsData || offset < teamsTotal) return;
|
||||
setOffset(teamsTotal === 0 ? 0 : Math.floor((teamsTotal - 1) / PAGE_SIZE) * PAGE_SIZE);
|
||||
}, [teamsTotal, offset]);
|
||||
|
||||
const isLoading = vkLoading || customersLoading || teamsLoading;
|
||||
|
||||
useEffect(() => {
|
||||
if (!vkError && !customersError && !teamsError) {
|
||||
shownErrorsRef.current.clear();
|
||||
return;
|
||||
}
|
||||
const errorKey = `${!!vkError}-${!!customersError}-${!!teamsError}`;
|
||||
if (shownErrorsRef.current.has(errorKey)) return;
|
||||
shownErrorsRef.current.add(errorKey);
|
||||
if (vkError && customersError && teamsError) {
|
||||
toast.error("Failed to load governance data.");
|
||||
} else {
|
||||
if (vkError) toast.error(`Failed to load virtual keys: ${getErrorMessage(vkError)}`);
|
||||
if (customersError) toast.error(`Failed to load customers: ${getErrorMessage(customersError)}`);
|
||||
if (teamsError) toast.error(`Failed to load teams: ${getErrorMessage(teamsError)}`);
|
||||
}
|
||||
}, [vkError, customersError, teamsError]);
|
||||
|
||||
if (isLoading) {
|
||||
return <FullPageLoader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-7xl">
|
||||
<TeamsTable
|
||||
teams={teamsData?.teams || []}
|
||||
totalCount={teamsData?.total_count || 0}
|
||||
customers={customersData?.customers || []}
|
||||
virtualKeys={virtualKeysData?.virtual_keys || []}
|
||||
search={search}
|
||||
debouncedSearch={debouncedSearch}
|
||||
onSearchChange={setSearch}
|
||||
offset={offset}
|
||||
limit={PAGE_SIZE}
|
||||
onOffsetChange={setOffset}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Users } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function UsersView() {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<Users className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock users & user governance"
|
||||
description="Manage users, set per-user budgets and rate limits, and control access with enterprise-grade governance. This feature is part of the Bifrost enterprise license."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/advanced-governance"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user