Merge branch 'main' into cloudflare

This commit is contained in:
hamster1963 2024-12-06 15:21:44 +08:00
commit ff76a1b02c
5 changed files with 129 additions and 7 deletions

View File

@ -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;
}); });

BIN
bun.lockb

Binary file not shown.

25
components/ui/label.tsx Normal file
View 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
View 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 };

View File

@ -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",