first commit
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { ShieldCheck } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function AccessProfilesIndexView() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<ShieldCheck className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock access profiles for better performance"
|
||||
description="This feature is a part of the Bifrost enterprise license. Create access profiles to control access to your resources."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/access-profiles"
|
||||
testIdPrefix="access-profiles"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Shuffle } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function AdaptiveRoutingView() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<Shuffle className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock adaptive routing for better performance"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/adaptive-load-balancing"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Siren } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function AlertChannelsView() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<Siren className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock alert channels for better observability"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/alert-channels"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useGetCoreConfigQuery } from "@/lib/store";
|
||||
import { useCopyToClipboard } from "@/hooks/useCopyToClipboard";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { Copy, InfoIcon, KeyRound } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function APIKeysView() {
|
||||
const { data: bifrostConfig, isLoading } = useGetCoreConfigQuery({ fromDB: true });
|
||||
const isAuthConfigure = useMemo(() => {
|
||||
return bifrostConfig?.auth_config?.is_enabled;
|
||||
}, [bifrostConfig]);
|
||||
|
||||
const curlExample = `# Base64 encode your username:password
|
||||
# Example: echo -n "username:password" | base64
|
||||
curl --location 'http://localhost:8080/v1/chat/completions'
|
||||
--header 'Content-Type: application/json'
|
||||
--header 'Accept: application/json'
|
||||
--header 'Authorization: Basic <base64_encoded_username:password>'
|
||||
--data '{
|
||||
"model": "openai/gpt-4",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "explain big bang?"
|
||||
}
|
||||
]
|
||||
}'`;
|
||||
|
||||
const { copy: copyToClipboard } = useCopyToClipboard();
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
if (!isAuthConfigure) {
|
||||
return (
|
||||
<Alert variant="default">
|
||||
<InfoIcon className="text-muted h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<p className="text-md text-muted-foreground">
|
||||
To generate API keys, you need to set up admin username and password first.{" "}
|
||||
<Link to="/workspace/config/security" className="text-md text-primary underline">
|
||||
Configure Security Settings
|
||||
</Link>
|
||||
.<br />
|
||||
<br />
|
||||
Once generated you will need to use this API key for all API calls to the Bifrost admin APIs and UI.
|
||||
</p>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
const isInferenceAuthDisabled = bifrostConfig?.auth_config?.disable_auth_on_inference ?? false;
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-4xl space-y-4">
|
||||
<Alert variant="default">
|
||||
<InfoIcon className="text-muted h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<p className="text-md text-muted-foreground">
|
||||
{isInferenceAuthDisabled ? (
|
||||
<>
|
||||
Authentication is currently <strong>disabled for inference API calls</strong>. You can make inference requests without
|
||||
authentication. Dashboard and admin API calls still require Basic auth with your admin credentials encoded in the standard{" "}
|
||||
<code className="bg-muted rounded px-1 py-0.5 text-sm">username:password</code> format with base64 encoding.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Use Basic auth with your admin credentials when making API calls to Bifrost. Encode your credentials in the standard{" "}
|
||||
<code className="bg-muted rounded px-1 py-0.5 text-sm">username:password</code> format with base64 encoding.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
{!isInferenceAuthDisabled && (
|
||||
<>
|
||||
<br />
|
||||
<p className="text-md text-muted-foreground">
|
||||
<strong>Example:</strong>
|
||||
</p>
|
||||
|
||||
<div className="relative mt-2 w-full min-w-0 overflow-x-auto">
|
||||
<Button variant="ghost" size="sm" onClick={() => copyToClipboard(curlExample)} className="absolute top-2 right-2 z-10 h-8">
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
<pre className="bg-muted min-w-max rounded p-3 pr-12 font-mono text-sm whitespace-pre">{curlExample}</pre>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<ContactUsView
|
||||
className="mt-4 rounded-md border px-3 py-8"
|
||||
icon={<KeyRound size={48} />}
|
||||
title="Scope Based API Keys"
|
||||
description="Need granular access control with scope-based API keys? Enterprise customers can create multiple API keys with specific permissions for different services, teams, or environments."
|
||||
readmeLink="https://docs.getbifrost.io/enterprise/api-keys"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ScrollText } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function AuditLogsView() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<ScrollText className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock audit logs for better compliance"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/audit-logs"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Layers } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function ClusterPage() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<Layers className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock cluster mode to scale reliably"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/clustering"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Database } from "lucide-react";
|
||||
import ContactUsView from "../../views/contactUsView";
|
||||
|
||||
interface EnableToggleProps {
|
||||
enabled: boolean;
|
||||
onToggle: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface BigQueryConnectorViewProps {
|
||||
onDelete?: () => void;
|
||||
isDeleting?: boolean;
|
||||
enableToggle?: EnableToggleProps;
|
||||
}
|
||||
|
||||
export default function BigQueryConnectorView(_props: BigQueryConnectorViewProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Content - OSS: paywall only; no delete/save buttons */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex w-full flex-col items-center justify-center py-8">
|
||||
<ContactUsView
|
||||
align="middle"
|
||||
className="mx-auto w-full max-w-lg"
|
||||
icon={<Database className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock native BigQuery data ingestion for analytics"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/bigquery-connector"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Dog } from "lucide-react";
|
||||
import ContactUsView from "../../views/contactUsView";
|
||||
|
||||
interface EnableToggleProps {
|
||||
enabled: boolean;
|
||||
onToggle: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface DatadogConnectorViewProps {
|
||||
onDelete?: () => void;
|
||||
isDeleting?: boolean;
|
||||
enableToggle?: EnableToggleProps;
|
||||
}
|
||||
|
||||
export default function DatadogConnectorView(_props: DatadogConnectorViewProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Content - OSS: paywall only; no delete/save buttons */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex w-full flex-col items-center justify-center py-8">
|
||||
<ContactUsView
|
||||
align="middle"
|
||||
className="mx-auto w-full max-w-lg"
|
||||
icon={<Dog className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock native Datadog data ingestion for better observability"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/datadog-connector"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Construction } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function GuardrailsConfigurationView() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<Construction className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock guardrails for better security"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/guardrails"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Construction } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function guardrailsProviderView() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<Construction className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock guardrails for better security"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/guardrails"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { LargePayloadConfig } from "@enterprise/lib/types/largePayload";
|
||||
|
||||
export interface LargePayloadSettingsFragmentProps {
|
||||
config: LargePayloadConfig;
|
||||
onConfigChange: (config: LargePayloadConfig) => void;
|
||||
controlsDisabled: boolean;
|
||||
}
|
||||
|
||||
export default function LargePayloadSettingsFragment(_props: LargePayloadSettingsFragmentProps) {
|
||||
return null;
|
||||
}
|
||||
186
ui/app/_fallbacks/enterprise/components/login/loginView.tsx
Normal file
186
ui/app/_fallbacks/enterprise/components/login/loginView.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { getErrorMessage, useIsAuthEnabledQuery, useLoginMutation } from "@/lib/store/apis";
|
||||
import { BooksIcon, DiscordLogoIcon, GithubLogoIcon } from "@phosphor-icons/react";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const externalLinks = [
|
||||
{
|
||||
title: "Discord Server",
|
||||
url: "https://discord.gg/exN5KAydbU",
|
||||
icon: DiscordLogoIcon,
|
||||
},
|
||||
{
|
||||
title: "GitHub Repository",
|
||||
url: "https://github.com/maximhq/bifrost",
|
||||
icon: GithubLogoIcon,
|
||||
},
|
||||
{
|
||||
title: "Full Documentation",
|
||||
url: "https://docs.getbifrost.ai",
|
||||
icon: BooksIcon,
|
||||
strokeWidth: 1,
|
||||
},
|
||||
];
|
||||
|
||||
export default function LoginView() {
|
||||
const { resolvedTheme } = useTheme();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { data: isAuthEnabledData, isLoading: isLoadingIsAuthEnabled, error: isAuthEnabledError } = useIsAuthEnabledQuery();
|
||||
const isAuthEnabled = isAuthEnabledData?.is_auth_enabled || false;
|
||||
const hasValidToken = isAuthEnabledData?.has_valid_token || false;
|
||||
const [login, { isLoading: isLoggingIn }] = useLoginMutation();
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
// Check auth status on component mount
|
||||
useEffect(() => {
|
||||
if (isLoadingIsAuthEnabled) {
|
||||
return;
|
||||
}
|
||||
if (isAuthEnabledError) {
|
||||
setErrorMessage("Unable to verify authentication status. Please retry.");
|
||||
return;
|
||||
}
|
||||
if (!isAuthEnabled || hasValidToken) {
|
||||
navigate({ to: "/workspace" });
|
||||
return;
|
||||
}
|
||||
// Auth is enabled but user is not logged in, show login form
|
||||
setIsCheckingAuth(false);
|
||||
}, [isLoadingIsAuthEnabled]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
setIsLoading(true);
|
||||
e.preventDefault();
|
||||
setErrorMessage("");
|
||||
try {
|
||||
await login({ username, password }).unwrap();
|
||||
// Cookie is set automatically by the server response — just navigate
|
||||
navigate({ to: "/workspace" });
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
setErrorMessage(message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Use light logo for SSR to avoid hydration mismatch
|
||||
const logoSrc = mounted && resolvedTheme === "dark" ? "/bifrost-logo-dark.webp" : "/bifrost-logo.webp";
|
||||
|
||||
// Show loading state while checking auth
|
||||
if (isCheckingAuth || isLoadingIsAuthEnabled) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center p-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="border-border bg-card w-full space-y-6 rounded-sm border p-8">
|
||||
<div className="flex items-center justify-center">
|
||||
<img src={logoSrc} alt="Bifrost" width={160} height={26} className="" />
|
||||
</div>
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="text-muted-foreground text-sm">Checking authentication...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center p-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="border-border bg-card w-full space-y-6 rounded-sm border p-8">
|
||||
{/* Logo */}
|
||||
<div className="flex items-center justify-center">
|
||||
<img src={logoSrc} alt="Bifrost" width={160} height={26} className="" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-center">
|
||||
<h1 className="text-foreground text-lg font-semibold">Welcome back</h1>
|
||||
<p className="text-muted-foreground text-sm">Sign in to your account to continue</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
{errorMessage && <div className="bg-destructive/10 text-destructive rounded-sm p-3 text-sm">{errorMessage}</div>}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="username" className="text-sm font-medium">
|
||||
Username
|
||||
</Label>
|
||||
<Input
|
||||
id="username"
|
||||
type="text"
|
||||
placeholder="Enter your username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
className="text-sm"
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password" className="text-sm font-medium">
|
||||
Password
|
||||
</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
id="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="Enter your password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="pr-10 text-sm"
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="text-muted-foreground hover:text-foreground absolute top-1/2 right-3 -translate-y-1/2 transition-colors"
|
||||
aria-label={showPassword ? "Hide password" : "Show password"}
|
||||
>
|
||||
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="h-9 w-full text-sm" isLoading={isLoading} disabled={isLoading}>
|
||||
{isLoading || isLoggingIn ? "Signing in..." : "Sign in"}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
{/* Social Links */}
|
||||
<div className="flex items-center justify-center gap-4 pt-4">
|
||||
{externalLinks.map((item, index) => (
|
||||
<a
|
||||
key={index}
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-primary transition-colors"
|
||||
title={item.title}
|
||||
>
|
||||
<item.icon className="h-5 w-5" size={20} weight="regular" strokeWidth={item.strokeWidth} />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ShieldUser } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function MCPAuthConfigView() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<ShieldUser className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock MCP Auth Config"
|
||||
description="This feature is a part of the Bifrost enterprise license. Configure authentication for MCP servers to secure your MCP connections."
|
||||
readmeLink="https://docs.getbifrost.ai/mcp/overview"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ToolCase } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function MCPToolGroups() {
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between gap-4 mb-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold tracking-tight">MCP tool groups</h2>
|
||||
<p className="text-muted-foreground text-sm">Configure tool groups for MCP servers to organize and govern tools.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-sm border">
|
||||
<div className="flex w-full flex-col items-center justify-center py-16">
|
||||
<ContactUsView
|
||||
className="mx-auto w-full max-w-lg"
|
||||
icon={<ToolCase className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock MCP Tool Groups"
|
||||
description="This feature is a part of the Bifrost enterprise license. Configure tool groups for MCP servers to organize your MCP tools and govern them across your organization."
|
||||
readmeLink="https://docs.getbifrost.ai/mcp/overview"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ScanEye } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function PiiRedactorProviderView() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<ScanEye className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock PII Redaction for better privacy"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/pii-redactor"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ScanEye } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function PiiRedactorRulesView() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<ScanEye className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock PII Redaction for better privacy"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/pii-redactor"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Router } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function PromptDeploymentView(_props?: { omitTitle?: boolean }) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<ContactUsView
|
||||
align="top"
|
||||
className="justify-start gap-3 rounded-md border p-4"
|
||||
icon={<Router className="h-8 w-8" strokeWidth={1.5} />}
|
||||
title="Unlock prompt deployments for better prompt versioning and A/B testing."
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/prompt-deployments"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { usePromptContext } from "@/components/prompts/context";
|
||||
import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||
import { cn } from "@/lib/utils";
|
||||
import PromptDeploymentView from "./promptDeploymentView";
|
||||
|
||||
export type SettingsSidebarSection = "parameters" | "deployments";
|
||||
|
||||
export function PromptDeploymentsAccordionItem({ activeSection }: { activeSection: SettingsSidebarSection | undefined }) {
|
||||
const { selectedPromptId } = usePromptContext();
|
||||
if (!selectedPromptId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const deploymentsOpen = activeSection === "deployments";
|
||||
|
||||
return (
|
||||
<AccordionItem
|
||||
value="deployments"
|
||||
className={cn(
|
||||
"border-border/60 flex min-h-0 flex-col border-b-0 border-t pt-1",
|
||||
deploymentsOpen ? "min-h-0 grow overflow-hidden" : "shrink-0 grow-0",
|
||||
)}
|
||||
>
|
||||
<AccordionTrigger
|
||||
data-testid="prompt-deployments-trigger"
|
||||
className="text-muted-foreground w-full min-w-0 shrink-0 py-3 pr-1 text-xs font-medium uppercase hover:no-underline [&[data-state=open]>svg]:rotate-180"
|
||||
>
|
||||
<span className="min-w-0 flex-1 text-left font-semibold">Deployments</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent
|
||||
containerClassName="data-[state=open]:flex data-[state=open]:min-h-0 data-[state=open]:flex-1 data-[state=open]:flex-col"
|
||||
className="min-h-0 flex-1 overflow-y-auto pt-0 pb-2"
|
||||
>
|
||||
<PromptDeploymentView omitTitle />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
);
|
||||
}
|
||||
16
ui/app/_fallbacks/enterprise/components/rbac/rbacView.tsx
Normal file
16
ui/app/_fallbacks/enterprise/components/rbac/rbacView.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { UserRoundCheck } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function RBACView() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<UserRoundCheck className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock roles and permissions for better security"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/advanced-governance"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
16
ui/app/_fallbacks/enterprise/components/scim/scimView.tsx
Normal file
16
ui/app/_fallbacks/enterprise/components/scim/scimView.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { BookUser } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function SCIMView() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<BookUser className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock SCIM based access management for user provisioning"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/advanced-governance"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Users } from "lucide-react";
|
||||
import ContactUsView from "../views/contactUsView";
|
||||
|
||||
export default function UserRankingsTab() {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<ContactUsView
|
||||
className="mx-auto min-h-[80vh]"
|
||||
icon={<Users className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
|
||||
title="Unlock user rankings for better visibility"
|
||||
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
|
||||
readmeLink="https://docs.getbifrost.ai/enterprise/user-rankings"
|
||||
testIdPrefix="user-rankings"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
readmeLink: string;
|
||||
align?: "middle" | "top";
|
||||
testIdPrefix?: string;
|
||||
}
|
||||
|
||||
export default function ContactUsView({ icon, title, description, className, readmeLink, align = "middle", testIdPrefix }: Props) {
|
||||
return (
|
||||
<div className={cn("flex flex-col items-center gap-4 text-center", align === "middle" ? "justify-center" : "justify-start", className)}>
|
||||
<div className="text-muted-foreground">{icon}</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-muted-foreground text-xl font-medium">{title}</h1>
|
||||
<div className="text-muted-foreground mt-2 max-w-[600px] text-sm font-normal">{description}</div>
|
||||
<div className="mx-auto flex flex-row items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
aria-label="Read more about this feature (opens in new tab)"
|
||||
className="mx-auto mt-6"
|
||||
data-testid={testIdPrefix ? `${testIdPrefix}-read-more` : undefined}
|
||||
onClick={() => {
|
||||
window.open(`${readmeLink}?utm_source=bfd`, "_blank", "noopener,noreferrer");
|
||||
}}
|
||||
>
|
||||
Read more <ArrowUpRight className="text-muted-foreground h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
className="mx-auto mt-6"
|
||||
aria-label="Book a demo (opens Calendly in new tab)"
|
||||
data-testid={testIdPrefix ? `${testIdPrefix}-book-demo` : undefined}
|
||||
onClick={() => {
|
||||
window.open("https://calendly.com/maximai/bifrost-demo?utm_source=bfd_ent", "_blank", "noopener,noreferrer");
|
||||
}}
|
||||
>
|
||||
Book a demo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
84
ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx
Normal file
84
ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
// RBAC Resource Names (must match backend definitions)
|
||||
export enum RbacResource {
|
||||
GuardrailsConfig = "GuardrailsConfig",
|
||||
GuardrailsProviders = "GuardrailsProviders",
|
||||
GuardrailRules = "GuardrailRules",
|
||||
UserProvisioning = "UserProvisioning",
|
||||
Cluster = "Cluster",
|
||||
Settings = "Settings",
|
||||
Users = "Users",
|
||||
Logs = "Logs",
|
||||
Observability = "Observability",
|
||||
VirtualKeys = "VirtualKeys",
|
||||
ModelProvider = "ModelProvider",
|
||||
Plugins = "Plugins",
|
||||
MCPGateway = "MCPGateway",
|
||||
AdaptiveRouter = "AdaptiveRouter",
|
||||
AuditLogs = "AuditLogs",
|
||||
Customers = "Customers",
|
||||
Teams = "Teams",
|
||||
RBAC = "RBAC",
|
||||
Governance = "Governance",
|
||||
RoutingRules = "RoutingRules",
|
||||
PIIRedactor = "PIIRedactor",
|
||||
PromptRepository = "PromptRepository",
|
||||
PromptDeploymentStrategy = "PromptDeploymentStrategy",
|
||||
AccessProfiles = "AccessProfiles",
|
||||
}
|
||||
|
||||
// RBAC Operation Names (must match backend definitions)
|
||||
export enum RbacOperation {
|
||||
Read = "Read",
|
||||
View = "View",
|
||||
Create = "Create",
|
||||
Update = "Update",
|
||||
Delete = "Delete",
|
||||
Download = "Download",
|
||||
}
|
||||
|
||||
interface RbacContextType {
|
||||
isAllowed: (resource: RbacResource, operation: RbacOperation) => boolean;
|
||||
permissions: Record<string, Record<string, boolean>>;
|
||||
isLoading: boolean;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
const RbacContext = createContext<RbacContextType | null>(null);
|
||||
|
||||
// Dummy provider that allows all permissions
|
||||
export function RbacProvider({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<RbacContext.Provider
|
||||
value={{
|
||||
isAllowed: () => true, // Always allow in OSS
|
||||
permissions: {},
|
||||
isLoading: false,
|
||||
refetch: () => {},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RbacContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// Hook that always returns true (no restrictions in OSS)
|
||||
export function useRbac(_resource: RbacResource, _operation: RbacOperation): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hook to access full RBAC context
|
||||
export function useRbacContext() {
|
||||
const context = useContext(RbacContext);
|
||||
if (!context) {
|
||||
// Return dummy values if used outside provider
|
||||
return {
|
||||
isAllowed: () => true,
|
||||
permissions: {},
|
||||
isLoading: false,
|
||||
refetch: () => {},
|
||||
};
|
||||
}
|
||||
return context;
|
||||
}
|
||||
25
ui/app/_fallbacks/enterprise/lib/index.ts
Normal file
25
ui/app/_fallbacks/enterprise/lib/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// Fallback exports for non-enterprise builds
|
||||
export * from "./store";
|
||||
|
||||
// Re-export OAuth token management utilities for convenience (fallback no-ops)
|
||||
export {
|
||||
REFRESH_TOKEN_ENDPOINT,
|
||||
clearOAuthStorage,
|
||||
clearUserInfo,
|
||||
getAccessToken,
|
||||
getRefreshState,
|
||||
getRefreshToken,
|
||||
getTokenExpiry,
|
||||
getUserInfo,
|
||||
isTokenExpired,
|
||||
setOAuthTokens,
|
||||
setRefreshState,
|
||||
setUserInfo,
|
||||
type UserInfo,
|
||||
} from "./store/utils/tokenManager";
|
||||
|
||||
// Re-export base query (fallback passthrough)
|
||||
export { createBaseQueryWithRefresh } from "./store/utils/baseQueryWithRefresh";
|
||||
|
||||
// Re-export RBAC context (dummy implementation for OSS)
|
||||
export * from "./contexts/rbacContext";
|
||||
@@ -0,0 +1,18 @@
|
||||
import { GetUserAccessProfilesResponse } from "@enterprise/lib/types/accessProfile";
|
||||
|
||||
// OSS build has no access-profile backend — return undefined data so consumers
|
||||
// (e.g. useVirtualKeyUsage) fall back to VK-owned budget/rate-limit values.
|
||||
export const useGetUserAccessProfilesQuery = (
|
||||
_userId: string,
|
||||
_opts?: { skip?: boolean; pollingInterval?: number },
|
||||
): {
|
||||
data: GetUserAccessProfilesResponse | undefined;
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
error: null;
|
||||
} => ({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
error: null,
|
||||
});
|
||||
11
ui/app/_fallbacks/enterprise/lib/store/apis/index.ts
Normal file
11
ui/app/_fallbacks/enterprise/lib/store/apis/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// Placeholder for enterprise APIs
|
||||
// Export empty objects when enterprise features are not available
|
||||
|
||||
export const scimApi = null;
|
||||
export const guardrailsApi = null;
|
||||
export const clusterApi = null;
|
||||
export const rbacApi = null;
|
||||
export const auditLogsApi = null;
|
||||
|
||||
// Empty apis array when enterprise features are not available
|
||||
export const apis = [];
|
||||
@@ -0,0 +1,18 @@
|
||||
import { LargePayloadConfig } from "@enterprise/lib/types/largePayload";
|
||||
|
||||
export const useGetLargePayloadConfigQuery = (): {
|
||||
data: LargePayloadConfig | undefined;
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
error: null;
|
||||
} => ({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
export const useUpdateLargePayloadConfigMutation = (): [
|
||||
(_config: LargePayloadConfig) => { unwrap: () => Promise<void> },
|
||||
{ isLoading: boolean },
|
||||
] => [() => ({ unwrap: async () => {} }), { isLoading: false }];
|
||||
@@ -0,0 +1,22 @@
|
||||
import { User } from "@enterprise/lib/types/user";
|
||||
|
||||
export interface GetVirtualKeyUsersResponse {
|
||||
users: User[];
|
||||
}
|
||||
|
||||
// OSS build has no VK-user-attachment backend — return undefined data so the
|
||||
// consumer treats the VK as unassigned (no AP-managed detection happens).
|
||||
export const useGetVirtualKeyUsersQuery = (
|
||||
_vkId: string,
|
||||
_opts?: { skip?: boolean },
|
||||
): {
|
||||
data: GetVirtualKeyUsersResponse | undefined;
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
error: null;
|
||||
} => ({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
error: null,
|
||||
});
|
||||
23
ui/app/_fallbacks/enterprise/lib/store/index.ts
Normal file
23
ui/app/_fallbacks/enterprise/lib/store/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// Fallback exports for non-enterprise builds
|
||||
export * from "./apis";
|
||||
export * from "./slices";
|
||||
|
||||
// Export OAuth token management utilities (fallback no-ops)
|
||||
export {
|
||||
REFRESH_TOKEN_ENDPOINT,
|
||||
clearOAuthStorage,
|
||||
clearUserInfo,
|
||||
getAccessToken,
|
||||
getRefreshState,
|
||||
getRefreshToken,
|
||||
getTokenExpiry,
|
||||
getUserInfo,
|
||||
isTokenExpired,
|
||||
setOAuthTokens,
|
||||
setRefreshState,
|
||||
setUserInfo,
|
||||
type UserInfo,
|
||||
} from "./utils/tokenManager";
|
||||
|
||||
// Export base query (fallback passthrough)
|
||||
export { createBaseQueryWithRefresh } from "./utils/baseQueryWithRefresh";
|
||||
12
ui/app/_fallbacks/enterprise/lib/store/slices/index.ts
Normal file
12
ui/app/_fallbacks/enterprise/lib/store/slices/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// Placeholder for enterprise reducers
|
||||
// Export noop reducers when enterprise features are not available
|
||||
|
||||
export const scimReducer = (state = {}) => state;
|
||||
export const userReducer = (state = {}) => state;
|
||||
export const guardrailReducer = (state = {}) => state;
|
||||
|
||||
// Empty reducers map when enterprise features are not available
|
||||
export const reducers = {};
|
||||
|
||||
// Empty enterprise state type when enterprise features are not available
|
||||
export type EnterpriseState = {};
|
||||
@@ -0,0 +1,13 @@
|
||||
// Fallback base query for non-enterprise builds
|
||||
// Simply passes through the base query without any refresh logic
|
||||
|
||||
import type { BaseQueryFn } from "@reduxjs/toolkit/query/react";
|
||||
|
||||
/**
|
||||
* Fallback base query wrapper that does nothing
|
||||
* Used when enterprise features are not available
|
||||
*/
|
||||
export function createBaseQueryWithRefresh(baseQuery: BaseQueryFn): BaseQueryFn {
|
||||
// Simply return the base query as-is (no refresh logic)
|
||||
return baseQuery;
|
||||
}
|
||||
77
ui/app/_fallbacks/enterprise/lib/store/utils/tokenManager.ts
Normal file
77
ui/app/_fallbacks/enterprise/lib/store/utils/tokenManager.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
// Fallback OAuth Token Manager for non-enterprise builds
|
||||
// These functions return null/no-op when enterprise features are not available
|
||||
|
||||
export const getAccessToken = async (): Promise<string | null> => Promise.resolve(null);
|
||||
|
||||
export const getRefreshToken = async (): Promise<string | null> => Promise.resolve(null);
|
||||
|
||||
export const getTokenExpiry = (): number | null => null;
|
||||
|
||||
export const isTokenExpired = (): boolean => false;
|
||||
|
||||
export const setOAuthTokens = async (_accessToken: string, _expiresIn?: number | null) => {
|
||||
// No-op in non-enterprise builds
|
||||
};
|
||||
|
||||
export const clearOAuthStorage = () => {
|
||||
// No-op in non-enterprise builds
|
||||
};
|
||||
|
||||
export const getRefreshState = () => ({
|
||||
isRefreshing: false,
|
||||
refreshPromise: null,
|
||||
});
|
||||
|
||||
export const setRefreshState = (_refreshing: boolean, _promise: Promise<any> | null = null) => {
|
||||
// No-op in non-enterprise builds
|
||||
};
|
||||
|
||||
export const REFRESH_TOKEN_ENDPOINT = "";
|
||||
|
||||
// User info type definition (matching enterprise version)
|
||||
export interface UserInfo {
|
||||
name?: string;
|
||||
email?: string;
|
||||
picture?: string;
|
||||
preferred_username?: string;
|
||||
given_name?: string;
|
||||
family_name?: string;
|
||||
}
|
||||
|
||||
// Fallback getUserInfo that returns null for non-enterprise builds
|
||||
export const getUserInfo = (): UserInfo | null => null;
|
||||
|
||||
// Fallback setUserInfo - no-op
|
||||
export const setUserInfo = (_userInfo: UserInfo) => {
|
||||
// No-op in non-enterprise builds
|
||||
};
|
||||
|
||||
// Fallback clearUserInfo - no-op
|
||||
export const clearUserInfo = () => {
|
||||
// No-op in non-enterprise builds
|
||||
};
|
||||
|
||||
// Fallback secure storage functions - no-op
|
||||
export const setSecureItem = async (key: string, value: string): Promise<void> => {
|
||||
// No-op in non-enterprise builds
|
||||
};
|
||||
|
||||
export const getSecureItem = async (key: string): Promise<string | null> => Promise.resolve(null);
|
||||
|
||||
export const removeSecureItem = (key: string): void => {
|
||||
// No-op in non-enterprise builds
|
||||
};
|
||||
|
||||
export const setSecureLocalItem = async (key: string, value: string): Promise<void> => {
|
||||
// No-op in non-enterprise builds
|
||||
};
|
||||
|
||||
export const getSecureLocalItem = async (key: string): Promise<string | null> => Promise.resolve(null);
|
||||
|
||||
export const removeSecureLocalItem = (key: string): void => {
|
||||
// No-op in non-enterprise builds
|
||||
};
|
||||
|
||||
export const clearEncryptionKey = (): void => {
|
||||
// No-op in non-enterprise builds
|
||||
};
|
||||
41
ui/app/_fallbacks/enterprise/lib/types/accessProfile.ts
Normal file
41
ui/app/_fallbacks/enterprise/lib/types/accessProfile.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
export interface AccessProfileBudgetLine {
|
||||
id: string;
|
||||
scope: string;
|
||||
max_limit: number;
|
||||
reset_duration: string;
|
||||
current_usage: number;
|
||||
last_reset: string;
|
||||
alert_thresholds?: number[];
|
||||
}
|
||||
|
||||
export interface AccessProfileRateLimitLine {
|
||||
token_max_limit?: number;
|
||||
token_reset_duration?: string;
|
||||
token_current_usage?: number;
|
||||
token_last_reset?: string;
|
||||
request_max_limit?: number;
|
||||
request_reset_duration?: string;
|
||||
request_current_usage?: number;
|
||||
request_last_reset?: string;
|
||||
}
|
||||
|
||||
export interface UserAccessProfile {
|
||||
id: number;
|
||||
user_id: string;
|
||||
parent_profile_id?: number;
|
||||
virtual_key_ids?: string[];
|
||||
virtual_key_values?: Record<string, string>;
|
||||
name: string;
|
||||
is_active: boolean;
|
||||
expires_at?: string;
|
||||
provider_configs?: unknown[];
|
||||
budgets?: AccessProfileBudgetLine[];
|
||||
rate_limit?: AccessProfileRateLimitLine;
|
||||
mcp_configs?: unknown;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface GetUserAccessProfilesResponse {
|
||||
access_profiles: UserAccessProfile[];
|
||||
}
|
||||
17
ui/app/_fallbacks/enterprise/lib/types/largePayload.ts
Normal file
17
ui/app/_fallbacks/enterprise/lib/types/largePayload.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export interface LargePayloadConfig {
|
||||
enabled: boolean;
|
||||
request_threshold_bytes: number;
|
||||
response_threshold_bytes: number;
|
||||
prefetch_size_bytes: number;
|
||||
max_payload_bytes: number;
|
||||
truncated_log_bytes: number;
|
||||
}
|
||||
|
||||
export const DefaultLargePayloadConfig: LargePayloadConfig = {
|
||||
enabled: false,
|
||||
request_threshold_bytes: 10 * 1024 * 1024, // 10MB
|
||||
response_threshold_bytes: 10 * 1024 * 1024, // 10MB
|
||||
prefetch_size_bytes: 64 * 1024, // 64KB
|
||||
max_payload_bytes: 500 * 1024 * 1024, // 500MB
|
||||
truncated_log_bytes: 1024 * 1024, // 1MB
|
||||
};
|
||||
30
ui/app/_fallbacks/enterprise/lib/types/user.ts
Normal file
30
ui/app/_fallbacks/enterprise/lib/types/user.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { UserAccessProfile } from "@enterprise/lib/types/accessProfile";
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
role_id?: number;
|
||||
role?: {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
is_system_role: boolean;
|
||||
};
|
||||
profile?: Record<string, unknown>;
|
||||
config?: Record<string, unknown>;
|
||||
claims?: Record<string, unknown>;
|
||||
access_profile?: UserAccessProfile;
|
||||
teams?: Array<{ id: string; name: string; business_unit_id?: string; business_unit_name?: string }>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface GetUsersResponse {
|
||||
users: User[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
total_pages: number;
|
||||
has_more: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user