feat(web): refresh rate limits timestamp and tpm settlement display

This commit is contained in:
2026-05-12 03:43:00 +08:00
parent b609640a9f
commit d69aaed444
4 changed files with 47 additions and 21 deletions
+13 -6
View File
@@ -187,6 +187,7 @@ export function App() {
const [auditLogs, setAuditLogs] = useState<GatewayAuditLog[]>([]); const [auditLogs, setAuditLogs] = useState<GatewayAuditLog[]>([]);
const [rateLimitWindows, setRateLimitWindows] = useState<RateLimitWindow[]>([]); const [rateLimitWindows, setRateLimitWindows] = useState<RateLimitWindow[]>([]);
const [modelRateLimits, setModelRateLimits] = useState<ModelRateLimitStatus[]>([]); const [modelRateLimits, setModelRateLimits] = useState<ModelRateLimitStatus[]>([]);
const [modelRateLimitsUpdatedAt, setModelRateLimitsUpdatedAt] = useState<number | null>(null);
const [tenants, setTenants] = useState<GatewayTenant[]>([]); const [tenants, setTenants] = useState<GatewayTenant[]>([]);
const [users, setUsers] = useState<GatewayUser[]>([]); const [users, setUsers] = useState<GatewayUser[]>([]);
const [userGroups, setUserGroups] = useState<UserGroup[]>([]); const [userGroups, setUserGroups] = useState<UserGroup[]>([]);
@@ -249,6 +250,7 @@ export function App() {
void Promise.all([listModelRateLimitStatuses(token), listPlatforms(token)]) void Promise.all([listModelRateLimitStatuses(token), listPlatforms(token)])
.then(([rateLimitResponse, platformResponse]) => { .then(([rateLimitResponse, platformResponse]) => {
setModelRateLimits(rateLimitResponse.items); setModelRateLimits(rateLimitResponse.items);
setModelRateLimitsUpdatedAt(Date.now());
setPlatforms(platformResponse.items); setPlatforms(platformResponse.items);
loadedDataKeysRef.current.add('modelRateLimits'); loadedDataKeysRef.current.add('modelRateLimits');
loadedDataKeysRef.current.add('platforms'); loadedDataKeysRef.current.add('platforms');
@@ -299,10 +301,11 @@ export function App() {
platforms, platforms,
pricingRules, pricingRules,
pricingRuleSets, pricingRuleSets,
providers, providers,
rateLimitWindows, rateLimitWindows,
modelRateLimits, modelRateLimits,
runtimePolicySets, modelRateLimitsUpdatedAt,
runtimePolicySets,
taskResult, taskResult,
tasks, tasks,
tenants, tenants,
@@ -310,7 +313,7 @@ export function App() {
users, users,
walletAccounts, walletAccounts,
walletTransactions, walletTransactions,
}), [accessRules, apiKeys, auditLogs, baseModels, modelCatalog, modelRateLimits, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]); }), [accessRules, apiKeys, auditLogs, baseModels, modelCatalog, modelRateLimits, modelRateLimitsUpdatedAt, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
async function refresh(nextToken = token) { async function refresh(nextToken = token) {
await ensureRouteData(nextToken, true); await ensureRouteData(nextToken, true);
@@ -415,7 +418,11 @@ export function App() {
setRateLimitWindows((await listRateLimitWindows(nextToken)).items); setRateLimitWindows((await listRateLimitWindows(nextToken)).items);
return; return;
case 'modelRateLimits': case 'modelRateLimits':
setModelRateLimits((await listModelRateLimitStatuses(nextToken)).items); {
const response = await listModelRateLimitStatuses(nextToken);
setModelRateLimits(response.items);
setModelRateLimitsUpdatedAt(Date.now());
}
return; return;
case 'tenants': case 'tenants':
setTenants((await listTenants(nextToken)).items); setTenants((await listTenants(nextToken)).items);
+1
View File
@@ -37,6 +37,7 @@ export interface ConsoleData {
providers: CatalogProvider[]; providers: CatalogProvider[];
rateLimitWindows: RateLimitWindow[]; rateLimitWindows: RateLimitWindow[];
modelRateLimits: ModelRateLimitStatus[]; modelRateLimits: ModelRateLimitStatus[];
modelRateLimitsUpdatedAt: number | null;
runtimePolicySets: RuntimePolicySet[]; runtimePolicySets: RuntimePolicySet[];
taskResult: GatewayTask | null; taskResult: GatewayTask | null;
tasks: GatewayTask[]; tasks: GatewayTask[];
+4 -3
View File
@@ -137,9 +137,10 @@ export function AdminPage(props: {
<PlatformManagementPanel <PlatformManagementPanel
baseModels={props.data.baseModels} baseModels={props.data.baseModels}
message={props.operationMessage} message={props.operationMessage}
networkProxyConfig={props.data.networkProxyConfig} networkProxyConfig={props.data.networkProxyConfig}
modelRateLimits={props.data.modelRateLimits} modelRateLimits={props.data.modelRateLimits}
platformModels={props.data.models} modelRateLimitsUpdatedAt={props.data.modelRateLimitsUpdatedAt}
platformModels={props.data.models}
platforms={props.data.platforms} platforms={props.data.platforms}
pricingRuleSets={props.data.pricingRuleSets} pricingRuleSets={props.data.pricingRuleSets}
providers={props.data.providers} providers={props.data.providers}
@@ -22,9 +22,10 @@ import { ModelCatalogCard } from './ModelCatalogCard';
export function PlatformManagementPanel(props: { export function PlatformManagementPanel(props: {
baseModels: BaseModelCatalogItem[]; baseModels: BaseModelCatalogItem[];
message: string; message: string;
modelRateLimits: ModelRateLimitStatus[]; modelRateLimits: ModelRateLimitStatus[];
networkProxyConfig: { globalHttpProxy?: string; globalHttpProxySet: boolean; globalHttpProxySource?: string } | null; modelRateLimitsUpdatedAt: number | null;
networkProxyConfig: { globalHttpProxy?: string; globalHttpProxySet: boolean; globalHttpProxySource?: string } | null;
platforms: IntegrationPlatform[]; platforms: IntegrationPlatform[];
platformModels: PlatformModel[]; platformModels: PlatformModel[];
pricingRuleSets: PricingRuleSet[]; pricingRuleSets: PricingRuleSet[];
@@ -200,11 +201,16 @@ export function PlatformManagementPanel(props: {
providerMap={providerMap} providerMap={providerMap}
onModelQueryChange={setModelQuery} onModelQueryChange={setModelQuery}
now={now} now={now}
onPlatformChange={setSelectedPlatformId} onPlatformChange={setSelectedPlatformId}
/> />
) : ( ) : (
<RateLimitStatusTable statuses={props.modelRateLimits} platformMap={platformMap} now={now} /> <RateLimitStatusTable
)} now={now}
platformMap={platformMap}
statuses={props.modelRateLimits}
updatedAt={props.modelRateLimitsUpdatedAt}
/>
)}
{props.message && <p className="formMessage">{props.message}</p>} {props.message && <p className="formMessage">{props.message}</p>}
</CardContent> </CardContent>
</Card> </Card>
@@ -572,7 +578,7 @@ function PlatformModelTable(props: {
); );
} }
function RateLimitStatusTable(props: { statuses: ModelRateLimitStatus[]; platformMap: Map<string, IntegrationPlatform>; now: number }) { function RateLimitStatusTable(props: { statuses: ModelRateLimitStatus[]; platformMap: Map<string, IntegrationPlatform>; now: number; updatedAt: number | null }) {
if (!props.statuses.length) { if (!props.statuses.length) {
return <EmptyState title="暂无限流状态" description="模型产生请求后会在这里显示实时 RPM、TPM 和并发窗口。" />; return <EmptyState title="暂无限流状态" description="模型产生请求后会在这里显示实时 RPM、TPM 和并发窗口。" />;
} }
@@ -580,7 +586,7 @@ function RateLimitStatusTable(props: { statuses: ModelRateLimitStatus[]; platfor
<section className="platformLimitView"> <section className="platformLimitView">
<div className="platformLimitHeader"> <div className="platformLimitHeader">
<span><Gauge size={15} /></span> <span><Gauge size={15} /></span>
<small> 3 TPM + </small> <small> 3 TPM + {formatTimeOfDay(props.updatedAt)}</small>
</div> </div>
<div className="platformLimitTableViewport"> <div className="platformLimitTableViewport">
<Table className="platformDataTable platformLimitTable"> <Table className="platformDataTable platformLimitTable">
@@ -1195,15 +1201,26 @@ function rateLimitMetricText(metric: string) {
} }
function metricCell(metric: ModelRateLimitStatus['rpm'], includeReserved = false) { function metricCell(metric: ModelRateLimitStatus['rpm'], includeReserved = false) {
if (!metric.limited) return <span className="rateMetricCell"><strong>{formatLimit(metric.currentValue)} / </strong><small></small></span>; if (!metric.limited) return <span className="rateMetricCell"><strong>{formatLimit(metric.currentValue)} / </strong><small>{includeReserved ? reservedMetricText(metric) : '未配置上限'}</small></span>;
return ( return (
<span className="rateMetricCell"> <span className="rateMetricCell">
<strong>{formatLimit(metric.currentValue)} / {formatLimit(metric.limitValue)}</strong> <strong>{formatLimit(metric.currentValue)} / {formatLimit(metric.limitValue)}</strong>
<small>{includeReserved && metric.reservedValue > 0 ? `已用 ${formatLimit(metric.usedValue)} · 预占 ${formatLimit(metric.reservedValue)}` : `窗口 ${formatPercent(metric.ratio)}`}</small> <small>{includeReserved ? reservedMetricText(metric) : `窗口 ${formatPercent(metric.ratio)}`}</small>
</span> </span>
); );
} }
function reservedMetricText(metric: ModelRateLimitStatus['rpm']) {
return `已结算 ${formatLimit(metric.usedValue)} + 预占 ${formatLimit(metric.reservedValue)}`;
}
function formatTimeOfDay(timestamp: number | null) {
if (!timestamp) return '暂无';
const date = new Date(timestamp);
const pad = (value: number) => String(value).padStart(2, '0');
return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
}
function modelRuntimeStatusCell(status: ModelRateLimitStatus, now: number) { function modelRuntimeStatusCell(status: ModelRateLimitStatus, now: number) {
const modelCooldownMs = cooldownRemainingMs(status.modelCooldownUntil, now); const modelCooldownMs = cooldownRemainingMs(status.modelCooldownUntil, now);
const platformCooldownMs = cooldownRemainingMs(status.platformCooldownUntil, now); const platformCooldownMs = cooldownRemainingMs(status.platformCooldownUntil, now);