forked from phoenix/litellm-mirror
Merge pull request #3197 from BerriAI/litellm_ui_flow_for_non_admins
[UI] - simplify "Create Key" for non admins
This commit is contained in:
commit
cc4e66d04a
3 changed files with 154 additions and 17 deletions
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { Button, TextInput, Grid, Col } from "@tremor/react";
|
import { Button, TextInput, Grid, Col } from "@tremor/react";
|
||||||
import { Card, Metric, Text, Title, Subtitle } from "@tremor/react";
|
import { Card, Metric, Text, Title, Subtitle, Accordion, AccordionHeader, AccordionBody, } from "@tremor/react";
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||||
import {
|
import {
|
||||||
Button as Button2,
|
Button as Button2,
|
||||||
|
@ -116,7 +116,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
||||||
wrapperCol={{ span: 16 }}
|
wrapperCol={{ span: 16 }}
|
||||||
labelAlign="left"
|
labelAlign="left"
|
||||||
>
|
>
|
||||||
{userRole === "App Owner" || userRole === "Admin" || userRole === "App User" ? (
|
{userRole === "App Owner" || userRole === "Admin" ? (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Key Name"
|
label="Key Name"
|
||||||
|
@ -248,16 +248,143 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Form.Item label="Key Name" name="key_alias">
|
<Form.Item
|
||||||
|
label="Key Name"
|
||||||
|
name="key_alias"
|
||||||
|
rules={[{ required: true, message: 'Please input a key name' }]}
|
||||||
|
help="required"
|
||||||
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Team ID (Contact Group)" name="team_id">
|
<Form.Item
|
||||||
<Input placeholder="default team (create a new team)" />
|
label="Team ID"
|
||||||
|
name="team_id"
|
||||||
|
hidden={true}
|
||||||
|
initialValue={team ? team["team_id"] : null}
|
||||||
|
valuePropName="team_id"
|
||||||
|
className="mt-8"
|
||||||
|
>
|
||||||
|
<Input value={team ? team["team_alias"] : ""} disabled />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label="Description" name="description">
|
<Form.Item
|
||||||
<Input.TextArea placeholder="Enter description" rows={4} />
|
label="Models"
|
||||||
|
name="models"
|
||||||
|
rules={[{ required: true, message: 'Please select a model' }]}
|
||||||
|
help="required"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
placeholder="Select models"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
>
|
||||||
|
<Option key="all-team-models" value="all-team-models">
|
||||||
|
All Team Models
|
||||||
|
</Option>
|
||||||
|
{team && team.models ? (
|
||||||
|
team.models.includes("all-proxy-models") ? (
|
||||||
|
userModels.map((model: string) => (
|
||||||
|
(
|
||||||
|
<Option key={model} value={model}>
|
||||||
|
{model}
|
||||||
|
</Option>
|
||||||
|
)
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
team.models.map((model: string) => (
|
||||||
|
<Option key={model} value={model}>
|
||||||
|
{model}
|
||||||
|
</Option>
|
||||||
|
))
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
userModels.map((model: string) => (
|
||||||
|
<Option key={model} value={model}>
|
||||||
|
{model}
|
||||||
|
</Option>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Accordion className="mt-8">
|
||||||
|
<AccordionHeader>
|
||||||
|
<b>Optional Settings</b>
|
||||||
|
</AccordionHeader>
|
||||||
|
<AccordionBody>
|
||||||
|
<Form.Item
|
||||||
|
className="mt-8"
|
||||||
|
label="Max Budget (USD)"
|
||||||
|
name="max_budget"
|
||||||
|
help={`Budget cannot exceed team max budget: $${team?.max_budget !== null && team?.max_budget !== undefined ? team?.max_budget : 'unlimited'}`}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator: async (_, value) => {
|
||||||
|
if (value && team && team.max_budget !== null && value > team.max_budget) {
|
||||||
|
throw new Error(`Budget cannot exceed team max budget: $${team.max_budget}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber step={0.01} precision={2} width={200} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
className="mt-8"
|
||||||
|
label="Reset Budget"
|
||||||
|
name="budget_duration"
|
||||||
|
help={`Team Reset Budget: ${team?.budget_duration !== null && team?.budget_duration !== undefined ? team?.budget_duration : 'None'}`}
|
||||||
|
>
|
||||||
|
<Select defaultValue={null} placeholder="n/a">
|
||||||
|
<Select.Option value="24h">daily</Select.Option>
|
||||||
|
<Select.Option value="30d">monthly</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
className="mt-8"
|
||||||
|
label="Tokens per minute Limit (TPM)"
|
||||||
|
name="tpm_limit"
|
||||||
|
help={`TPM cannot exceed team TPM limit: ${team?.tpm_limit !== null && team?.tpm_limit !== undefined ? team?.tpm_limit : 'unlimited'}`}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator: async (_, value) => {
|
||||||
|
if (value && team && team.tpm_limit !== null && value > team.tpm_limit) {
|
||||||
|
throw new Error(`TPM limit cannot exceed team TPM limit: ${team.tpm_limit}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber step={1} width={400} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
className="mt-8"
|
||||||
|
label="Requests per minute Limit (RPM)"
|
||||||
|
name="rpm_limit"
|
||||||
|
help={`RPM cannot exceed team RPM limit: ${team?.rpm_limit !== null && team?.rpm_limit !== undefined ? team?.rpm_limit : 'unlimited'}`}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator: async (_, value) => {
|
||||||
|
if (value && team && team.rpm_limit !== null && value > team.rpm_limit) {
|
||||||
|
throw new Error(`RPM limit cannot exceed team RPM limit: ${team.rpm_limit}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber step={1} width={400} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Expire Key (eg: 30s, 30h, 30d)" name="duration" className="mt-8">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Metadata" name="metadata">
|
||||||
|
<Input.TextArea rows={4} placeholder="Enter metadata as JSON" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
</AccordionBody>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { Select, SelectItem, Text, Title } from "@tremor/react";
|
||||||
interface DashboardTeamProps {
|
interface DashboardTeamProps {
|
||||||
teams: Object[] | null;
|
teams: Object[] | null;
|
||||||
setSelectedTeam: React.Dispatch<React.SetStateAction<any | null>>;
|
setSelectedTeam: React.Dispatch<React.SetStateAction<any | null>>;
|
||||||
|
userRole: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TeamInterface = {
|
type TeamInterface = {
|
||||||
|
@ -15,6 +16,7 @@ type TeamInterface = {
|
||||||
const DashboardTeam: React.FC<DashboardTeamProps> = ({
|
const DashboardTeam: React.FC<DashboardTeamProps> = ({
|
||||||
teams,
|
teams,
|
||||||
setSelectedTeam,
|
setSelectedTeam,
|
||||||
|
userRole,
|
||||||
}) => {
|
}) => {
|
||||||
const defaultTeam: TeamInterface = {
|
const defaultTeam: TeamInterface = {
|
||||||
models: [],
|
models: [],
|
||||||
|
@ -25,19 +27,27 @@ const DashboardTeam: React.FC<DashboardTeamProps> = ({
|
||||||
|
|
||||||
const [value, setValue] = useState(defaultTeam);
|
const [value, setValue] = useState(defaultTeam);
|
||||||
|
|
||||||
const updatedTeams = teams ? [...teams, defaultTeam] : [defaultTeam];
|
let updatedTeams;
|
||||||
|
if (userRole === "App User") {
|
||||||
|
// Non-Admin SSO users should only see their own team - they should not see "Default Team"
|
||||||
|
updatedTeams = teams;
|
||||||
|
} else {
|
||||||
|
updatedTeams = teams ? [...teams, defaultTeam] : [defaultTeam];
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-5 mb-5">
|
<div className="mt-5 mb-5">
|
||||||
<Title>Select Team</Title>
|
<Title>Select Team</Title>
|
||||||
<Text>
|
{userRole !== "App User" && (
|
||||||
If you belong to multiple teams, this setting controls which team is
|
<>
|
||||||
used by default when creating new API Keys.
|
<Text>
|
||||||
</Text>
|
If you belong to multiple teams, this setting controls which team is used by default when creating new API Keys.
|
||||||
<Text className="mt-3 mb-3">
|
</Text>
|
||||||
<b>Default Team:</b> If no team_id is set for a key, it will be grouped under here.
|
<Text className="mt-3 mb-3">
|
||||||
</Text>
|
<b>Default Team:</b> If no team_id is set for a key, it will be grouped under here.
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{updatedTeams && updatedTeams.length > 0 ? (
|
{updatedTeams && updatedTeams.length > 0 ? (
|
||||||
<Select defaultValue="0">
|
<Select defaultValue="0">
|
||||||
{updatedTeams.map((team: any, index) => (
|
{updatedTeams.map((team: any, index) => (
|
||||||
|
|
|
@ -257,7 +257,7 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
|
||||||
data={keys}
|
data={keys}
|
||||||
setData={setKeys}
|
setData={setKeys}
|
||||||
/>
|
/>
|
||||||
<DashboardTeam teams={teams} setSelectedTeam={setSelectedTeam} />
|
<DashboardTeam teams={teams} setSelectedTeam={setSelectedTeam} userRole={userRole}/>
|
||||||
</Col>
|
</Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue