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

This commit is contained in:
wangbo 2026-05-12 03:43:00 +08:00
parent b609640a9f
commit d69aaed444
4 changed files with 47 additions and 21 deletions

View File

@ -187,6 +187,7 @@ export function App() {
const [auditLogs, setAuditLogs] = useState<GatewayAuditLog[]>([]);
const [rateLimitWindows, setRateLimitWindows] = useState<RateLimitWindow[]>([]);
const [modelRateLimits, setModelRateLimits] = useState<ModelRateLimitStatus[]>([]);
const [modelRateLimitsUpdatedAt, setModelRateLimitsUpdatedAt] = useState<number | null>(null);
const [tenants, setTenants] = useState<GatewayTenant[]>([]);
const [users, setUsers] = useState<GatewayUser[]>([]);
const [userGroups, setUserGroups] = useState<UserGroup[]>([]);
@ -249,6 +250,7 @@ export function App() {
void Promise.all([listModelRateLimitStatuses(token), listPlatforms(token)])
.then(([rateLimitResponse, platformResponse]) => {
setModelRateLimits(rateLimitResponse.items);
setModelRateLimitsUpdatedAt(Date.now());
setPlatforms(platformResponse.items);
loadedDataKeysRef.current.add('modelRateLimits');
loadedDataKeysRef.current.add('platforms');
@ -299,10 +301,11 @@ export function App() {
platforms,
pricingRules,
pricingRuleSets,
providers,
rateLimitWindows,
modelRateLimits,
runtimePolicySets,
providers,
rateLimitWindows,
modelRateLimits,
modelRateLimitsUpdatedAt,
runtimePolicySets,
taskResult,
tasks,
tenants,
@ -310,7 +313,7 @@ export function App() {
users,
walletAccounts,
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) {
await ensureRouteData(nextToken, true);
@ -415,7 +418,11 @@ export function App() {
setRateLimitWindows((await listRateLimitWindows(nextToken)).items);
return;
case 'modelRateLimits':
setModelRateLimits((await listModelRateLimitStatuses(nextToken)).items);
{
const response = await listModelRateLimitStatuses(nextToken);
setModelRateLimits(response.items);
setModelRateLimitsUpdatedAt(Date.now());
}
return;
case 'tenants':
setTenants((await listTenants(nextToken)).items);

View File

@ -37,6 +37,7 @@ export interface ConsoleData {
providers: CatalogProvider[];
rateLimitWindows: RateLimitWindow[];
modelRateLimits: ModelRateLimitStatus[];
modelRateLimitsUpdatedAt: number | null;
runtimePolicySets: RuntimePolicySet[];
taskResult: GatewayTask | null;
tasks: GatewayTask[];

View File

@ -137,9 +137,10 @@ export function AdminPage(props: {
<PlatformManagementPanel
baseModels={props.data.baseModels}
message={props.operationMessage}
networkProxyConfig={props.data.networkProxyConfig}
modelRateLimits={props.data.modelRateLimits}
platformModels={props.data.models}
networkProxyConfig={props.data.networkProxyConfig}
modelRateLimits={props.data.modelRateLimits}
modelRateLimitsUpdatedAt={props.data.modelRateLimitsUpdatedAt}
platformModels={props.data.models}
platforms={props.data.platforms}
pricingRuleSets={props.data.pricingRuleSets}
providers={props.data.providers}

View File

@ -22,9 +22,10 @@ import { ModelCatalogCard } from './ModelCatalogCard';
export function PlatformManagementPanel(props: {
baseModels: BaseModelCatalogItem[];
message: string;
modelRateLimits: ModelRateLimitStatus[];
networkProxyConfig: { globalHttpProxy?: string; globalHttpProxySet: boolean; globalHttpProxySource?: string } | null;
message: string;
modelRateLimits: ModelRateLimitStatus[];
modelRateLimitsUpdatedAt: number | null;
networkProxyConfig: { globalHttpProxy?: string; globalHttpProxySet: boolean; globalHttpProxySource?: string } | null;
platforms: IntegrationPlatform[];
platformModels: PlatformModel[];
pricingRuleSets: PricingRuleSet[];
@ -200,11 +201,16 @@ export function PlatformManagementPanel(props: {
providerMap={providerMap}
onModelQueryChange={setModelQuery}
now={now}
onPlatformChange={setSelectedPlatformId}
/>
) : (
<RateLimitStatusTable statuses={props.modelRateLimits} platformMap={platformMap} now={now} />
)}
onPlatformChange={setSelectedPlatformId}
/>
) : (
<RateLimitStatusTable
now={now}
platformMap={platformMap}
statuses={props.modelRateLimits}
updatedAt={props.modelRateLimitsUpdatedAt}
/>
)}
{props.message && <p className="formMessage">{props.message}</p>}
</CardContent>
</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) {
return <EmptyState title="暂无限流状态" description="模型产生请求后会在这里显示实时 RPM、TPM 和并发窗口。" />;
}
@ -580,7 +586,7 @@ function RateLimitStatusTable(props: { statuses: ModelRateLimitStatus[]; platfor
<section className="platformLimitView">
<div className="platformLimitHeader">
<span><Gauge size={15} /></span>
<small> 3 TPM + </small>
<small> 3 TPM + {formatTimeOfDay(props.updatedAt)}</small>
</div>
<div className="platformLimitTableViewport">
<Table className="platformDataTable platformLimitTable">
@ -1195,15 +1201,26 @@ function rateLimitMetricText(metric: string) {
}
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 (
<span className="rateMetricCell">
<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>
);
}
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) {
const modelCooldownMs = cooldownRemainingMs(status.modelCooldownUntil, now);
const platformCooldownMs = cooldownRemainingMs(status.platformCooldownUntil, now);