mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-24 18:24:20 +00:00
Require authentication for all Dashboard pages
This commit is contained in:
parent
f5996b2f6b
commit
7a2357ddb4
6 changed files with 143 additions and 11 deletions
50
ui/litellm-dashboard/package-lock.json
generated
50
ui/litellm-dashboard/package-lock.json
generated
|
@ -17,6 +17,7 @@
|
|||
"@tremor/react": "^3.13.3",
|
||||
"@types/papaparse": "^5.3.15",
|
||||
"antd": "^5.13.2",
|
||||
"cva": "^1.0.0-beta.3",
|
||||
"fs": "^0.0.1-security",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwt-decode": "^4.0.0",
|
||||
|
@ -28,7 +29,8 @@
|
|||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-dom": "^18",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-syntax-highlighter": "^15.6.1"
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"tailwind-merge": "^3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
|
@ -869,6 +871,16 @@
|
|||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tremor/react/node_modules/tailwind-merge": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
|
||||
"integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
|
||||
|
@ -1854,9 +1866,10 @@
|
|||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
@ -1953,6 +1966,26 @@
|
|||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
},
|
||||
"node_modules/cva": {
|
||||
"version": "1.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/cva/-/cva-1.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-CZa8pTkpEygxJRLH9aod/wfnSgK5z/0GJqG/NNehlwam+S8llqCWUXS3eCenvAiW5sTUpwTWE6bJaeeZ/b4pzA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://polar.sh/cva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">= 4.5.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
|
@ -7195,9 +7228,10 @@
|
|||
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
|
||||
"integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.2.0.tgz",
|
||||
"integrity": "sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
|
@ -7452,7 +7486,7 @@
|
|||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"@tremor/react": "^3.13.3",
|
||||
"@types/papaparse": "^5.3.15",
|
||||
"antd": "^5.13.2",
|
||||
"cva": "^1.0.0-beta.3",
|
||||
"fs": "^0.0.1-security",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwt-decode": "^4.0.0",
|
||||
|
@ -29,7 +30,8 @@
|
|||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-dom": "^18",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-syntax-highlighter": "^15.6.1"
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"tailwind-merge": "^3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
|
|
|
@ -26,7 +26,7 @@ import ChatUI from "@/components/chat_ui";
|
|||
import Sidebar from "@/components/leftnav";
|
||||
import Usage from "@/components/usage";
|
||||
import CacheDashboard from "@/components/cache_dashboard";
|
||||
import { setGlobalLitellmHeaderName } from "@/components/networking";
|
||||
import { proxyBaseUrl, setGlobalLitellmHeaderName } from "@/components/networking";
|
||||
import { Organization } from "@/components/networking";
|
||||
import GuardrailsPanel from "@/components/guardrails";
|
||||
import TransformRequestPanel from "@/components/transform_request";
|
||||
|
@ -34,6 +34,8 @@ import { fetchUserModels } from "@/components/create_key_button";
|
|||
import { fetchTeams } from "@/components/common_components/fetch_teams";
|
||||
import MCPToolsViewer from "@/components/mcp_tools";
|
||||
import TagManagement from "@/components/tag_management";
|
||||
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
|
||||
import { cx } from '@/lib/cva.config';
|
||||
|
||||
function getCookie(name: string) {
|
||||
const cookieValue = document.cookie
|
||||
|
@ -79,6 +81,21 @@ interface ProxySettings {
|
|||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
function LoadingScreen() {
|
||||
return (
|
||||
<div className={cx("h-screen", "flex items-center justify-center gap-4")}>
|
||||
<div className="text-lg font-medium py-2 pr-4 border-r border-r-gray-200">
|
||||
🚅 LiteLLM
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<UiLoadingSpinner className="size-4" />
|
||||
<span className="text-gray-600 text-sm">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CreateKeyPage() {
|
||||
const [userRole, setUserRole] = useState("");
|
||||
const [premiumUser, setPremiumUser] = useState(false);
|
||||
|
@ -98,6 +115,7 @@ export default function CreateKeyPage() {
|
|||
const searchParams = useSearchParams()!;
|
||||
const [modelData, setModelData] = useState<any>({ data: [] });
|
||||
const [token, setToken] = useState<string | null>(null);
|
||||
const [authLoading, setAuthLoading] = useState(true);
|
||||
const [userID, setUserID] = useState<string | null>(null);
|
||||
|
||||
const invitation_id = searchParams.get("invitation_id");
|
||||
|
@ -124,8 +142,15 @@ export default function CreateKeyPage() {
|
|||
useEffect(() => {
|
||||
const token = getCookie("token");
|
||||
setToken(token);
|
||||
setAuthLoading(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (authLoading === false && token === null) {
|
||||
window.location.href = (proxyBaseUrl || "") + "/sso/key/generate"
|
||||
}
|
||||
}, [token, authLoading])
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
return;
|
||||
|
@ -196,9 +221,12 @@ export default function CreateKeyPage() {
|
|||
}
|
||||
}, [accessToken, userID, userRole]);
|
||||
|
||||
if (authLoading || (authLoading == false && token === null)) {
|
||||
return <LoadingScreen />
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Suspense fallback={<LoadingScreen />}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{invitation_id ? (
|
||||
<UserDashboard
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import React, { useId } from 'react';
|
||||
import { useSafeLayoutEffect } from '@/hooks/use-safe-layout-effect';
|
||||
import { cx } from '@/lib/cva.config';
|
||||
|
||||
type LoadingSpinnerProps = React.SVGProps<SVGSVGElement>;
|
||||
|
||||
export function UiLoadingSpinner({ className = "", ...props }: LoadingSpinnerProps) {
|
||||
const id = useId();
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
const animations = document
|
||||
.getAnimations()
|
||||
.filter((a) => a instanceof CSSAnimation && a.animationName === 'spin') as CSSAnimation[];
|
||||
|
||||
const self = animations.find(
|
||||
(a) => (a.effect as KeyframeEffect).target?.getAttribute('data-spinner-id') === id,
|
||||
);
|
||||
|
||||
const anyOther = animations.find(
|
||||
(a) =>
|
||||
a.effect instanceof KeyframeEffect &&
|
||||
a.effect.target?.getAttribute('data-spinner-id') !== id,
|
||||
);
|
||||
|
||||
if (self && anyOther) {
|
||||
self.currentTime = anyOther.currentTime;
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<svg
|
||||
data-spinner-id={id}
|
||||
className={cx('pointer-events-none size-12 animate-spin text-current', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
7
ui/litellm-dashboard/src/hooks/use-safe-layout-effect.ts
Normal file
7
ui/litellm-dashboard/src/hooks/use-safe-layout-effect.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { DependencyList, EffectCallback, useEffect, useLayoutEffect } from 'react';
|
||||
|
||||
export function useSafeLayoutEffect(effect: EffectCallback, deps?: DependencyList) {
|
||||
const isSSR = typeof window === 'undefined';
|
||||
const safeUseLayoutEffect = isSSR ? useEffect : useLayoutEffect;
|
||||
return safeUseLayoutEffect(effect, deps);
|
||||
}
|
8
ui/litellm-dashboard/src/lib/cva.config.ts
Normal file
8
ui/litellm-dashboard/src/lib/cva.config.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { defineConfig } from 'cva';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export const { cva, cx, compose } = defineConfig({
|
||||
hooks: {
|
||||
onComplete: (className) => twMerge(className),
|
||||
},
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue