build(ui): add stubbed teams page

This commit is contained in:
Krrish Dholakia 2024-02-24 16:14:21 -08:00
parent 785a4a2ac2
commit ad9169a4da
6 changed files with 458 additions and 13 deletions

View file

@ -5,14 +5,16 @@ import Navbar from "../components/navbar";
import UserDashboard from "../components/user_dashboard";
import ModelDashboard from "@/components/model_dashboard";
import ViewUserDashboard from "@/components/view_users";
import Teams from "@/components/teams";
import ChatUI from "@/components/chat_ui";
import Sidebar from "../components/leftnav";
import Usage from "../components/usage";
import { jwtDecode } from "jwt-decode";
const CreateKeyPage = () => {
const [userRole, setUserRole] = useState('');
const [userRole, setUserRole] = useState("");
const [userEmail, setUserEmail] = useState<null | string>(null);
const [teams, setTeams] = useState<null | string[]>(null);
const searchParams = useSearchParams();
const userID = searchParams.get("userID");
@ -74,14 +76,20 @@ const CreateKeyPage = () => {
<div className="flex flex-col min-h-screen">
<Navbar userID={userID} userRole={userRole} userEmail={userEmail} />
<div className="flex flex-1 overflow-auto">
<Sidebar setPage={setPage} userRole={userRole}/>
<Sidebar
setPage={setPage}
userRole={userRole}
defaultSelectedKey={null}
/>
{page == "api-keys" ? (
<UserDashboard
userID={userID}
userRole={userRole}
teams={teams}
setUserRole={setUserRole}
userEmail={userEmail}
setUserEmail={setUserEmail}
setTeams={setTeams}
/>
) : page == "models" ? (
<ModelDashboard
@ -97,16 +105,16 @@ const CreateKeyPage = () => {
token={token}
accessToken={accessToken}
/>
)
: page == "users" ? (
) : page == "users" ? (
<ViewUserDashboard
userID={userID}
userRole={userRole}
token={token}
accessToken={accessToken}
/>
)
: (
) : page == "teams" ? (
<Teams teams={teams} searchParams={searchParams} />
) : (
<Usage
userID={userID}
userRole={userRole}

View file

@ -0,0 +1,149 @@
"use client";
import React, { Suspense, useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import Navbar from "../../components/navbar";
import Sidebar from "../../components/leftnav";
import { jwtDecode } from "jwt-decode";
import Link from "next/link";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
Card,
Icon,
Button,
Col,
Grid,
} from "@tremor/react";
import { CogIcon } from "@heroicons/react/outline";
const TeamSettingsPage = () => {
const [userRole, setUserRole] = useState("");
const [userEmail, setUserEmail] = useState<null | string>(null);
const [teams, setTeams] = useState<null | string[]>(null);
const searchParams = useSearchParams();
const userID = searchParams.get("userID");
const token = searchParams.get("token");
const [page, setPage] = useState("team");
const [accessToken, setAccessToken] = useState<string | null>(null);
useEffect(() => {
if (token) {
const decoded = jwtDecode(token) as { [key: string]: any };
if (decoded) {
// cast decoded to dictionary
console.log("Decoded token:", decoded);
console.log("Decoded key:", decoded.key);
// set accessToken
setAccessToken(decoded.key);
// check if userRole is defined
if (decoded.user_role) {
const formattedUserRole = formatUserRole(decoded.user_role);
console.log("Decoded user_role:", formattedUserRole);
setUserRole(formattedUserRole);
} else {
console.log("User role not defined");
}
if (decoded.user_email) {
setUserEmail(decoded.user_email);
} else {
console.log(`User Email is not set ${decoded}`);
}
}
}
}, [token]);
function formatUserRole(userRole: string) {
if (!userRole) {
return "Undefined Role";
}
console.log(`Received user role: ${userRole}`);
switch (userRole.toLowerCase()) {
case "app_owner":
return "App Owner";
case "demo_app_owner":
return "App Owner";
case "app_admin":
return "Admin";
case "app_user":
return "App User";
default:
return "Unknown Role";
}
}
return (
<Suspense fallback={<div>Loading...</div>}>
<div className="flex flex-col min-h-screen">
<Navbar userID={userID} userRole={userRole} userEmail={userEmail} />
<div className="flex flex-1 overflow-auto">
<Grid numItems={1} className="gap-0 p-10 h-[75vh] w-full">
<Col numColSpan={1}>
<Card className="w-full mx-auto flex-auto overflow-y-auto max-h-[50vh]">
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Team Name</TableHeaderCell>
<TableHeaderCell>Spend (USD)</TableHeaderCell>
<TableHeaderCell>Budget (USD)</TableHeaderCell>
<TableHeaderCell>TPM / RPM Limits</TableHeaderCell>
<TableHeaderCell>Settings</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Wilhelm Tell</TableCell>
<TableCell className="text-right">1</TableCell>
<TableCell>Uri, Schwyz, Unterwalden</TableCell>
<TableCell>National Hero</TableCell>
<TableCell>
<Icon icon={CogIcon} size="sm" />
</TableCell>
</TableRow>
<TableRow>
<TableCell>The Witcher</TableCell>
<TableCell className="text-right">129</TableCell>
<TableCell>Kaedwen</TableCell>
<TableCell>Legend</TableCell>
<TableCell>
<Icon icon={CogIcon} size="sm" />
</TableCell>
</TableRow>
<TableRow>
<TableCell>Mizutsune</TableCell>
<TableCell className="text-right">82</TableCell>
<TableCell>Japan</TableCell>
<TableCell>N/A</TableCell>
<TableCell>
<Icon icon={CogIcon} size="sm" />
</TableCell>
</TableRow>
</TableBody>
</Table>
</Card>
</Col>
<Col numColSpan={1}>
<Link
href={`/team?userID=${searchParams.get(
"userID"
)}&token=${searchParams.get("token")}`}
>
<Button className="mx-auto">+ Create New Team</Button>
</Link>
</Col>
</Grid>
</div>
</div>
</Suspense>
);
};
export default TeamSettingsPage;

View file

@ -1,5 +1,6 @@
import { Layout, Menu } from "antd";
import Link from "next/link";
import { List } from "postcss/lib/list";
const { Sider } = Layout;
@ -7,15 +8,20 @@ const { Sider } = Layout;
interface SidebarProps {
setPage: React.Dispatch<React.SetStateAction<string>>;
userRole: string;
defaultSelectedKey: string[] | null;
}
const Sidebar: React.FC<SidebarProps> = ({ setPage, userRole }) => {
const Sidebar: React.FC<SidebarProps> = ({
setPage,
userRole,
defaultSelectedKey,
}) => {
return (
<Layout style={{ minHeight: "100vh", maxWidth: "120px" }}>
<Sider width={120}>
<Menu
mode="inline"
defaultSelectedKeys={["1"]}
defaultSelectedKeys={defaultSelectedKey ? defaultSelectedKey : ["1"]}
style={{ height: "100%", borderRight: 0 }}
>
<Menu.Item key="1" onClick={() => setPage("api-keys")}>

View file

@ -29,14 +29,19 @@ const Navbar: React.FC<NavbarProps> = ({ userID, userRole, userEmail }) => {
const isLocal = process.env.NODE_ENV === "development";
const imageUrl = isLocal ? "http://localhost:4000/get_image" : "/get_image";
return (
<nav className="left-0 right-0 top-0 flex justify-between items-center h-12 mb-4">
<div className="text-left mx-4 my-2 absolute top-0 left-0">
<div className="text-left my-2 absolute top-0 left-0">
<div className="flex flex-col items-center">
<Link href="/">
<button className="text-gray-800 text-2xl px-4 py-1 rounded text-center">
<img src={imageUrl} width={200} height={200} alt="LiteLLM Brand" className="mr-2" />
<button className="text-gray-800 text-2xl py-1 rounded text-center">
<img
src={imageUrl}
width={200}
height={200}
alt="LiteLLM Brand"
className="mr-2"
/>
</button>
</Link>
</div>

View file

@ -0,0 +1,274 @@
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { Typography } from "antd";
import {
Button as Button2,
Modal,
Form,
Input,
Select as Select2,
InputNumber,
message,
} from "antd";
import { Select, SelectItem } from "@tremor/react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
Card,
Icon,
Button,
Col,
Grid,
} from "@tremor/react";
import { CogIcon } from "@heroicons/react/outline";
interface TeamProps {
teams: string[] | null;
searchParams: any;
}
const Team: React.FC<TeamProps> = ({ teams, searchParams }) => {
const [form] = Form.useForm();
const [memberForm] = Form.useForm();
const { Title, Paragraph } = Typography;
const [value, setValue] = useState("");
const [isTeamModalVisible, setIsTeamModalVisible] = useState(false);
const [isAddMemberModalVisible, setIsAddMemberModalVisible] = useState(false);
const handleOk = () => {
setIsTeamModalVisible(false);
form.resetFields();
};
const handleMemberOk = () => {
setIsAddMemberModalVisible(false);
memberForm.resetFields();
};
const handleCancel = () => {
setIsTeamModalVisible(false);
form.resetFields();
};
const handleMemberCancel = () => {
setIsAddMemberModalVisible(false);
memberForm.resetFields();
};
const handleCreate = async (formValues: Record<string, any>) => {
try {
console.log("reaches here");
} catch (error) {
console.error("Error creating the key:", error);
}
};
const handleMemberCreate = async (formValues: Record<string, any>) => {
try {
console.log("reaches here");
} catch (error) {
console.error("Error creating the key:", error);
}
};
console.log(`received teams ${teams}`);
return (
<div className="w-full">
<Grid numItems={1} className="gap-2 p-2 h-[75vh] w-full">
<Col numColSpan={1}>
<Title level={4}>All Teams</Title>
<Card className="w-full mx-auto flex-auto overflow-y-auto max-h-[50vh]">
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Team Name</TableHeaderCell>
<TableHeaderCell>Spend (USD)</TableHeaderCell>
<TableHeaderCell>Budget (USD)</TableHeaderCell>
<TableHeaderCell>TPM / RPM Limits</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Wilhelm Tell</TableCell>
<TableCell className="text-right">1</TableCell>
<TableCell>Uri, Schwyz, Unterwalden</TableCell>
<TableCell>National Hero</TableCell>
</TableRow>
<TableRow>
<TableCell>The Witcher</TableCell>
<TableCell className="text-right">129</TableCell>
<TableCell>Kaedwen</TableCell>
<TableCell>Legend</TableCell>
</TableRow>
<TableRow>
<TableCell>Mizutsune</TableCell>
<TableCell className="text-right">82</TableCell>
<TableCell>Japan</TableCell>
<TableCell>N/A</TableCell>
</TableRow>
</TableBody>
</Table>
</Card>
</Col>
<Col numColSpan={1}>
<Button
className="mx-auto"
onClick={() => setIsTeamModalVisible(true)}
>
+ Create New Team
</Button>
<Modal
title="Create Team"
visible={isTeamModalVisible}
width={800}
footer={null}
onOk={handleOk}
onCancel={handleCancel}
>
<Form
form={form}
onFinish={handleCreate}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
labelAlign="left"
>
<>
<Form.Item label="Team Name" name="team_alias">
<Input />
</Form.Item>
<Form.Item label="Models" name="models">
<Select2
mode="multiple"
placeholder="Select models"
style={{ width: "100%" }}
>
{/* {userModels.map((model) => (
<Option key={model} value={model}>
{model}
</Option>
))} */}
</Select2>
</Form.Item>
<Form.Item label="Max Budget (USD)" name="max_budget">
<InputNumber step={0.01} precision={2} width={200} />
</Form.Item>
<Form.Item
label="Tokens per minute Limit (TPM)"
name="tpm_limit"
>
<InputNumber step={1} width={400} />
</Form.Item>
<Form.Item
label="Requests per minute Limit (RPM)"
name="rpm_limit"
>
<InputNumber step={1} width={400} />
</Form.Item>
</>
<div style={{ textAlign: "right", marginTop: "10px" }}>
<Button2 htmlType="submit">Create Team</Button2>
</div>
</Form>
</Modal>
</Col>
<Col numColSpan={1}>
<Title level={4}>Team Members</Title>
<Paragraph>
If you belong to multiple teams, this setting controls which teams'
members you see.
</Paragraph>
{teams && teams.length > 0 ? (
<Select
id="distance"
value={value}
onValueChange={setValue}
className="mt-2"
>
{teams.map((model) => (
<SelectItem value="model">{model}</SelectItem>
))}
</Select>
) : (
<Paragraph>
No team created. <b>Defaulting to personal account.</b>
</Paragraph>
)}
</Col>
<Col numColSpan={1}>
<Card className="w-full mx-auto flex-auto overflow-y-auto max-h-[50vh]">
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Member Name</TableHeaderCell>
<TableHeaderCell>Role</TableHeaderCell>
<TableHeaderCell>Action</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Wilhelm Tell</TableCell>
<TableCell>Uri, Schwyz, Unterwalden</TableCell>
<TableCell>
<Icon icon={CogIcon} size="sm" />
</TableCell>
</TableRow>
<TableRow>
<TableCell>The Witcher</TableCell>
<TableCell>Kaedwen</TableCell>
<TableCell>
<Icon icon={CogIcon} size="sm" />
</TableCell>
</TableRow>
<TableRow>
<TableCell>Mizutsune</TableCell>
<TableCell>Japan</TableCell>
<TableCell>
<Icon icon={CogIcon} size="sm" />
</TableCell>
</TableRow>
</TableBody>
</Table>
</Card>
</Col>
<Col numColSpan={1}>
<Button
className="mx-auto mb-5"
onClick={() => setIsAddMemberModalVisible(true)}
>
+ Add member
</Button>
<Modal
title="Add member"
visible={isAddMemberModalVisible}
width={800}
footer={null}
onOk={handleMemberOk}
onCancel={handleMemberCancel}
>
<Form
form={form}
onFinish={handleMemberCreate}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
labelAlign="left"
>
<>
<Form.Item label="Email" name="user_email">
<Input />
</Form.Item>
</>
<div style={{ textAlign: "right", marginTop: "10px" }}>
<Button2 htmlType="submit">Add member</Button2>
</div>
</Form>
</Modal>
</Col>
</Grid>
</div>
);
};
export default Team;

View file

@ -25,19 +25,22 @@ interface UserDashboardProps {
userID: string | null;
userRole: string | null;
userEmail: string | null;
teams: string[] | null;
setUserRole: React.Dispatch<React.SetStateAction<string>>;
setUserEmail: React.Dispatch<React.SetStateAction<string | null>>;
setTeams: React.Dispatch<React.SetStateAction<string[] | null>>;
}
const UserDashboard: React.FC<UserDashboardProps> = ({
userID,
userRole,
teams,
setUserRole,
userEmail,
setUserEmail,
setTeams,
}) => {
const [data, setData] = useState<null | any[]>(null); // Keep the initialization of state here
const [teams, setTeams] = useState<null | string[]>(null); // Keep the initialization of state here
const [userSpendData, setUserSpendData] = useState<UserSpendData | null>(
null
);