chore: commit pending gateway changes
This commit is contained in:
+102
-143
@@ -1,51 +1,73 @@
|
||||
import type { FormEvent, ReactNode } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Boxes, Building2, Gauge, KeyRound, Route, ServerCog, ShieldCheck, UsersRound, Workflow } from 'lucide-react';
|
||||
import type { BaseModelUpsertRequest, CatalogProviderUpsertRequest, PricingRuleSetUpsertRequest } from '@easyai-ai-gateway/contracts';
|
||||
import type {
|
||||
BaseModelUpsertRequest,
|
||||
CatalogProviderUpsertRequest,
|
||||
GatewayAccessRuleBatchRequest,
|
||||
GatewayAccessRuleUpsertRequest,
|
||||
GatewayTenantUpsertRequest,
|
||||
GatewayUserUpsertRequest,
|
||||
PricingRuleSetUpsertRequest,
|
||||
RuntimePolicySetUpsertRequest,
|
||||
UserGroupUpsertRequest,
|
||||
} from '@easyai-ai-gateway/contracts';
|
||||
import type { ConsoleData, StatItem } from '../app-state';
|
||||
import { EntityTable } from '../components/EntityTable';
|
||||
import { PageHeader } from '../components/PageHeader';
|
||||
import { StatGrid } from '../components/StatGrid';
|
||||
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, Input, Label, Select, Tabs } from '../components/ui';
|
||||
import type { AdminSection, LoadState, PlatformForm, PlatformModelForm } from '../types';
|
||||
import { Badge, Card, CardContent, CardHeader, CardTitle, Tabs } from '../components/ui';
|
||||
import type { AdminSection, LoadState, PlatformWithModelsInput } from '../types';
|
||||
import { AccessRulesPanel } from './admin/AccessRulesPanel';
|
||||
import { BaseModelCatalogPanel } from './admin/BaseModelCatalogPanel';
|
||||
import { TenantsPanel, UserGroupsPanel, UsersPanel } from './admin/IdentityManagementPanels';
|
||||
import { PlatformManagementPanel } from './admin/PlatformManagementPanel';
|
||||
import { PricingRulesPanel } from './admin/PricingRulesPanel';
|
||||
import { ProviderManagementPanel } from './admin/ProviderManagementPanel';
|
||||
import { RuntimePoliciesPanel } from './admin/RuntimePoliciesPanel';
|
||||
|
||||
const tabs = [
|
||||
{ value: 'overview', label: '总览', icon: <Workflow size={15} /> },
|
||||
{ value: 'globalModels', label: 'Provider', icon: <Boxes size={15} /> },
|
||||
{ value: 'baseModels', label: '基准模型库', icon: <Boxes size={15} /> },
|
||||
{ value: 'pricing', label: '定价规则', icon: <Gauge size={15} /> },
|
||||
{ value: 'runtime', label: '运行策略', icon: <ShieldCheck size={15} /> },
|
||||
{ value: 'baseModels', label: '基准模型库', icon: <Boxes size={15} /> },
|
||||
{ value: 'platforms', label: '平台管理', icon: <ServerCog size={15} /> },
|
||||
{ value: 'tenants', label: '租户', icon: <Building2 size={15} /> },
|
||||
{ value: 'users', label: '用户', icon: <UsersRound size={15} /> },
|
||||
{ value: 'userGroups', label: '用户组', icon: <UsersRound size={15} /> },
|
||||
{ value: 'runtime', label: '运行限流', icon: <Gauge size={15} /> },
|
||||
{ value: 'accessRules', label: '模型权限', icon: <KeyRound size={15} /> },
|
||||
] satisfies Array<{ value: AdminSection; label: string; icon: ReactNode }>;
|
||||
|
||||
export function AdminPage(props: {
|
||||
data: ConsoleData;
|
||||
platformForm: PlatformForm;
|
||||
platformModelForm: PlatformModelForm;
|
||||
operationMessage: string;
|
||||
section: AdminSection;
|
||||
stats: StatItem[];
|
||||
state: LoadState;
|
||||
onDeleteBaseModel: (baseModelId: string) => Promise<void>;
|
||||
onDeletePlatform: (platformId: string) => Promise<void>;
|
||||
onDeleteProvider: (providerId: string) => Promise<void>;
|
||||
onDeletePricingRuleSet: (ruleSetId: string) => Promise<void>;
|
||||
onPlatformFormChange: (value: PlatformForm) => void;
|
||||
onPlatformModelFormChange: (value: PlatformModelForm) => void;
|
||||
onDeleteRuntimePolicySet: (policySetId: string) => Promise<void>;
|
||||
onDeleteAccessRule: (ruleId: string) => Promise<void>;
|
||||
onDeleteTenant: (tenantId: string) => Promise<void>;
|
||||
onDeleteUser: (userId: string) => Promise<void>;
|
||||
onDeleteUserGroup: (groupId: string) => Promise<void>;
|
||||
onSaveBaseModel: (input: BaseModelUpsertRequest, baseModelId?: string) => Promise<void>;
|
||||
onResetAllBaseModels: () => Promise<void>;
|
||||
onResetBaseModel: (baseModelId: string) => Promise<void>;
|
||||
onBatchAccessRules: (input: GatewayAccessRuleBatchRequest) => Promise<void>;
|
||||
onSavePlatform: (input: PlatformWithModelsInput) => Promise<void>;
|
||||
onSaveProvider: (input: CatalogProviderUpsertRequest, providerId?: string) => Promise<void>;
|
||||
onSavePricingRuleSet: (input: PricingRuleSetUpsertRequest, ruleSetId?: string) => Promise<void>;
|
||||
onSaveRuntimePolicySet: (input: RuntimePolicySetUpsertRequest, policySetId?: string) => Promise<void>;
|
||||
onSaveAccessRule: (input: GatewayAccessRuleUpsertRequest, ruleId?: string) => Promise<void>;
|
||||
onSaveTenant: (input: GatewayTenantUpsertRequest, tenantId?: string) => Promise<void>;
|
||||
onSaveUser: (input: GatewayUserUpsertRequest, userId?: string) => Promise<void>;
|
||||
onSaveUserGroup: (input: UserGroupUpsertRequest, groupId?: string) => Promise<void>;
|
||||
onSectionChange: (value: AdminSection) => void;
|
||||
onSubmitPlatform: (event: FormEvent<HTMLFormElement>) => void;
|
||||
onSubmitPlatformModel: (event: FormEvent<HTMLFormElement>) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="pageStack">
|
||||
<PageHeader eyebrow="Admin" title="管理工作台" description="全局模型、平台、租户用户和运行策略。" />
|
||||
<div className="subPageLayout">
|
||||
<Tabs className="sideTabs" value={props.section} tabs={tabs} onValueChange={props.onSectionChange} />
|
||||
<div className="subPageContent">
|
||||
@@ -63,12 +85,37 @@ export function AdminPage(props: {
|
||||
<BaseModelCatalogPanel
|
||||
baseModels={props.data.baseModels}
|
||||
message={props.operationMessage}
|
||||
pricingRuleSets={props.data.pricingRuleSets}
|
||||
providers={props.data.providers}
|
||||
runtimePolicySets={props.data.runtimePolicySets}
|
||||
state={props.state}
|
||||
onDeleteBaseModel={props.onDeleteBaseModel}
|
||||
onResetAllBaseModels={props.onResetAllBaseModels}
|
||||
onResetBaseModel={props.onResetBaseModel}
|
||||
onSaveBaseModel={props.onSaveBaseModel}
|
||||
/>
|
||||
)}
|
||||
{props.section === 'runtime' && (
|
||||
<RuntimePoliciesPanel
|
||||
message={props.operationMessage}
|
||||
runtimePolicySets={props.data.runtimePolicySets}
|
||||
state={props.state}
|
||||
onDeleteRuntimePolicySet={props.onDeleteRuntimePolicySet}
|
||||
onSaveRuntimePolicySet={props.onSaveRuntimePolicySet}
|
||||
/>
|
||||
)}
|
||||
{props.section === 'accessRules' && (
|
||||
<AccessRulesPanel
|
||||
accessRules={props.data.accessRules}
|
||||
baseModels={props.data.baseModels}
|
||||
message={props.operationMessage}
|
||||
platformModels={props.data.models}
|
||||
platforms={props.data.platforms}
|
||||
state={props.state}
|
||||
userGroups={props.data.userGroups}
|
||||
onBatchAccessRules={props.onBatchAccessRules}
|
||||
/>
|
||||
)}
|
||||
{props.section === 'pricing' && (
|
||||
<PricingRulesPanel
|
||||
message={props.operationMessage}
|
||||
@@ -78,17 +125,52 @@ export function AdminPage(props: {
|
||||
onSavePricingRuleSet={props.onSavePricingRuleSet}
|
||||
/>
|
||||
)}
|
||||
{props.section === 'platforms' && <PlatformsPanel {...props} />}
|
||||
{props.section === 'tenants' && <TenantsPanel data={props.data} />}
|
||||
{props.section === 'users' && <UsersPanel data={props.data} />}
|
||||
{props.section === 'userGroups' && <UserGroupsPanel data={props.data} />}
|
||||
{props.section === 'runtime' && <RuntimePanel data={props.data} />}
|
||||
{props.section === 'platforms' && (
|
||||
<PlatformManagementPanel
|
||||
baseModels={props.data.baseModels}
|
||||
message={props.operationMessage}
|
||||
platformModels={props.data.models}
|
||||
platforms={props.data.platforms}
|
||||
pricingRuleSets={props.data.pricingRuleSets}
|
||||
providers={props.data.providers}
|
||||
state={props.state}
|
||||
onDeletePlatform={props.onDeletePlatform}
|
||||
onSavePlatform={props.onSavePlatform}
|
||||
/>
|
||||
)}
|
||||
{props.section === 'tenants' && <TenantsPanel {...identityPanelProps(props)} />}
|
||||
{props.section === 'users' && <UsersPanel {...identityPanelProps(props)} />}
|
||||
{props.section === 'userGroups' && <UserGroupsPanel {...identityPanelProps(props)} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function identityPanelProps(props: {
|
||||
data: ConsoleData;
|
||||
operationMessage: string;
|
||||
state: LoadState;
|
||||
onDeleteTenant: (tenantId: string) => Promise<void>;
|
||||
onDeleteUser: (userId: string) => Promise<void>;
|
||||
onDeleteUserGroup: (groupId: string) => Promise<void>;
|
||||
onSaveTenant: (input: GatewayTenantUpsertRequest, tenantId?: string) => Promise<void>;
|
||||
onSaveUser: (input: GatewayUserUpsertRequest, userId?: string) => Promise<void>;
|
||||
onSaveUserGroup: (input: UserGroupUpsertRequest, groupId?: string) => Promise<void>;
|
||||
}) {
|
||||
return {
|
||||
data: props.data,
|
||||
operationMessage: props.operationMessage,
|
||||
state: props.state,
|
||||
onDeleteTenant: props.onDeleteTenant,
|
||||
onDeleteUser: props.onDeleteUser,
|
||||
onDeleteUserGroup: props.onDeleteUserGroup,
|
||||
onSaveTenant: props.onSaveTenant,
|
||||
onSaveUser: props.onSaveUser,
|
||||
onSaveUserGroup: props.onSaveUserGroup,
|
||||
};
|
||||
}
|
||||
|
||||
function OverviewPanel(props: { data: ConsoleData; stats: StatItem[] }) {
|
||||
const enabledPlatforms = props.data.platforms.filter((item) => item.status === 'enabled');
|
||||
const chatModels = props.data.models.filter((item) => item.modelType === 'chat' && item.enabled);
|
||||
@@ -111,7 +193,7 @@ function OverviewPanel(props: { data: ConsoleData; stats: StatItem[] }) {
|
||||
<EntityTable
|
||||
columns={['Provider', '平台', '状态', '优先级']}
|
||||
empty="暂无平台"
|
||||
rows={props.data.platforms.slice(0, 6).map((item) => [item.provider, item.name, item.status, item.priority])}
|
||||
rows={props.data.platforms.slice(0, 6).map((item) => [item.provider, item.internalName || item.name, item.status, item.priority])}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -183,126 +265,3 @@ function ManagementCard(props: { detail: string; icon: ReactNode; label: string;
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function PlatformsPanel(props: {
|
||||
data: ConsoleData;
|
||||
platformForm: PlatformForm;
|
||||
platformModelForm: PlatformModelForm;
|
||||
state: LoadState;
|
||||
onPlatformFormChange: (value: PlatformForm) => void;
|
||||
onPlatformModelFormChange: (value: PlatformModelForm) => void;
|
||||
onSubmitPlatform: (event: FormEvent<HTMLFormElement>) => void;
|
||||
onSubmitPlatformModel: (event: FormEvent<HTMLFormElement>) => void;
|
||||
}) {
|
||||
return (
|
||||
<section className="contentGrid two">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>创建平台</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form className="formGrid" onSubmit={props.onSubmitPlatform}>
|
||||
<Label>Provider<Input value={props.platformForm.provider} onChange={(event) => props.onPlatformFormChange({ ...props.platformForm, provider: event.target.value })} /></Label>
|
||||
<Label>平台 Key<Input value={props.platformForm.platformKey} onChange={(event) => props.onPlatformFormChange({ ...props.platformForm, platformKey: event.target.value })} /></Label>
|
||||
<Label>名称<Input value={props.platformForm.name} onChange={(event) => props.onPlatformFormChange({ ...props.platformForm, name: event.target.value })} /></Label>
|
||||
<Label>Base URL<Input value={props.platformForm.baseUrl} onChange={(event) => props.onPlatformFormChange({ ...props.platformForm, baseUrl: event.target.value })} /></Label>
|
||||
<Label>
|
||||
定价规则
|
||||
<Select value={props.platformForm.pricingRuleSetId} onChange={(event) => props.onPlatformFormChange({ ...props.platformForm, pricingRuleSetId: event.target.value })}>
|
||||
<option value="">默认规则</option>
|
||||
{props.data.pricingRuleSets.map((item) => <option value={item.id} key={item.id}>{item.name}</option>)}
|
||||
</Select>
|
||||
</Label>
|
||||
<Label>平台折扣率<Input value={props.platformForm.defaultDiscountFactor} onChange={(event) => props.onPlatformFormChange({ ...props.platformForm, defaultDiscountFactor: event.target.value })} /></Label>
|
||||
<Button type="submit" disabled={props.state === 'loading'}>创建平台</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>绑定平台模型</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form className="formGrid" onSubmit={props.onSubmitPlatformModel}>
|
||||
<Label>
|
||||
平台
|
||||
<Select value={props.platformModelForm.platformId} onChange={(event) => props.onPlatformModelFormChange({ ...props.platformModelForm, platformId: event.target.value })}>
|
||||
<option value="">选择平台</option>
|
||||
{props.data.platforms.map((item) => <option value={item.id} key={item.id}>{item.name}</option>)}
|
||||
</Select>
|
||||
</Label>
|
||||
<Label>
|
||||
基准模型
|
||||
<Select value={props.platformModelForm.canonicalModelKey} onChange={(event) => updateBaseModel(event.target.value, props)}>
|
||||
<option value="">选择基准模型</option>
|
||||
{props.data.baseModels.map((item) => <option value={item.canonicalModelKey} key={item.id}>{item.canonicalModelKey}</option>)}
|
||||
</Select>
|
||||
</Label>
|
||||
<Label>模型名<Input value={props.platformModelForm.modelName} onChange={(event) => props.onPlatformModelFormChange({ ...props.platformModelForm, modelName: event.target.value })} /></Label>
|
||||
<Label>别名<Input value={props.platformModelForm.modelAlias} onChange={(event) => props.onPlatformModelFormChange({ ...props.platformModelForm, modelAlias: event.target.value })} /></Label>
|
||||
<Label>
|
||||
定价规则
|
||||
<Select value={props.platformModelForm.pricingRuleSetId} onChange={(event) => props.onPlatformModelFormChange({ ...props.platformModelForm, pricingRuleSetId: event.target.value })}>
|
||||
<option value="">继承平台规则</option>
|
||||
{props.data.pricingRuleSets.map((item) => <option value={item.id} key={item.id}>{item.name}</option>)}
|
||||
</Select>
|
||||
</Label>
|
||||
<Label>模型折扣率<Input value={props.platformModelForm.discountFactor} onChange={(event) => props.onPlatformModelFormChange({ ...props.platformModelForm, discountFactor: event.target.value })} /></Label>
|
||||
<Button type="submit" disabled={props.state === 'loading'}>绑定模型</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="spanTwo">
|
||||
<CardHeader>
|
||||
<CardTitle>平台模型</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<EntityTable
|
||||
columns={['模型', '类型', '平台', '状态']}
|
||||
empty="暂无平台模型"
|
||||
rows={props.data.models.map((item) => [item.modelName, item.modelType, item.platformName ?? item.provider ?? '-', item.enabled ? 'enabled' : 'disabled'])}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function TenantsPanel(props: { data: ConsoleData }) {
|
||||
return <Card><CardHeader><CardTitle>租户管理</CardTitle></CardHeader><CardContent><EntityTable columns={['Key', '名称', '来源', '状态']} empty="暂无租户" rows={props.data.tenants.map((item) => [item.tenantKey, item.name, item.source, item.status])} /></CardContent></Card>;
|
||||
}
|
||||
|
||||
function UsersPanel(props: { data: ConsoleData }) {
|
||||
return <Card><CardHeader><CardTitle>用户管理</CardTitle></CardHeader><CardContent><EntityTable columns={['用户名', '租户', '来源', '状态']} empty="暂无用户" rows={props.data.users.map((item) => [item.username, item.tenantKey ?? '-', item.source, item.status])} /></CardContent></Card>;
|
||||
}
|
||||
|
||||
function UserGroupsPanel(props: { data: ConsoleData }) {
|
||||
return <Card><CardHeader><CardTitle>用户组策略</CardTitle></CardHeader><CardContent><EntityTable columns={['Key', '名称', '优先级', '状态']} empty="暂无用户组" rows={props.data.userGroups.map((item) => [item.groupKey, item.name, item.priority, item.status])} /></CardContent></Card>;
|
||||
}
|
||||
|
||||
function RuntimePanel(props: { data: ConsoleData }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>TPM / RPM / 并发窗口</CardTitle>
|
||||
<Badge variant="secondary">{props.data.rateLimitWindows.length}</Badge>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<EntityTable columns={['Scope', '指标', '使用', '限制']} empty="暂无限流窗口" rows={props.data.rateLimitWindows.map((item) => [item.scopeKey, item.metric, item.usedValue, item.limitValue])} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function updateBaseModel(value: string, props: Parameters<typeof PlatformsPanel>[0]) {
|
||||
const base = props.data.baseModels.find((item) => item.canonicalModelKey === value);
|
||||
props.onPlatformModelFormChange({
|
||||
...props.platformModelForm,
|
||||
canonicalModelKey: value,
|
||||
modelAlias: value,
|
||||
modelName: base?.providerModelName ?? '',
|
||||
modelType: base?.modelType ?? props.platformModelForm.modelType,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user