mirror of
https://github.com/hamster1963/nezha-dash.git
synced 2025-04-24 21:10:45 +08:00
Merge branch 'main' into cloudflare
This commit is contained in:
commit
ff76a1b02c
@ -17,6 +17,8 @@ import {
|
|||||||
ChartTooltip,
|
ChartTooltip,
|
||||||
ChartTooltipContent,
|
ChartTooltipContent,
|
||||||
} from "@/components/ui/chart";
|
} from "@/components/ui/chart";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import getEnv from "@/lib/env-entry";
|
import getEnv from "@/lib/env-entry";
|
||||||
import { formatTime, nezhaFetcher } from "@/lib/utils";
|
import { formatTime, nezhaFetcher } from "@/lib/utils";
|
||||||
import { formatRelativeTime } from "@/lib/utils";
|
import { formatRelativeTime } from "@/lib/utils";
|
||||||
@ -28,7 +30,7 @@ import useSWR from "swr";
|
|||||||
|
|
||||||
interface ResultItem {
|
interface ResultItem {
|
||||||
created_at: number;
|
created_at: number;
|
||||||
[key: string]: number | null;
|
[key: string]: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NetworkChartClient({
|
export function NetworkChartClient({
|
||||||
@ -106,6 +108,7 @@ export const NetworkChart = React.memo(function NetworkChart({
|
|||||||
const defaultChart = "All";
|
const defaultChart = "All";
|
||||||
|
|
||||||
const [activeChart, setActiveChart] = React.useState(defaultChart);
|
const [activeChart, setActiveChart] = React.useState(defaultChart);
|
||||||
|
const [isPeakEnabled, setIsPeakEnabled] = React.useState(false);
|
||||||
|
|
||||||
const handleButtonClick = useCallback(
|
const handleButtonClick = useCallback(
|
||||||
(chart: string) => {
|
(chart: string) => {
|
||||||
@ -169,6 +172,63 @@ export const NetworkChart = React.memo(function NetworkChart({
|
|||||||
));
|
));
|
||||||
}, [activeChart, defaultChart, chartDataKey, getColorByIndex]);
|
}, [activeChart, defaultChart, chartDataKey, getColorByIndex]);
|
||||||
|
|
||||||
|
const processedData = useMemo(() => {
|
||||||
|
if (!isPeakEnabled) {
|
||||||
|
return activeChart === defaultChart
|
||||||
|
? formattedData
|
||||||
|
: chartData[activeChart];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果开启了削峰,对数据进行处理
|
||||||
|
const data = (
|
||||||
|
activeChart === defaultChart ? formattedData : chartData[activeChart]
|
||||||
|
) as ResultItem[];
|
||||||
|
const windowSize = 7; // 增加到7个点的移动平均
|
||||||
|
const weights = [0.1, 0.1, 0.15, 0.3, 0.15, 0.1, 0.1]; // 加权平均的权重
|
||||||
|
|
||||||
|
return data.map((point, index) => {
|
||||||
|
if (index < windowSize - 1) return point;
|
||||||
|
|
||||||
|
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[];
|
||||||
|
if (values.length === windowSize) {
|
||||||
|
smoothed[key] = values.reduce(
|
||||||
|
(acc, val, idx) => acc + val * weights[idx],
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 处理单条线路的数据
|
||||||
|
const values = window
|
||||||
|
.map((w) => w.avg_delay)
|
||||||
|
.filter((v) => v !== undefined && v !== null) as number[];
|
||||||
|
if (values.length === windowSize) {
|
||||||
|
smoothed.avg_delay = values.reduce(
|
||||||
|
(acc, val, idx) => acc + val * weights[idx],
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return smoothed;
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
isPeakEnabled,
|
||||||
|
activeChart,
|
||||||
|
formattedData,
|
||||||
|
chartData,
|
||||||
|
chartDataKey,
|
||||||
|
defaultChart,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-col items-stretch space-y-0 p-0 sm:flex-row">
|
<CardHeader className="flex flex-col items-stretch space-y-0 p-0 sm:flex-row">
|
||||||
@ -179,6 +239,16 @@ export const NetworkChart = React.memo(function NetworkChart({
|
|||||||
<CardDescription className="text-xs">
|
<CardDescription className="text-xs">
|
||||||
{chartDataKey.length} {t("ServerMonitorCount")}
|
{chartDataKey.length} {t("ServerMonitorCount")}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
<div className="flex items-center mt-0.5 space-x-2">
|
||||||
|
<Switch
|
||||||
|
id="Peak"
|
||||||
|
checked={isPeakEnabled}
|
||||||
|
onCheckedChange={setIsPeakEnabled}
|
||||||
|
/>
|
||||||
|
<Label className="text-xs" htmlFor="Peak">
|
||||||
|
Peak cut
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap w-full">{chartButtons}</div>
|
<div className="flex flex-wrap w-full">{chartButtons}</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@ -189,11 +259,7 @@ export const NetworkChart = React.memo(function NetworkChart({
|
|||||||
>
|
>
|
||||||
<LineChart
|
<LineChart
|
||||||
accessibilityLayer
|
accessibilityLayer
|
||||||
data={
|
data={processedData}
|
||||||
activeChart === defaultChart
|
|
||||||
? formattedData
|
|
||||||
: chartData[activeChart]
|
|
||||||
}
|
|
||||||
margin={{ left: 12, right: 12 }}
|
margin={{ left: 12, right: 12 }}
|
||||||
>
|
>
|
||||||
<CartesianGrid vertical={false} />
|
<CartesianGrid vertical={false} />
|
||||||
@ -276,6 +342,7 @@ const formatData = (rawData: NezhaAPIMonitor[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const timeIndex = created_at.indexOf(time);
|
const timeIndex = created_at.indexOf(time);
|
||||||
|
// @ts-expect-error - avg_delay is an array
|
||||||
result[time][monitor_name] =
|
result[time][monitor_name] =
|
||||||
timeIndex !== -1 ? avg_delay[timeIndex] : null;
|
timeIndex !== -1 ? avg_delay[timeIndex] : null;
|
||||||
});
|
});
|
||||||
|
25
components/ui/label.tsx
Normal file
25
components/ui/label.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||||
|
import { type VariantProps, cva } from "class-variance-authority";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
const labelVariants = cva(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||||
|
);
|
||||||
|
|
||||||
|
const Label = React.forwardRef<
|
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
|
VariantProps<typeof labelVariants>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(labelVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
Label.displayName = LabelPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
export { Label };
|
28
components/ui/switch.tsx
Normal file
28
components/ui/switch.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
const Switch = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SwitchPrimitives.Root
|
||||||
|
className={cn(
|
||||||
|
"peer inline-flex h-3 w-6 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<SwitchPrimitives.Thumb
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-none block h-2 w-2 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-3 data-[state=unchecked]:translate-x-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SwitchPrimitives.Root>
|
||||||
|
));
|
||||||
|
Switch.displayName = SwitchPrimitives.Root.displayName;
|
||||||
|
|
||||||
|
export { Switch };
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nezha-dash",
|
"name": "nezha-dash",
|
||||||
"version": "1.7.1",
|
"version": "1.7.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3040",
|
"dev": "next dev -p 3040",
|
||||||
@ -15,11 +15,13 @@
|
|||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
"@radix-ui/react-dialog": "^1.1.2",
|
"@radix-ui/react-dialog": "^1.1.2",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
||||||
|
"@radix-ui/react-label": "^2.1.0",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.1",
|
"@radix-ui/react-navigation-menu": "^1.2.1",
|
||||||
"@radix-ui/react-popover": "^1.1.2",
|
"@radix-ui/react-popover": "^1.1.2",
|
||||||
"@radix-ui/react-progress": "^1.1.0",
|
"@radix-ui/react-progress": "^1.1.0",
|
||||||
"@radix-ui/react-separator": "^1.1.0",
|
"@radix-ui/react-separator": "^1.1.0",
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
|
"@radix-ui/react-switch": "^1.1.1",
|
||||||
"@radix-ui/react-tooltip": "^1.1.4",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@turf/turf": "^7.1.0",
|
"@turf/turf": "^7.1.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user