完善 API Key 能力范围可视化和维护
This commit is contained in:
@@ -62,6 +62,7 @@ export function AuditLogsPanel(props: { auditLogs: GatewayAuditLog[]; message?:
|
||||
|
||||
function actionLabel(action: string) {
|
||||
if (action === 'wallet.balance.set') return '余额调整';
|
||||
if (action === 'wallet.balance.recharge') return '余额充值';
|
||||
return action;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
GatewayWalletAccount,
|
||||
UserGroup,
|
||||
UserGroupUpsertRequest,
|
||||
WalletBalanceAdjustmentRequest,
|
||||
WalletRechargeRequest,
|
||||
} from '@easyai-ai-gateway/contracts';
|
||||
import {
|
||||
Badge,
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
FormDialog,
|
||||
Input,
|
||||
Label,
|
||||
ScreenMessage,
|
||||
Select,
|
||||
Table,
|
||||
TableCell,
|
||||
@@ -68,7 +69,7 @@ type UserForm = {
|
||||
|
||||
type WalletForm = {
|
||||
currency: string;
|
||||
balance: string;
|
||||
amount: string;
|
||||
reason: string;
|
||||
};
|
||||
|
||||
@@ -149,11 +150,12 @@ export function TenantsPanel(props: IdentityPanelProps) {
|
||||
|
||||
return (
|
||||
<div className="pageStack">
|
||||
<ScreenMessage message={identityLocalErrorMessage(localError, props)} variant="error" duration={0} onClose={() => setLocalError('')} />
|
||||
<IdentityHeader
|
||||
count={props.data.tenants.length}
|
||||
description="租户可独立维护,也可承载 server-main 同步过来的租户标识。"
|
||||
icon={<Building2 size={17} />}
|
||||
message={localError || props.operationMessage}
|
||||
message={identityHeaderMessage(props)}
|
||||
title="租户管理"
|
||||
actionLabel="新增租户"
|
||||
onCreate={openCreateDialog}
|
||||
@@ -256,8 +258,8 @@ export function UsersPanel(props: IdentityPanelProps) {
|
||||
setWalletUser(user);
|
||||
setWalletForm({
|
||||
currency: wallet?.currency ?? 'resource',
|
||||
balance: String(wallet?.balance ?? 0),
|
||||
reason: '',
|
||||
amount: '',
|
||||
reason: defaultRechargeReason,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -290,9 +292,9 @@ export function UsersPanel(props: IdentityPanelProps) {
|
||||
event.preventDefault();
|
||||
setLocalError('');
|
||||
if (!walletUser) return;
|
||||
const balance = Number(walletForm.balance);
|
||||
if (!Number.isFinite(balance) || balance < 0) {
|
||||
setLocalError('余额必须是非负数字');
|
||||
const amount = Number(walletForm.amount);
|
||||
if (!Number.isFinite(amount) || amount <= 0) {
|
||||
setLocalError('充值金额必须大于 0');
|
||||
return;
|
||||
}
|
||||
if (!walletForm.reason.trim()) {
|
||||
@@ -300,15 +302,15 @@ export function UsersPanel(props: IdentityPanelProps) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await props.onSetUserWalletBalance(walletUser.id, {
|
||||
await props.onRechargeUserWalletBalance(walletUser.id, {
|
||||
currency: walletForm.currency,
|
||||
balance,
|
||||
amount,
|
||||
reason: walletForm.reason.trim(),
|
||||
idempotencyKey: newIdempotencyKey(),
|
||||
});
|
||||
closeWalletDialog();
|
||||
} catch (err) {
|
||||
setLocalError(err instanceof Error ? err.message : '更新余额失败');
|
||||
setLocalError(err instanceof Error ? err.message : '充值用户余额失败');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,11 +321,12 @@ export function UsersPanel(props: IdentityPanelProps) {
|
||||
|
||||
return (
|
||||
<div className="pageStack">
|
||||
<ScreenMessage message={identityLocalErrorMessage(localError, props)} variant="error" duration={0} onClose={() => setLocalError('')} />
|
||||
<IdentityHeader
|
||||
count={props.data.users.length}
|
||||
description="支持本地用户闭环,也保留 server-main 用户同步字段。"
|
||||
icon={<UserRound size={17} />}
|
||||
message={localError || props.operationMessage}
|
||||
message={identityHeaderMessage(props)}
|
||||
title="用户管理"
|
||||
actionLabel="新增用户"
|
||||
onCreate={openCreateDialog}
|
||||
@@ -400,18 +403,19 @@ export function UsersPanel(props: IdentityPanelProps) {
|
||||
onConfirm={() => pendingDeleteUser ? deleteUser(pendingDeleteUser) : undefined}
|
||||
/>
|
||||
<FormDialog
|
||||
ariaLabel="修改用户余额"
|
||||
ariaLabel="充值用户余额"
|
||||
className="identityDialog walletDialog"
|
||||
eyebrow="Wallet Adjustment"
|
||||
footer={<WalletDialogFooter loading={props.state === 'loading'} onCancel={closeWalletDialog} />}
|
||||
open={Boolean(walletUser)}
|
||||
title={`修改余额${walletUser ? ` · ${walletUser.displayName || walletUser.username}` : ''}`}
|
||||
title={`充值余额${walletUser ? ` · ${walletUser.displayName || walletUser.username}` : ''}`}
|
||||
onClose={closeWalletDialog}
|
||||
onSubmit={submitWallet}
|
||||
>
|
||||
<Label>币种<Input size="sm" value={walletForm.currency} onChange={(event) => setWalletForm({ ...walletForm, currency: event.target.value })} placeholder="resource" /></Label>
|
||||
<Label>目标余额<Input size="sm" value={walletForm.balance} inputMode="decimal" onChange={(event) => setWalletForm({ ...walletForm, balance: event.target.value })} /></Label>
|
||||
<Label className="spanTwo">调整原因<Textarea size="sm" rows={3} value={walletForm.reason} onChange={(event) => setWalletForm({ ...walletForm, reason: event.target.value })} placeholder="例如:线下充值确认 / 客服补偿 / 账务修正" /></Label>
|
||||
<Label>充值金额<Input size="sm" value={walletForm.amount} inputMode="decimal" onChange={(event) => setWalletForm({ ...walletForm, amount: event.target.value })} placeholder="100" /></Label>
|
||||
<Label className="spanTwo">当前余额<Input size="sm" value={walletUser ? walletSummary(walletUser) : '0 resource'} disabled /></Label>
|
||||
<Label className="spanTwo">充值原因<Textarea size="sm" rows={3} value={walletForm.reason} onChange={(event) => setWalletForm({ ...walletForm, reason: event.target.value })} placeholder="例如:线下充值确认 / 客服补偿 / 账务修正" /></Label>
|
||||
</FormDialog>
|
||||
</div>
|
||||
);
|
||||
@@ -466,11 +470,12 @@ export function UserGroupsPanel(props: IdentityPanelProps) {
|
||||
|
||||
return (
|
||||
<div className="pageStack">
|
||||
<ScreenMessage message={identityLocalErrorMessage(localError, props)} variant="error" duration={0} onClose={() => setLocalError('')} />
|
||||
<IdentityHeader
|
||||
count={props.data.userGroups.length}
|
||||
description="用户组承载充值折扣、计费折扣、限流和额度策略。"
|
||||
icon={<UsersRound size={17} />}
|
||||
message={localError || props.operationMessage}
|
||||
message={identityHeaderMessage(props)}
|
||||
title="用户组管理"
|
||||
actionLabel="新增用户组"
|
||||
onCreate={openCreateDialog}
|
||||
@@ -546,10 +551,22 @@ type IdentityPanelProps = {
|
||||
onDeleteUserGroup: (groupId: string) => Promise<void>;
|
||||
onSaveTenant: (input: GatewayTenantUpsertRequest, tenantId?: string) => Promise<void>;
|
||||
onSaveUser: (input: GatewayUserUpsertRequest, userId?: string) => Promise<void>;
|
||||
onSetUserWalletBalance: (userId: string, input: WalletBalanceAdjustmentRequest) => Promise<void>;
|
||||
onRechargeUserWalletBalance: (userId: string, input: WalletRechargeRequest) => Promise<void>;
|
||||
onSaveUserGroup: (input: UserGroupUpsertRequest, groupId?: string) => Promise<void>;
|
||||
};
|
||||
|
||||
const defaultRechargeReason = '手动充值';
|
||||
|
||||
function identityHeaderMessage(props: Pick<IdentityPanelProps, 'operationMessage' | 'state'>) {
|
||||
return props.state === 'error' ? '' : props.operationMessage;
|
||||
}
|
||||
|
||||
function identityLocalErrorMessage(localError: string, props: Pick<IdentityPanelProps, 'operationMessage' | 'state'>) {
|
||||
if (!localError) return '';
|
||||
if (props.state === 'error' && localError === props.operationMessage) return '';
|
||||
return localError;
|
||||
}
|
||||
|
||||
function IdentityHeader(props: {
|
||||
actionLabel: string;
|
||||
count: number;
|
||||
@@ -621,7 +638,7 @@ function WalletDialogFooter(props: { loading: boolean; onCancel: () => void }) {
|
||||
return (
|
||||
<>
|
||||
<Button type="button" variant="outline" onClick={props.onCancel}><RotateCcw size={15} />取消</Button>
|
||||
<Button type="submit" disabled={props.loading}><CircleDollarSign size={15} />保存余额</Button>
|
||||
<Button type="submit" disabled={props.loading}><CircleDollarSign size={15} />确认充值</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -715,8 +732,8 @@ function defaultUserForm(tenant?: GatewayTenant): UserForm {
|
||||
function defaultWalletForm(): WalletForm {
|
||||
return {
|
||||
currency: 'resource',
|
||||
balance: '0',
|
||||
reason: '',
|
||||
amount: '',
|
||||
reason: defaultRechargeReason,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user