fix: enhance NetworkChart with user login state and period selection (#54)

This commit is contained in:
仓鼠
2026-01-29 09:29:00 +08:00
committed by GitHub
parent 76590a6bd0
commit 1aa66f98ed
4 changed files with 131 additions and 20 deletions
+1
View File
@@ -35,6 +35,7 @@ jobs:
uses: softprops/action-gh-release@v2
with:
files: dist.zip
prerelease: ${{ contains(github.ref_name, '-alpha') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-rc') }}
- name: Changelog
run: bun x changelogithub
+1
View File
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "nazha-dashboard-vite",
+113 -8
View File
@@ -27,7 +27,11 @@ import {
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { fetchMonitor } from "@/lib/nezha-api";
import {
fetchLoginUser,
fetchMonitor,
type MonitorPeriod,
} from "@/lib/nezha-api";
import { cn, formatTime } from "@/lib/utils";
import type { NezhaMonitor, ServerMonitorChart } from "@/types/nezha-api";
@@ -40,6 +44,12 @@ interface ResultItem {
[key: string]: number;
}
const TIME_RANGE_OPTIONS: { value: MonitorPeriod; label: string }[] = [
{ value: "1d", label: "1D" },
{ value: "7d", label: "7D" },
{ value: "30d", label: "30D" },
];
/**
* Helper method to calculate packet loss from delay data
*/
@@ -115,10 +125,31 @@ export function NetworkChart({
show: boolean;
}) {
const { t } = useTranslation();
const [period, setPeriod] = React.useState<MonitorPeriod>("30d");
const { data: userData, isError: isLoginError } = useQuery({
queryKey: ["login-user"],
queryFn: () => fetchLoginUser(),
refetchOnMount: false,
refetchOnWindowFocus: true,
refetchIntervalInBackground: true,
refetchInterval: 1000 * 30,
retry: 0,
});
const isLogin = isLoginError
? false
: userData
? !!userData?.data?.id && !!document.cookie
: false;
React.useEffect(() => {
if (!isLogin && period !== "1d") {
setPeriod("1d");
}
}, [isLogin, period]);
const { data: monitorData } = useQuery({
queryKey: ["monitor", server_id],
queryFn: () => fetchMonitor(server_id),
queryKey: ["monitor", server_id, period],
queryFn: () => fetchMonitor(server_id, period),
enabled: show,
refetchOnMount: true,
refetchOnWindowFocus: true,
@@ -145,7 +176,19 @@ export function NetworkChart({
const formattedData = formatData(monitorData.data);
const chartDataKey = Object.keys(transformedData);
const monitorIdByName = new Map(
monitorData.data.map((item) => [item.monitor_name, item.monitor_id]),
);
const chartDataKey = Object.keys(transformedData).sort((a, b) => {
const aId = monitorIdByName.get(a);
const bId = monitorIdByName.get(b);
if (aId === undefined && bId === undefined) {
return a.localeCompare(b);
}
if (aId === undefined) return 1;
if (bId === undefined) return -1;
return aId - bId;
});
const initChartConfig = {
avg_delay: {
@@ -166,6 +209,9 @@ export function NetworkChart({
chartData={transformedData}
serverName={monitorData.data[0].server_name}
formattedData={formattedData}
period={period}
onPeriodChange={setPeriod}
isLogin={isLogin}
/>
);
}
@@ -176,12 +222,18 @@ export const NetworkChartClient = React.memo(function NetworkChart({
chartData,
serverName,
formattedData,
period,
onPeriodChange,
isLogin,
}: {
chartDataKey: string[];
chartConfig: ChartConfig;
chartData: ServerMonitorChart;
serverName: string;
formattedData: ResultItem[];
period: MonitorPeriod;
onPeriodChange: (period: MonitorPeriod) => void;
isLogin: boolean;
}) {
const { t } = useTranslation();
@@ -221,11 +273,30 @@ export const NetworkChartClient = React.memo(function NetworkChart({
[chartDataKey],
);
const chartStats = useMemo(() => {
const stats: { [key: string]: { minDelay: number; maxDelay: number } } = {};
for (const key of chartDataKey) {
const data = chartData[key] || [];
if (data.length > 0) {
const delays = data.map((item) => item.avg_delay);
const minDelay = Math.min(...delays);
const maxDelay = Math.max(...delays);
stats[key] = { minDelay, maxDelay };
} else {
stats[key] = { minDelay: 0, maxDelay: 0 };
}
}
return stats;
}, [chartDataKey, chartData]);
const chartButtons = useMemo(
() =>
chartDataKey.map((key) => {
const monitorData = chartData[key];
const lastDelay = monitorData[monitorData.length - 1].avg_delay;
const stats = chartStats[key];
// Calculate average packet loss if available
const packetLossData = monitorData.reduce<number[]>((acc, item) => {
@@ -251,19 +322,27 @@ export const NetworkChartClient = React.memo(function NetworkChart({
{key}
</span>
<div className="flex flex-col gap-0.5">
<span className="text-md font-bold leading-none sm:text-lg">
<span className="text-md font-semibold leading-none sm:text-xl">
{lastDelay.toFixed(2)}ms
</span>
<div className="flex items-center gap-2 text-[12px]">
<span className="text-green-600 dark:text-green-400">
{stats.minDelay.toFixed(0)}
</span>
<span className="text-red-600 dark:text-red-500">
{stats.maxDelay.toFixed(0)}
</span>
{avgPacketLoss !== null && (
<span className="text-xs text-muted-foreground">
<span className="text-muted-foreground">
{avgPacketLoss.toFixed(2)}% avg loss
</span>
)}
</div>
</div>
</button>
);
}),
[chartDataKey, activeCharts, chartData, handleButtonClick],
[chartDataKey, activeCharts, chartData, chartStats, handleButtonClick],
);
const chartElements = useMemo(() => {
@@ -463,7 +542,32 @@ export const NetworkChartClient = React.memo(function NetworkChart({
<CardDescription className="text-xs">
{chartDataKey.length} {t("monitor.monitorCount")}
</CardDescription>
<div className="flex items-center mt-0.5 space-x-2">
<div className="mt-0.5 flex items-center gap-3">
<div className="flex items-center gap-1 rounded-lg bg-muted/50 p-1">
{TIME_RANGE_OPTIONS.map((option) => {
const isLocked = !isLogin && option.value !== "1d";
return (
<button
key={option.value}
type="button"
disabled={isLocked}
onClick={() => {
if (!isLocked) {
onPeriodChange(option.value);
}
}}
className={`rounded-md px-2.5 py-1 text-xs font-medium transition-all duration-200 ${
period === option.value
? "bg-primary text-primary-foreground shadow-sm"
: "text-muted-foreground hover:bg-muted hover:text-foreground"
} ${isLocked ? "cursor-not-allowed opacity-50" : ""}`}
>
{option.label}
</button>
);
})}
</div>
<div className="flex items-center space-x-2">
<Switch
id="Peak"
checked={isPeakEnabled}
@@ -474,6 +578,7 @@ export const NetworkChartClient = React.memo(function NetworkChart({
</Label>
</div>
</div>
</div>
<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">
+5 -1
View File
@@ -37,10 +37,14 @@ export const fetchLoginUser = async (): Promise<LoginUserResponse> => {
return data;
};
export type MonitorPeriod = "1d" | "7d" | "30d";
export const fetchMonitor = async (
server_id: number,
period?: MonitorPeriod,
): Promise<MonitorResponse> => {
const response = await fetch(`/api/v1/service/${server_id}`);
const query = period ? `?period=${period}` : "";
const response = await fetch(`/api/v1/server/${server_id}/service${query}`);
const data = await response.json();
if (data.error) {
throw new Error(data.error);