394 lines
12 KiB
JavaScript
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",
|
|
],
|
|
});
|