Files
SingBox-Gopanel/frontend/admin/src-reverse/pages/DashboardOverviewPage.js
CN-JS-HuiBai 25fd919477
All checks were successful
build / build (api, amd64, linux) (push) Successful in -43s
build / build (api, arm64, linux) (push) Successful in -44s
build / build (api.exe, amd64, windows) (push) Successful in -43s
API功能性修复
2026-04-17 15:13:43 +08:00

394 lines
12 KiB
JavaScript

import { makeRecoveredPage } from "../runtime/makeRecoveredPage.js";
export default makeRecoveredPage({
title: "Dashboard Overview",
routePath: "/",
moduleId: "tGt",
featureKey: "dashboard.overview",
translationNamespaces: ["dashboard", "common", "order"],
summary:
"The dashboard route is composed of four live sections: summary statistic cards, order/commission trend analytics, traffic rankings, and queue health details. All sections are hydrated from polling queries rather than static config.",
api: [
{
name: "stat.getStats",
method: "GET",
path: "{secure}/stat/getStats",
queryKey: ["dashboardStats"],
pollIntervalMs: 300000,
purpose: "Top-level KPI cards shown at the top of the dashboard.",
responseShape: {
todayIncome: "number",
dayIncomeGrowth: "number",
currentMonthIncome: "number",
monthIncomeGrowth: "number",
ticketPendingTotal: "number",
commissionPendingTotal: "number",
currentMonthNewUsers: "number",
userGrowth: "number",
totalUsers: "number",
activeUsers: "number",
monthTraffic: {
upload: "number",
download: "number",
},
todayTraffic: {
upload: "number",
download: "number",
},
},
},
{
name: "stat.getOrder",
method: "GET",
path: "{secure}/stat/getOrder",
queryKey: ["orderStat", "{start_date,end_date}"],
pollIntervalMs: 30000,
purpose: "Income and commission summary plus chart series for the overview analytics card.",
params: {
start_date: "yyyy-MM-dd",
end_date: "yyyy-MM-dd",
},
responseShape: {
summary: {
start_date: "string",
end_date: "string",
paid_total: "number",
paid_count: "number",
avg_paid_amount: "number",
commission_total: "number",
commission_count: "number",
commission_rate: "number",
},
list: [
{
date: "date-like string",
paid_total: "number",
commission_total: "number",
paid_count: "number",
commission_count: "number",
},
],
},
},
{
name: "stat.getTrafficRank",
method: "GET",
path: "{secure}/stat/getTrafficRank",
queryKey: ["nodeTrafficRank", "{start}", "{end}"] ,
pollIntervalMs: 30000,
purpose: "Node and user traffic ranking lists with current value, previous value, and delta.",
params: {
type: '"node" | "user"',
start_time: "unix seconds",
end_time: "unix seconds",
},
responseShape: [
{
id: "string | number",
name: "string",
value: "number",
previousValue: "number",
change: "number",
},
],
notes: [
"The compiled page tolerates both raw arrays and { data: [...] } payloads via a normalizer.",
"Node and user rankings maintain separate time-range states and separate queries.",
],
},
{
name: "config.getQueueStats",
method: "GET",
path: "{secure}/system/getQueueStats",
queryKey: ["queueStats", "{refreshToken}"],
pollIntervalMs: 30000,
purpose: "Queue health panel, throughput metrics, failed job count, and active process summary.",
responseShape: {
status: "boolean",
wait: {
default: "number",
},
recentJobs: "number",
jobsPerMinute: "number",
failedJobs: "number",
processes: "number",
pausedMasters: "number",
periods: {
recentJobs: "number",
failedJobs: "number",
},
queueWithMaxThroughput: {
throughput: "number",
},
queueWithMaxRuntime: {
runtime: "number",
name: "string",
},
},
},
{
name: "config.getHorizonFailedJobs",
method: "GET",
path: "{secure}/system/getHorizonFailedJobs",
queryKey: ["failedJobs", "{current}", "{page_size}"],
enabledWhen: "Only fetched after the failed jobs dialog is opened.",
purpose: "Paginated failed-job table and detail modal.",
params: {
current: "1-based page number",
page_size: "number",
},
responseShape: {
data: [
{
id: "string",
failed_at: "timestamp | string",
queue: "string",
connection: "string",
name: "string",
exception: "string",
payload: "stringified JSON or plain string",
},
],
total: "number",
},
},
],
stateModel: {
statsCards: {
queryKey: ["dashboardStats"],
loadingState: "Eight skeleton cards arranged as a 2x4 responsive grid.",
},
orderOverview: {
metricMode: {
default: "amount",
options: ["amount", "count"],
},
rangePreset: {
default: "30d",
options: ["7d", "30d", "90d", "180d", "365d", "custom"],
},
customRange: {
defaultFrom: "today - 7 days",
defaultTo: "today",
},
},
trafficRank: {
nodeRange: {
default: "today",
options: ["today", "last7days", "last30days", "custom"],
},
userRange: {
default: "today",
options: ["today", "last7days", "last30days", "custom"],
},
nodeCustomRange: {
defaultFrom: "today - 7 days",
defaultTo: "today",
},
userCustomRange: {
defaultFrom: "today - 7 days",
defaultTo: "today",
},
},
queue: {
refreshToken: "Manual refresh increments a token in the query key to force refetch.",
failedJobsDialog: {
open: false,
page: 1,
pageSize: 10,
},
selectedFailedJob: null,
jobDetailDialogOpen: false,
},
},
sections: [
{
key: "stats-cards",
titleKey: "dashboard:title",
componentHint: "x$t",
layout: "Responsive 2/4-column card grid.",
cards: [
{
key: "todayIncome",
titleKey: "dashboard:stats.todayIncome",
valueField: "todayIncome",
formatter: "currency via DS",
trendField: "dayIncomeGrowth",
trendLabelKey: "dashboard:stats.vsYesterday",
},
{
key: "currentMonthIncome",
titleKey: "dashboard:stats.monthlyIncome",
valueField: "currentMonthIncome",
formatter: "currency via DS",
trendField: "monthIncomeGrowth",
trendLabelKey: "dashboard:stats.vsLastMonth",
},
{
key: "ticketPendingTotal",
titleKey: "dashboard:stats.pendingTickets",
valueField: "ticketPendingTotal",
descriptionKeys: [
"dashboard:stats.hasPendingTickets",
"dashboard:stats.noPendingTickets",
],
highlightWhen: "ticketPendingTotal > 0",
clickAction: "navigate:/user/ticket",
},
{
key: "commissionPendingTotal",
titleKey: "dashboard:stats.pendingCommission",
valueField: "commissionPendingTotal",
descriptionKeys: [
"dashboard:stats.hasPendingCommission",
"dashboard:stats.noPendingCommission",
],
highlightWhen: "commissionPendingTotal > 0",
clickAction:
"navigate:/finance/order?commission_status=0&status=3&commission_balance=gt:0",
},
{
key: "currentMonthNewUsers",
titleKey: "dashboard:stats.monthlyNewUsers",
valueField: "currentMonthNewUsers",
trendField: "userGrowth",
trendLabelKey: "dashboard:stats.vsLastMonth",
},
{
key: "totalUsers",
titleKey: "dashboard:stats.totalUsers",
valueField: "totalUsers",
descriptionTemplateKey: "dashboard:stats.activeUsers",
descriptionField: "activeUsers",
},
{
key: "monthlyUpload",
titleKey: "dashboard:stats.monthlyUpload",
valueField: "monthTraffic.upload",
formatter: "traffic via IS",
descriptionTemplateKey: "dashboard:stats.todayTraffic",
descriptionField: "todayTraffic.upload",
},
{
key: "monthlyDownload",
titleKey: "dashboard:stats.monthlyDownload",
valueField: "monthTraffic.download",
formatter: "traffic via IS",
descriptionTemplateKey: "dashboard:stats.todayTraffic",
descriptionField: "todayTraffic.download",
},
],
},
{
key: "order-overview",
titleKey: "dashboard:overview.title",
componentHint: "t$t",
layout: "Summary header plus a 400px chart area.",
summaryFields: [
"summary.start_date",
"summary.end_date",
"summary.paid_total",
"summary.paid_count",
"summary.avg_paid_amount",
"summary.commission_total",
"summary.commission_count",
"summary.commission_rate",
],
seriesMapping: {
amountMode: ["paid_total", "commission_total"],
countMode: ["paid_count", "commission_count"],
},
chartBehavior: {
amountMode: "Dual area chart with gradient fills.",
countMode: "Dual bar chart with shared x-axis.",
xAxisField: "date",
},
},
{
key: "traffic-rank",
titleKey: "dashboard:trafficRank.nodeTrafficRank / dashboard:trafficRank.userTrafficRank",
componentHint: "dqt",
layout: "Two side-by-side cards on desktop, each using a scroll area with tooltip details.",
lists: [
{
key: "node",
requestType: "node",
maxBarWidthSource: "highest current value in the current result set",
},
{
key: "user",
requestType: "user",
maxBarWidthSource: "highest current value in the current result set",
},
],
itemFields: ["id", "name", "value", "previousValue", "change"],
tooltipFields: [
"dashboard:trafficRank.currentTraffic",
"dashboard:trafficRank.previousTraffic",
"dashboard:trafficRank.changeRate",
],
},
{
key: "queue-status",
titleKey: "dashboard:queue.title",
componentHint: "eGt",
layout: "Two cards in a responsive 2-column grid plus two dialogs.",
cards: [
"running-status",
"recent-jobs",
"jobs-per-minute",
"failed-jobs-7-days",
"longest-running-queue",
"active-processes",
],
tableColumns: ["failed_at", "queue", "name", "exception", "actions"],
detailDialogFields: [
"id",
"failed_at",
"queue",
"connection",
"name",
"exception",
"payload",
],
},
],
interactions: [
"Pending ticket KPI navigates directly to /user/ticket.",
"Pending commission KPI navigates to /finance/order with commission_status=0, status=3, commission_balance=gt:0.",
"Overview analytics supports preset date windows plus a custom date-range picker.",
"Traffic ranking supports independent node/user filters and treats custom ranges as inclusive by extending the end date by one day before unix conversion.",
"Queue card refresh button bumps a local refresh token and lets react-query refetch immediately.",
"Failed-job rows open a dedicated detail dialog; payload is pretty-printed as JSON when possible.",
],
recoveredFields: {
orderStatusEnum: {
PENDING: 0,
PROCESSING: 1,
CANCELLED: 2,
COMPLETED: 3,
DISCOUNTED: 4,
},
commissionStatusEnum: {
PENDING: 0,
PROCESSING: 1,
VALID: 2,
INVALID: 3,
},
},
notes: [
"The route factory renders dashboard header controls before the four live sections.",
"The compiled bundle localizes labels through translation keys rather than hard-coded copy.",
"Traffic rank payloads are normalized through a helper that accepts either a bare array or an object containing data.",
],
sourceRefs: [
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:263492",
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:264031",
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:264772",
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:268829",
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:269427",
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:27302",
"frontend/admin/reverse/output/index-CO3BwsT2.pretty.js:27417",
],
});