feat(admin): 资源与策略 - 后台统一维护账号配额与运行时请求限制 (#37)
Fixes #35 Co-authored-by: ddCat <ddcat666@88.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Tooltip } from "antd";
|
||||
import { BarChart3, BellRing, Coins, FileClock, Gauge, HardDrive, Home, Mail, MessageSquareText, PanelLeftClose, PanelLeftOpen, RadioTower, ShieldCheck, TicketCheck, UsersRound } from "lucide-react";
|
||||
import { BarChart3, BellRing, Coins, FileClock, HardDrive, Home, Mail, MessageSquareText, PanelLeftClose, PanelLeftOpen, RadioTower, Settings2, ShieldCheck, TicketCheck, UsersRound } from "lucide-react";
|
||||
import { useState, type ReactNode } from "react";
|
||||
import { NavLink, Outlet } from "react-router";
|
||||
|
||||
@@ -40,7 +40,7 @@ const adminNavigation: Array<{ label: string; items: AdminNavigationItem[] }> =
|
||||
{
|
||||
label: "系统配置",
|
||||
items: [
|
||||
{ path: "/admin/settings/concurrency", label: "任务并发", description: "Worker 与渠道请求调度", icon: <Gauge className="size-4" />, secondary: true },
|
||||
{ path: "/admin/settings/runtime-policy", label: "资源与策略", description: "配额、并发、频控与超时", icon: <Settings2 className="size-4" />, secondary: true },
|
||||
{ path: "/admin/settings/access", label: "登录与注册", description: "注册策略与 Linux.do", icon: <ShieldCheck className="size-4" />, secondary: true },
|
||||
{ path: "/admin/settings/email", label: "邮件服务", description: "注册验证码 SMTP", icon: <Mail className="size-4" />, secondary: true },
|
||||
{ path: "/admin/settings/storage", label: "存储服务", description: "OSS 与资源存储", icon: <HardDrive className="size-4" />, secondary: true },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { App, Descriptions, Drawer, Empty, Skeleton, Table, Tabs, Tag } from "antd";
|
||||
import { App, Descriptions, Drawer, Empty, Progress, Skeleton, Table, Tabs, Tag } from "antd";
|
||||
|
||||
import { formatCredits } from "@/constant/credits";
|
||||
import { getAdminUserDetail, listAdminUserAuditEvents, listAdminUserLedger, listAdminUserTasks, type AdminAuditEvent, type AdminUserDetail, type AdminUserTask } from "@/services/api/auth";
|
||||
@@ -119,6 +119,20 @@ export function AdminUserDetailDrawer({ userId, onClose }: { userId: string | nu
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-3 text-sm font-medium">资源与配额占用</div>
|
||||
<div className="grid gap-x-6 gap-y-4 sm:grid-cols-2">
|
||||
{quotaUsageItems(detail).map((item) => (
|
||||
<div key={item.label}>
|
||||
<div className="mb-1 flex items-center justify-between gap-3 text-xs">
|
||||
<span className="text-foreground/60">{item.label}</span>
|
||||
<span className="shrink-0 tabular-nums text-foreground/75">{item.display}</span>
|
||||
</div>
|
||||
<Progress percent={Math.min(100, item.limit > 0 ? Math.round(item.value / item.limit * 100) : 0)} size="small" showInfo={false} status={item.value >= item.limit ? "exception" : "normal"} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -195,3 +209,20 @@ export function AdminUserDetailDrawer({ userId, onClose }: { userId: string | nu
|
||||
function formatTime(value?: string) {
|
||||
return value ? new Date(value).toLocaleString("zh-CN", { hour12: false }) : "--";
|
||||
}
|
||||
|
||||
function quotaUsageItems(detail: AdminUserDetail) {
|
||||
const structuredBytes = detail.storageUsage.assetBytes + detail.storageUsage.canvasBytes + detail.storageUsage.sessionBytes;
|
||||
const bytes = (value: number) => value >= 1024 ** 3 ? `${(value / 1024 ** 3).toFixed(2)} GB` : `${(value / 1024 ** 2).toFixed(1)} MB`;
|
||||
const number = (value: number) => new Intl.NumberFormat("zh-CN").format(value);
|
||||
return [
|
||||
{ label: "资源与附件", value: detail.storedFileBytes, limit: detail.quota.storedFileGB * 1024 ** 3, display: `${bytes(detail.storedFileBytes)} / ${detail.quota.storedFileGB} GB` },
|
||||
{ label: "今日上传(UTC)", value: detail.dailyUploadBytes, limit: detail.quota.dailyUploadMB * 1024 ** 2, display: `${bytes(detail.dailyUploadBytes)} / ${detail.quota.dailyUploadMB} MB` },
|
||||
{ label: "画布、素材与会话数据", value: structuredBytes, limit: detail.quota.structuredDataMB * 1024 ** 2, display: `${bytes(structuredBytes)} / ${detail.quota.structuredDataMB} MB` },
|
||||
{ label: "任务与请求日志数据", value: detail.storageUsage.taskBytes, limit: detail.quota.taskDataGB * 1024 ** 3, display: `${bytes(detail.storageUsage.taskBytes)} / ${detail.quota.taskDataGB} GB` },
|
||||
{ label: "素材数量", value: detail.storageUsage.assetCount, limit: detail.quota.assetCount, display: `${number(detail.storageUsage.assetCount)} / ${number(detail.quota.assetCount)}` },
|
||||
{ label: "画布数量", value: detail.storageUsage.canvasCount, limit: detail.quota.canvasCount, display: `${number(detail.storageUsage.canvasCount)} / ${number(detail.quota.canvasCount)}` },
|
||||
{ label: "Agent 会话数量", value: detail.storageUsage.sessionCount, limit: detail.quota.sessionCount, display: `${number(detail.storageUsage.sessionCount)} / ${number(detail.quota.sessionCount)}` },
|
||||
{ label: "任务历史数量", value: detail.storageUsage.taskCount, limit: detail.quota.taskCount, display: `${number(detail.storageUsage.taskCount)} / ${number(detail.quota.taskCount)}` },
|
||||
{ label: "上游请求日志数量", value: detail.storageUsage.apiCallCount, limit: detail.quota.apiCallLogCount, display: `${number(detail.storageUsage.apiCallCount)} / ${number(detail.quota.apiCallLogCount)}` },
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user