mirror of
https://github.com/woodchen-ink/nezha-dash-v1.git
synced 2025-07-19 01:51:54 +08:00
commit
5394750aa7
@ -90,21 +90,30 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const defaultChart = "All"
|
|
||||||
|
|
||||||
const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined
|
const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined
|
||||||
|
|
||||||
const forcePeakCutEnabled = (window.ForcePeakCutEnabled as boolean) ?? false
|
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 [isPeakEnabled, setIsPeakEnabled] = React.useState(forcePeakCutEnabled)
|
||||||
|
|
||||||
const handleButtonClick = useCallback(
|
// Function to clear all selected charts
|
||||||
(chart: string) => {
|
const clearAllSelections = useCallback(() => {
|
||||||
setActiveChart((prev) => (prev === chart ? defaultChart : chart))
|
setActiveCharts([])
|
||||||
},
|
}, [])
|
||||||
[defaultChart],
|
|
||||||
)
|
// 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(
|
const getColorByIndex = useCallback(
|
||||||
(chart: string) => {
|
(chart: string) => {
|
||||||
@ -119,7 +128,7 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
|||||||
chartDataKey.map((key) => (
|
chartDataKey.map((key) => (
|
||||||
<button
|
<button
|
||||||
key={key}
|
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`}
|
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)}
|
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>
|
<span className="text-md font-bold leading-none sm:text-lg">{chartData[key][chartData[key].length - 1].avg_delay.toFixed(2)}ms</span>
|
||||||
</button>
|
</button>
|
||||||
)),
|
)),
|
||||||
[chartDataKey, activeChart, chartData, handleButtonClick],
|
[chartDataKey, activeCharts, chartData, handleButtonClick],
|
||||||
)
|
)
|
||||||
|
|
||||||
const chartLines = useMemo(() => {
|
const chartLines = useMemo(() => {
|
||||||
if (activeChart !== defaultChart) {
|
// If we have active charts selected, render only those
|
||||||
return <Line isAnimationActive={false} strokeWidth={1} type="linear" dot={false} dataKey="avg_delay" stroke={getColorByIndex(activeChart)} />
|
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) => (
|
return chartDataKey.map((key) => (
|
||||||
<Line
|
<Line
|
||||||
key={key}
|
key={key}
|
||||||
@ -146,14 +169,16 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
|||||||
connectNulls={true}
|
connectNulls={true}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}, [activeChart, defaultChart, chartDataKey, getColorByIndex])
|
}, [activeCharts, chartDataKey, getColorByIndex])
|
||||||
|
|
||||||
const processedData = useMemo(() => {
|
const processedData = useMemo(() => {
|
||||||
if (!isPeakEnabled) {
|
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 windowSize = 11 // 增加窗口大小以获取更好的统计效果
|
||||||
const alpha = 0.3 // EWMA平滑因子
|
const alpha = 0.3 // EWMA平滑因子
|
||||||
@ -200,14 +225,16 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
|||||||
const window = data.slice(index - windowSize + 1, index + 1)
|
const window = data.slice(index - windowSize + 1, index + 1)
|
||||||
const smoothed = { ...point } as ResultItem
|
const smoothed = { ...point } as ResultItem
|
||||||
|
|
||||||
if (activeChart === defaultChart) {
|
// Process all chart keys or just the selected ones
|
||||||
chartDataKey.forEach((key) => {
|
const keysToProcess = activeCharts.length > 0 ? activeCharts : chartDataKey
|
||||||
|
|
||||||
|
keysToProcess.forEach((key) => {
|
||||||
const values = window.map((w) => w[key]).filter((v) => v !== undefined && v !== null) as number[]
|
const values = window.map((w) => w[key]).filter((v) => v !== undefined && v !== null) as number[]
|
||||||
|
|
||||||
if (values.length > 0) {
|
if (values.length > 0) {
|
||||||
const processed = processValues(values)
|
const processed = processValues(values)
|
||||||
if (processed !== null) {
|
if (processed !== null) {
|
||||||
// 应用EWMA平滑
|
// Apply EWMA smoothing
|
||||||
if (ewmaHistory[key] === undefined) {
|
if (ewmaHistory[key] === undefined) {
|
||||||
ewmaHistory[key] = processed
|
ewmaHistory[key] = processed
|
||||||
} else {
|
} else {
|
||||||
@ -217,26 +244,10 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
const values = window.map((w) => w.avg_delay).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
|
|
||||||
} else {
|
|
||||||
ewmaHistory["current"] = alpha * processed + (1 - alpha) * ewmaHistory["current"]
|
|
||||||
}
|
|
||||||
smoothed.avg_delay = ewmaHistory["current"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return smoothed
|
return smoothed
|
||||||
})
|
})
|
||||||
}, [isPeakEnabled, activeChart, formattedData, chartData, chartDataKey, defaultChart])
|
}, [isPeakEnabled, activeCharts, formattedData, chartDataKey])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
@ -260,6 +271,15 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
|||||||
<div className="flex flex-wrap w-full">{chartButtons}</div>
|
<div className="flex flex-wrap w-full">{chartButtons}</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pr-2 pl-0 py-4 sm:pt-6 sm:pb-6 sm:pr-6 sm:pl-2">
|
<CardContent className="pr-2 pl-0 py-4 sm:pt-6 sm:pb-6 sm:pr-6 sm:pl-2">
|
||||||
|
<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">
|
<ChartContainer config={chartConfig} className="aspect-auto h-[250px] w-full">
|
||||||
<LineChart accessibilityLayer data={processedData} margin={{ left: 12, right: 12 }}>
|
<LineChart accessibilityLayer data={processedData} margin={{ left: 12, right: 12 }}>
|
||||||
<CartesianGrid vertical={false} />
|
<CartesianGrid vertical={false} />
|
||||||
@ -309,10 +329,11 @@ export const NetworkChartClient = React.memo(function NetworkChart({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{activeChart === defaultChart && <ChartLegend content={<ChartLegendContent />} />}
|
<ChartLegend content={<ChartLegendContent />} />
|
||||||
{chartLines}
|
{chartLines}
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { createContext } from "react"
|
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"
|
export type SortOrder = "asc" | "desc"
|
||||||
|
|
||||||
|
@ -190,10 +190,10 @@ export default function Servers() {
|
|||||||
comparison = (a.state?.cpu ?? 0) - (b.state?.cpu ?? 0)
|
comparison = (a.state?.cpu ?? 0) - (b.state?.cpu ?? 0)
|
||||||
break
|
break
|
||||||
case "mem":
|
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
|
break
|
||||||
case "stg":
|
case "disk":
|
||||||
comparison = (a.state?.disk_used ?? 0) - (b.state?.disk_used ?? 0)
|
comparison = (formatNezhaInfo(nezhaWsData.now, a).disk ?? 0) - (formatNezhaInfo(nezhaWsData.now, b).disk ?? 0)
|
||||||
break
|
break
|
||||||
case "up":
|
case "up":
|
||||||
comparison = (a.state?.net_out_speed ?? 0) - (b.state?.net_out_speed ?? 0)
|
comparison = (a.state?.net_out_speed ?? 0) - (b.state?.net_out_speed ?? 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user