UI Fixes and Improvements (02/14/2025) p1 (#8546)

* fix(user_dashboard.tsx): add bounding height to keys table ui

prevents table from exceeding page height

* fix(create_key_button.tsx): do not require team to be selected when user creating keys - allow personal key creation

* fix(team_info.tsx): allow proxy admin to edit/delete team members even when not specifically team admins
This commit is contained in:
Krish Dholakia 2025-02-14 11:30:49 -08:00 committed by GitHub
parent 08aa201e15
commit 3deefc8e2f
5 changed files with 16 additions and 183 deletions

View file

@ -277,7 +277,7 @@ export function AllKeysTable({
]; ];
return ( return (
<div className="w-full"> <div className="w-full h-full overflow-hidden">
{selectedKeyId ? ( {selectedKeyId ? (
<KeyInfoView <KeyInfoView
keyId={selectedKeyId} keyId={selectedKeyId}
@ -289,7 +289,7 @@ export function AllKeysTable({
teams={teams} teams={teams}
/> />
) : ( ) : (
<div className="border-b py-4"> <div className="border-b py-4 flex-1 overflow-hidden">
<div className="flex items-center justify-between w-full"> <div className="flex items-center justify-between w-full">
<TeamFilter <TeamFilter
teams={teams} teams={teams}
@ -324,6 +324,7 @@ export function AllKeysTable({
</div> </div>
</div> </div>
</div> </div>
<div className="h-[32rem] overflow-auto">
<DataTable <DataTable
columns={columns.filter(col => col.id !== 'expander')} columns={columns.filter(col => col.id !== 'expander')}
data={keys} data={keys}
@ -332,7 +333,7 @@ export function AllKeysTable({
renderSubComponent={() => <></>} renderSubComponent={() => <></>}
/> />
</div> </div>
</div>
)} )}
</div> </div>

View file

@ -289,7 +289,6 @@ const CreateKey: React.FC<CreateKeyProps> = ({
name="team_id" name="team_id"
initialValue={team ? team.team_id : null} initialValue={team ? team.team_id : null}
className="mt-8" className="mt-8"
rules={[{ required: true, message: 'Please select a team' }]}
> >
<Select <Select
showSearch showSearch

View file

@ -2545,8 +2545,8 @@ export const teamMemberDeleteCall = async (
}, },
body: JSON.stringify({ body: JSON.stringify({
team_id: teamId, team_id: teamId,
...(formValues.user_email && { user_email: formValues.user_email }), ...(formValues.user_email !== undefined && { user_email: formValues.user_email }),
...(formValues.user_id && { user_id: formValues.user_id }) ...(formValues.user_id !== undefined && { user_id: formValues.user_id })
}), }),
}); });

View file

@ -90,7 +90,6 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
console.log("userModels in team info", userModels); console.log("userModels in team info", userModels);
const canManageMembers = is_team_admin || is_proxy_admin;
const canEditTeam = is_team_admin || is_proxy_admin; const canEditTeam = is_team_admin || is_proxy_admin;
const fetchTeamInfo = async () => { const fetchTeamInfo = async () => {
@ -202,172 +201,6 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
} }
}; };
const renderSettingsPanel = () => {
if (!teamData?.team_info) return null;
const info = teamData.team_info;
// Extract existing guardrails from team metadata
let existingGuardrails: string[] = [];
try {
existingGuardrails = info.metadata?.guardrails || [];
} catch (error) {
console.error("Error extracting guardrails:", error);
}
if (!isEditing) {
return (
<Card>
<div className="flex justify-between">
<Title>Team Settings</Title>
{canEditTeam && (
<Button type="primary" onClick={() => setIsEditing(true)}>
Edit Settings
</Button>
)}
</div>
<div className="mt-4 space-y-4">
<div>
<Text className="font-medium">Team Name</Text>
<Text>{info.team_alias}</Text>
</div>
<div>
<Text className="font-medium">Team ID</Text>
<Text className="font-mono">{info.team_id}</Text>
</div>
<div>
<Text className="font-medium">Created At</Text>
<Text>{new Date(info.created_at).toLocaleString()}</Text>
</div>
<div>
<Text className="font-medium">Models</Text>
<div className="flex flex-wrap gap-2 mt-1">
{info.models.map((model, index) => (
<Badge key={index} color="red">
{model}
</Badge>
))}
</div>
</div>
<div>
<Text className="font-medium">Rate Limits</Text>
<Text>TPM: {info.tpm_limit || 'Unlimited'}</Text>
<Text>RPM: {info.rpm_limit || 'Unlimited'}</Text>
</div>
<div>
<Text className="font-medium">Budget</Text>
<Text>Max: ${info.max_budget || 'Unlimited'}</Text>
<Text>Reset: {info.budget_duration || 'Never'}</Text>
</div>
<div>
<Text className="font-medium">Status</Text>
<Badge color={info.blocked ? 'red' : 'green'}>
{info.blocked ? 'Blocked' : 'Active'}
</Badge>
</div>
</div>
</Card>
);
}
return (
<Card>
<Title>Edit Team Settings</Title>
<Form
form={form}
onFinish={handleTeamUpdate}
initialValues={{
...info,
guardrails: existingGuardrails
}}
layout="vertical"
className="mt-4"
>
<Form.Item
label="Team Name"
name="team_alias"
rules={[{ required: true, message: "Please input a team name" }]}
>
<Input />
</Form.Item>
<Form.Item label="Models" name="models">
<Select2
mode="multiple"
placeholder="Select models"
style={{ width: "100%" }}
>
<Select2.Option
key="all-proxy-models"
value="all-proxy-models"
>
All Proxy Models
</Select2.Option>
{userModels.map((model) => (
<Select2.Option key={model} value={model}>
{getModelDisplayName(model)}
</Select2.Option>
))}
</Select2>
</Form.Item>
<Form.Item label="Max Budget (USD)" name="max_budget">
<InputNumber step={0.01} precision={2} style={{ width: 200 }} />
</Form.Item>
<Form.Item label="Reset Budget" name="budget_duration">
<Select placeholder="n/a">
<Select.Option value="24h">daily</Select.Option>
<Select.Option value="7d">weekly</Select.Option>
<Select.Option value="30d">monthly</Select.Option>
</Select>
</Form.Item>
<Form.Item label="Tokens per minute Limit (TPM)" name="tpm_limit">
<InputNumber step={1} style={{ width: "100%" }} />
</Form.Item>
<Form.Item label="Requests per minute Limit (RPM)" name="rpm_limit">
<InputNumber step={1} style={{ width: "100%" }} />
</Form.Item>
<Form.Item
label={
<span>
Guardrails{' '}
<Tooltip title="Setup your first guardrail">
<a
href="https://docs.litellm.ai/docs/proxy/guardrails/quick_start"
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
>
<InfoCircleOutlined style={{ marginLeft: '4px' }} />
</a>
</Tooltip>
</span>
}
name="guardrails"
help="Select existing guardrails or enter new ones"
>
<Select
mode="tags"
placeholder="Select or enter guardrails"
/>
</Form.Item>
<div className="flex justify-end gap-2 mt-6">
<Button onClick={() => setIsEditing(false)}>
Cancel
</Button>
<Button type="primary" htmlType="submit">
Save Changes
</Button>
</div>
</Form>
</Card>
);
};
if (loading) { if (loading) {
return <div className="p-4">Loading...</div>; return <div className="p-4">Loading...</div>;
} }
@ -461,7 +294,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
<Text className="font-mono">{member.role}</Text> <Text className="font-mono">{member.role}</Text>
</TableCell> </TableCell>
<TableCell> <TableCell>
{is_team_admin && ( {canEditTeam && (
<> <>
<Icon <Icon
icon={PencilAltIcon} icon={PencilAltIcon}

View file

@ -335,8 +335,8 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
console.log("inside user dashboard, selected team", selectedTeam); console.log("inside user dashboard, selected team", selectedTeam);
return ( return (
<div className="w-full mx-4"> <div className="w-full mx-4 overflow-hidden h-[75vh]">
<Grid numItems={1} className="gap-2 p-8 h-[75vh] w-full mt-2"> <Grid numItems={1} className="gap-2 p-8 w-full mt-2">
<Col numColSpan={1} className="flex flex-col gap-2"> <Col numColSpan={1} className="flex flex-col gap-2">
<CreateKey <CreateKey
key={selectedTeam ? selectedTeam.team_id : null} key={selectedTeam ? selectedTeam.team_id : null}