mirror of
https://github.com/woodchen-ink/nezha-dash-v1.git
synced 2025-07-18 01:21:56 +08:00
commit
3c6e8c1730
@ -90,21 +90,30 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const defaultChart = "All"
|
||||
|
||||
const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined
|
||||
|
||||
const forcePeakCutEnabled = (window.ForcePeakCutEnabled as boolean) ?? false
|
||||
|
||||
const [activeChart, setActiveChart] = React.useState(defaultChart)
|
||||
// Change from string to string array for multi-selection
|
||||
const [activeCharts, setActiveCharts] = React.useState<string[]>([])
|
||||
const [isPeakEnabled, setIsPeakEnabled] = React.useState(forcePeakCutEnabled)
|
||||
|
||||
const handleButtonClick = useCallback(
|
||||
(chart: string) => {
|
||||
setActiveChart((prev) => (prev === chart ? defaultChart : chart))
|
||||
},
|
||||
[defaultChart],
|
||||
)
|
||||
// Function to clear all selected charts
|
||||
const clearAllSelections = useCallback(() => {
|
||||
setActiveCharts([])
|
||||
}, [])
|
||||
|
||||
// Updated to handle multiple selections
|
||||
const handleButtonClick = useCallback((chart: string) => {
|
||||
setActiveCharts((prev) => {
|
||||
// If chart is already selected, remove it
|
||||
if (prev.includes(chart)) {
|
||||
return prev.filter((c) => c !== chart)
|
||||
}
|
||||
// Otherwise, add it to selected charts
|
||||
return [...prev, chart]
|
||||
})
|
||||
}, [])
|
||||
|
||||
const getColorByIndex = useCallback(
|
||||
(chart: string) => {
|
||||
@ -119,7 +128,7 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
||||
chartDataKey.map((key) => (
|
||||
<button
|
||||
key={key}
|
||||
data-active={activeChart === key}
|
||||
data-active={activeCharts.includes(key)}
|
||||
className={`relative z-30 flex cursor-pointer grow basis-0 flex-col justify-center gap-1 border-b border-neutral-200 dark:border-neutral-800 px-6 py-4 text-left data-[active=true]:bg-muted/50 sm:border-l sm:border-t-0 sm:px-6`}
|
||||
onClick={() => handleButtonClick(key)}
|
||||
>
|
||||
@ -127,13 +136,27 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
||||
<span className="text-md font-bold leading-none sm:text-lg">{chartData[key][chartData[key].length - 1].avg_delay.toFixed(2)}ms</span>
|
||||
</button>
|
||||
)),
|
||||
[chartDataKey, activeChart, chartData, handleButtonClick],
|
||||
[chartDataKey, activeCharts, chartData, handleButtonClick],
|
||||
)
|
||||
|
||||
const chartLines = useMemo(() => {
|
||||
if (activeChart !== defaultChart) {
|
||||
return <Line isAnimationActive={false} strokeWidth={1} type="linear" dot={false} dataKey="avg_delay" stroke={getColorByIndex(activeChart)} />
|
||||
// If we have active charts selected, render only those
|
||||
if (activeCharts.length > 0) {
|
||||
return activeCharts.map((chart) => (
|
||||
<Line
|
||||
key={chart}
|
||||
isAnimationActive={false}
|
||||
strokeWidth={1}
|
||||
type="linear"
|
||||
dot={false}
|
||||
dataKey={chart} // Change from "avg_delay" to the actual chart key name
|
||||
stroke={getColorByIndex(chart)}
|
||||
name={chart}
|
||||
connectNulls={true}
|
||||
/>
|
||||
))
|
||||
}
|
||||
// Otherwise show all charts (default view)
|
||||
return chartDataKey.map((key) => (
|
||||
<Line
|
||||
key={key}
|
||||
@ -146,14 +169,16 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
||||
connectNulls={true}
|
||||
/>
|
||||
))
|
||||
}, [activeChart, defaultChart, chartDataKey, getColorByIndex])
|
||||
}, [activeCharts, chartDataKey, getColorByIndex])
|
||||
|
||||
const processedData = useMemo(() => {
|
||||
if (!isPeakEnabled) {
|
||||
return activeChart === defaultChart ? formattedData : chartData[activeChart]
|
||||
// Always use formattedData when multiple charts are selected or none selected
|
||||
return formattedData
|
||||
}
|
||||
|
||||
const data = (activeChart === defaultChart ? formattedData : chartData[activeChart]) as ResultItem[]
|
||||
// For peak cutting, always use the formatted data which contains all series
|
||||
const data = formattedData
|
||||
|
||||
const windowSize = 11 // 增加窗口大小以获取更好的统计效果
|
||||
const alpha = 0.3 // EWMA平滑因子
|
||||
@ -200,43 +225,29 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
||||
const window = data.slice(index - windowSize + 1, index + 1)
|
||||
const smoothed = { ...point } as ResultItem
|
||||
|
||||
if (activeChart === defaultChart) {
|
||||
chartDataKey.forEach((key) => {
|
||||
const values = window.map((w) => w[key]).filter((v) => v !== undefined && v !== null) as number[]
|
||||
// Process all chart keys or just the selected ones
|
||||
const keysToProcess = activeCharts.length > 0 ? activeCharts : chartDataKey
|
||||
|
||||
if (values.length > 0) {
|
||||
const processed = processValues(values)
|
||||
if (processed !== null) {
|
||||
// 应用EWMA平滑
|
||||
if (ewmaHistory[key] === undefined) {
|
||||
ewmaHistory[key] = processed
|
||||
} else {
|
||||
ewmaHistory[key] = alpha * processed + (1 - alpha) * ewmaHistory[key]
|
||||
}
|
||||
smoothed[key] = ewmaHistory[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const values = window.map((w) => w.avg_delay).filter((v) => v !== undefined && v !== null) as number[]
|
||||
keysToProcess.forEach((key) => {
|
||||
const values = window.map((w) => w[key]).filter((v) => v !== undefined && v !== null) as number[]
|
||||
|
||||
if (values.length > 0) {
|
||||
const processed = processValues(values)
|
||||
if (processed !== null) {
|
||||
// 应用EWMA平滑
|
||||
if (ewmaHistory["current"] === undefined) {
|
||||
ewmaHistory["current"] = processed
|
||||
// Apply EWMA smoothing
|
||||
if (ewmaHistory[key] === undefined) {
|
||||
ewmaHistory[key] = processed
|
||||
} else {
|
||||
ewmaHistory["current"] = alpha * processed + (1 - alpha) * ewmaHistory["current"]
|
||||
ewmaHistory[key] = alpha * processed + (1 - alpha) * ewmaHistory[key]
|
||||
}
|
||||
smoothed.avg_delay = ewmaHistory["current"]
|
||||
smoothed[key] = ewmaHistory[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return smoothed
|
||||
})
|
||||
}, [isPeakEnabled, activeChart, formattedData, chartData, chartDataKey, defaultChart])
|
||||
}, [isPeakEnabled, activeCharts, formattedData, chartDataKey])
|
||||
|
||||
return (
|
||||
<Card
|
||||
@ -260,59 +271,69 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
||||
<div className="flex flex-wrap w-full">{chartButtons}</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pr-2 pl-0 py-4 sm:pt-6 sm:pb-6 sm:pr-6 sm:pl-2">
|
||||
<ChartContainer config={chartConfig} className="aspect-auto h-[250px] w-full">
|
||||
<LineChart accessibilityLayer data={processedData} margin={{ left: 12, right: 12 }}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="created_at"
|
||||
tickLine={true}
|
||||
tickSize={3}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
minTickGap={80}
|
||||
ticks={processedData
|
||||
.filter((item, index, array) => {
|
||||
if (array.length < 6) {
|
||||
return index === 0 || index === array.length - 1
|
||||
}
|
||||
<div className="relative">
|
||||
{activeCharts.length > 0 && (
|
||||
<button
|
||||
className="absolute -top-2 right-1 z-10 text-xs px-2 py-1 bg-stone-100/80 dark:bg-stone-800/80 backdrop-blur-sm rounded-[5px] text-muted-foreground hover:text-foreground transition-colors"
|
||||
onClick={clearAllSelections}
|
||||
>
|
||||
{t("monitor.clearSelections", "Clear")} ({activeCharts.length})
|
||||
</button>
|
||||
)}
|
||||
<ChartContainer config={chartConfig} className="aspect-auto h-[250px] w-full">
|
||||
<LineChart accessibilityLayer data={processedData} margin={{ left: 12, right: 12 }}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="created_at"
|
||||
tickLine={true}
|
||||
tickSize={3}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
minTickGap={80}
|
||||
ticks={processedData
|
||||
.filter((item, index, array) => {
|
||||
if (array.length < 6) {
|
||||
return index === 0 || index === array.length - 1
|
||||
}
|
||||
|
||||
// 计算数据的总时间跨度(毫秒)
|
||||
const timeSpan = array[array.length - 1].created_at - array[0].created_at
|
||||
const hours = timeSpan / (1000 * 60 * 60)
|
||||
// 计算数据的总时间跨度(毫秒)
|
||||
const timeSpan = array[array.length - 1].created_at - array[0].created_at
|
||||
const hours = timeSpan / (1000 * 60 * 60)
|
||||
|
||||
// 根据时间跨度调整显示间隔
|
||||
if (hours <= 12) {
|
||||
// 12小时内,每60分钟显示一个刻度
|
||||
return index === 0 || index === array.length - 1 || new Date(item.created_at).getMinutes() % 60 === 0
|
||||
}
|
||||
// 超过12小时,每2小时显示一个刻度
|
||||
const date = new Date(item.created_at)
|
||||
return date.getMinutes() === 0 && date.getHours() % 2 === 0
|
||||
})
|
||||
.map((item) => item.created_at)}
|
||||
tickFormatter={(value) => {
|
||||
const date = new Date(value)
|
||||
const minutes = date.getMinutes()
|
||||
return minutes === 0 ? `${date.getHours()}:00` : `${date.getHours()}:${minutes}`
|
||||
}}
|
||||
/>
|
||||
<YAxis tickLine={false} axisLine={false} tickMargin={15} minTickGap={20} tickFormatter={(value) => `${value}ms`} />
|
||||
<ChartTooltip
|
||||
isAnimationActive={false}
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
indicator={"line"}
|
||||
labelKey="created_at"
|
||||
labelFormatter={(_, payload) => {
|
||||
return formatTime(payload[0].payload.created_at)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{activeChart === defaultChart && <ChartLegend content={<ChartLegendContent />} />}
|
||||
{chartLines}
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
// 根据时间跨度调整显示间隔
|
||||
if (hours <= 12) {
|
||||
// 12小时内,每60分钟显示一个刻度
|
||||
return index === 0 || index === array.length - 1 || new Date(item.created_at).getMinutes() % 60 === 0
|
||||
}
|
||||
// 超过12小时,每2小时显示一个刻度
|
||||
const date = new Date(item.created_at)
|
||||
return date.getMinutes() === 0 && date.getHours() % 2 === 0
|
||||
})
|
||||
.map((item) => item.created_at)}
|
||||
tickFormatter={(value) => {
|
||||
const date = new Date(value)
|
||||
const minutes = date.getMinutes()
|
||||
return minutes === 0 ? `${date.getHours()}:00` : `${date.getHours()}:${minutes}`
|
||||
}}
|
||||
/>
|
||||
<YAxis tickLine={false} axisLine={false} tickMargin={15} minTickGap={20} tickFormatter={(value) => `${value}ms`} />
|
||||
<ChartTooltip
|
||||
isAnimationActive={false}
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
indicator={"line"}
|
||||
labelKey="created_at"
|
||||
labelFormatter={(_, payload) => {
|
||||
return formatTime(payload[0].payload.created_at)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
{chartLines}
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { createContext } from "react"
|
||||
|
||||
export type SortType = "default" | "name" | "uptime" | "system" | "cpu" | "mem" | "stg" | "up" | "down" | "up total" | "down total"
|
||||
export type SortType = "default" | "name" | "uptime" | "system" | "cpu" | "mem" | "disk" | "up" | "down" | "up total" | "down total"
|
||||
|
||||
export const SORT_TYPES: SortType[] = ["default", "name", "uptime", "system", "cpu", "mem", "stg", "up", "down", "up total", "down total"]
|
||||
export const SORT_TYPES: SortType[] = ["default", "name", "uptime", "system", "cpu", "mem", "disk", "up", "down", "up total", "down total"]
|
||||
|
||||
export type SortOrder = "asc" | "desc"
|
||||
|
||||
|
@ -190,10 +190,10 @@ export default function Servers() {
|
||||
comparison = (a.state?.cpu ?? 0) - (b.state?.cpu ?? 0)
|
||||
break
|
||||
case "mem":
|
||||
comparison = (a.state?.mem_used ?? 0) - (b.state?.mem_used ?? 0)
|
||||
comparison = (formatNezhaInfo(nezhaWsData.now, a).mem ?? 0) - (formatNezhaInfo(nezhaWsData.now, b).mem ?? 0)
|
||||
break
|
||||
case "stg":
|
||||
comparison = (a.state?.disk_used ?? 0) - (b.state?.disk_used ?? 0)
|
||||
case "disk":
|
||||
comparison = (formatNezhaInfo(nezhaWsData.now, a).disk ?? 0) - (formatNezhaInfo(nezhaWsData.now, b).disk ?? 0)
|
||||
break
|
||||
case "up":
|
||||
comparison = (a.state?.net_out_speed ?? 0) - (b.state?.net_out_speed ?? 0)
|
||||
|
Loading…
x
Reference in New Issue
Block a user