fix(api): 修复节点流量限额共享统计与父子显隐联动
统一节点流量统计与限额展示口径,节点详情新增昨日流量, 并让今日、昨日和本月使用清晰的半开时间窗口聚合 同 machine_id 或同 host 的节点现在共享当前账期已用流量, 管理端优先使用后端 traffic_limit_snapshot 展示月额度状态, mi-node 下发的 current_used 也改为共享账期统计 新增 parent_auto_hidden 标记与父节点显隐联动服务,父节点 因自动上线或流量限额变为不可展示时会隐藏当前显示的子节点, 恢复时只恢复这批自动隐藏的子节点,避免覆盖手动操作
This commit is contained in:
Vendored
+18
@@ -874,10 +874,26 @@ export interface AdminNodeMetrics {
|
||||
|
||||
export interface AdminNodeTrafficStats {
|
||||
today: TrafficAmount
|
||||
yesterday: TrafficAmount
|
||||
month: TrafficAmount
|
||||
total: TrafficAmount
|
||||
}
|
||||
|
||||
export interface AdminNodeTrafficLimitSnapshot {
|
||||
enabled: boolean
|
||||
limit: number
|
||||
used: number
|
||||
percent: number
|
||||
suspended: boolean
|
||||
last_reset_at?: number
|
||||
cycle_start_at?: number
|
||||
next_reset_at?: number
|
||||
suspended_at?: number
|
||||
status?: string
|
||||
scope_key?: string
|
||||
scope_node_ids?: number[]
|
||||
}
|
||||
|
||||
export interface AdminNodeRateTimeRange {
|
||||
start: string
|
||||
end: string
|
||||
@@ -909,6 +925,7 @@ export interface AdminNodeItem {
|
||||
traffic_limit_next_reset_at?: number | null
|
||||
traffic_limit_suspended_at?: number | null
|
||||
enabled?: boolean
|
||||
machine_id?: number | null
|
||||
parent_id?: number | null
|
||||
rate?: number | null
|
||||
transfer_enable?: number | null
|
||||
@@ -926,6 +943,7 @@ export interface AdminNodeItem {
|
||||
last_push_at?: number | null
|
||||
metrics?: AdminNodeMetrics | null
|
||||
traffic_stats?: AdminNodeTrafficStats | null
|
||||
traffic_limit_snapshot?: AdminNodeTrafficLimitSnapshot | null
|
||||
groups?: AdminServerGroupItem[]
|
||||
parent?: AdminNodeParentRef | null
|
||||
gfw_check?: AdminNodeGfwCheck | null
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface NodeGfwMeta {
|
||||
}
|
||||
|
||||
export interface NodeTrafficDetail {
|
||||
key: 'today' | 'month' | 'total'
|
||||
key: 'today' | 'yesterday' | 'month' | 'total'
|
||||
label: string
|
||||
upload: string
|
||||
download: string
|
||||
@@ -70,6 +70,15 @@ function normalizeTrafficValue(value: unknown): number {
|
||||
return Number.isFinite(normalized) && normalized > 0 ? normalized : 0
|
||||
}
|
||||
|
||||
function normalizeOptionalTrafficValue(value: unknown): number | null {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return null
|
||||
}
|
||||
|
||||
const normalized = Number(value)
|
||||
return Number.isFinite(normalized) && normalized >= 0 ? normalized : null
|
||||
}
|
||||
|
||||
function normalizeTrafficAmount(amount?: TrafficAmountLike | null): TrafficAmount {
|
||||
const upload = normalizeTrafficValue(amount?.upload)
|
||||
const download = normalizeTrafficValue(amount?.download)
|
||||
@@ -255,6 +264,7 @@ export function getNodeTrafficDetails(node: AdminNodeItem): NodeTrafficDetail[]
|
||||
|
||||
const rows: Array<{ key: NodeTrafficDetail['key']; label: string; source?: TrafficAmountLike | null }> = [
|
||||
{ key: 'today', label: '今日', source: stats?.today },
|
||||
{ key: 'yesterday', label: '昨日', source: stats?.yesterday },
|
||||
{ key: 'month', label: '本月', source: stats?.month },
|
||||
{ key: 'total', label: '累计', source: stats?.total ?? totalFallback },
|
||||
]
|
||||
@@ -272,16 +282,25 @@ export function getNodeTrafficDetails(node: AdminNodeItem): NodeTrafficDetail[]
|
||||
}
|
||||
|
||||
export function getNodeTrafficLimitDetail(node: AdminNodeItem): NodeTrafficLimitDetail {
|
||||
const limit = normalizeTrafficValue(node.transfer_enable)
|
||||
const snapshot = node.traffic_limit_snapshot
|
||||
const metrics = node.metrics?.traffic_limit
|
||||
const used = normalizeTrafficValue(metrics?.used) || normalizeTrafficValue(node.u) + normalizeTrafficValue(node.d)
|
||||
const suspended = Boolean(metrics?.suspended) || node.traffic_limit_status === 'suspended'
|
||||
const nextResetAt = normalizeTrafficValue(metrics?.next_reset_at) || normalizeTrafficValue(node.traffic_limit_next_reset_at)
|
||||
const limit = normalizeOptionalTrafficValue(snapshot?.limit)
|
||||
?? normalizeOptionalTrafficValue(metrics?.limit)
|
||||
?? normalizeTrafficValue(node.transfer_enable)
|
||||
const used = normalizeOptionalTrafficValue(snapshot?.used)
|
||||
?? normalizeOptionalTrafficValue(metrics?.used)
|
||||
?? normalizeTrafficValue(node.u) + normalizeTrafficValue(node.d)
|
||||
const status = normalizeText(snapshot?.status || metrics?.status || node.traffic_limit_status)
|
||||
const suspended = Boolean(snapshot?.suspended) || Boolean(metrics?.suspended) || status === 'suspended'
|
||||
const nextResetAt = normalizeOptionalTrafficValue(snapshot?.next_reset_at)
|
||||
?? normalizeOptionalTrafficValue(metrics?.next_reset_at)
|
||||
?? normalizeTrafficValue(node.traffic_limit_next_reset_at)
|
||||
const percent = limit > 0 ? Math.min(100, Math.round((used / limit) * 100)) : 0
|
||||
const enabled = (snapshot ? Boolean(snapshot.enabled) : Boolean(node.traffic_limit_enabled)) && limit > 0
|
||||
|
||||
let statusLabel = '未启用'
|
||||
let tagType: NodeTrafficLimitDetail['tagType'] = 'info'
|
||||
if (Boolean(node.traffic_limit_enabled) && limit > 0) {
|
||||
if (enabled) {
|
||||
if (suspended) {
|
||||
statusLabel = '已限额'
|
||||
tagType = 'danger'
|
||||
@@ -295,7 +314,7 @@ export function getNodeTrafficLimitDetail(node: AdminNodeItem): NodeTrafficLimit
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: Boolean(node.traffic_limit_enabled) && limit > 0,
|
||||
enabled,
|
||||
used: formatTrafficBytes(used),
|
||||
limit: formatTrafficBytes(limit),
|
||||
percent,
|
||||
|
||||
Reference in New Issue
Block a user