feat: service tracker

This commit is contained in:
hamster1963
2024-11-29 09:00:04 +08:00
parent d7f0410dcd
commit 2462dfc21b
14 changed files with 229 additions and 69 deletions

View File

@@ -26,7 +26,6 @@ import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
import NetworkChartLoading from "./NetworkChartLoading";
import { NezhaMonitor, ServerMonitorChart } from "@/types/nezha-api";
interface ResultItem {
created_at: number;
[key: string]: number | null;
@@ -41,17 +40,15 @@ export function NetworkChart({
}) {
const { t } = useTranslation();
const { data: monitorData} = useQuery(
{
queryKey: ["monitor", server_id],
queryFn: () => fetchMonitor(server_id),
enabled: show,
refetchOnMount: true,
refetchOnWindowFocus: true,
refetchInterval: 10000,
}
)
const { data: monitorData } = useQuery({
queryKey: ["monitor", server_id],
queryFn: () => fetchMonitor(server_id),
enabled: show,
refetchOnMount: true,
refetchOnWindowFocus: true,
refetchInterval: 10000,
});
if (!monitorData) return <NetworkChartLoading />;
if (monitorData?.success && monitorData.data.length === 0) {
@@ -68,8 +65,6 @@ export function NetworkChart({
);
}
const transformedData = transformData(monitorData.data);
const formattedData = formatData(monitorData.data);

View File

@@ -7,7 +7,11 @@ import { Card } from "./ui/card";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
export default function ServerCard({ serverInfo }: { serverInfo: NezhaServer }) {
export default function ServerCard({
serverInfo,
}: {
serverInfo: NezhaServer;
}) {
const { t } = useTranslation();
const navigate = useNavigate();
const { name, country_code, online, cpu, up, down, mem, stg } =

View File

@@ -50,7 +50,11 @@ type connectChartData = {
udp: number;
};
export default function ServerDetailChart({server_id}: {server_id: string}) {
export default function ServerDetailChart({
server_id,
}: {
server_id: string;
}) {
const { lastMessage, readyState } = useWebSocketContext();
if (readyState !== 1) {

View File

@@ -9,10 +9,14 @@ import { NezhaWebsocketResponse } from "@/types/nezha-api";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
export default function ServerDetailOverview({server_id}: {server_id: string}) {
export default function ServerDetailOverview({
server_id,
}: {
server_id: string;
}) {
const { t } = useTranslation();
const navigate = useNavigate();
const { lastMessage, readyState } = useWebSocketContext();
if (readyState !== 1) {

View File

@@ -0,0 +1,56 @@
import React from "react";
import ServiceTrackerClient from "./ServiceTrackerClient";
import { useQuery } from "@tanstack/react-query";
import { fetchService } from "@/lib/nezha-api";
import { ServiceData } from "@/types/nezha-api";
export const ServiceTracker: React.FC = () => {
const { data: serviceData, isLoading } = useQuery({
queryKey: ["service"],
queryFn: () => fetchService(),
refetchOnMount: true,
refetchOnWindowFocus: true,
refetchInterval: 10000,
});
const processServiceData = (serviceData: ServiceData) => {
const days = serviceData.up.map((up, index) => ({
completed: up > serviceData.down[index],
date: new Date(Date.now() - (29 - index) * 24 * 60 * 60 * 1000),
}));
const totalUp = serviceData.up.reduce((a, b) => a + b, 0);
const totalChecks =
serviceData.up.reduce((a, b) => a + b, 0) +
serviceData.down.reduce((a, b) => a + b, 0);
const uptime = (totalUp / totalChecks) * 100;
return { days, uptime };
};
if (isLoading) {
return <div className="mt-4">Loading...</div>;
}
if (!serviceData?.data?.services) {
return <div className="mt-4">No service data available</div>;
}
return (
<div className="mt-4 w-full mx-auto grid grid-cols-1 md:grid-cols-2 gap-2 md:gap-4">
{Object.entries(serviceData.data.services).map(([name, data]) => {
const { days, uptime } = processServiceData(data);
return (
<ServiceTrackerClient
key={name}
days={days}
title={data.service.name}
uptime={uptime}
/>
);
})}
</div>
);
};
export default ServiceTracker;

View File

@@ -0,0 +1,62 @@
import React from "react";
import { cn } from "@/lib/utils";
interface ServiceTrackerProps {
days: Array<{
completed: boolean;
date?: Date;
}>;
className?: string;
title?: string;
uptime?: number;
}
export const ServiceTrackerClient: React.FC<ServiceTrackerProps> = ({
days,
className,
title,
uptime = 100,
}) => {
return (
<div
className={cn(
"w-full space-y-3 bg-white px-4 py-4 dark:bg-black rounded-lg border bg-card text-card-foreground shadow-lg shadow-neutral-200/40 dark:shadow-none",
className,
)}
>
<div className="flex justify-between items-center">
<div className="flex items-center gap-2">
<div className="w-5 h-5 rounded-full bg-green-600 flex items-center justify-center">
<div className="w-3 h-3 rounded-full bg-white dark:bg-black" />
</div>
<span className="font-medium text-sm">{title}</span>
</div>
<span className="text-green-600 font-medium text-sm">
{uptime.toFixed(1)}% uptime
</span>
</div>
<div className="flex gap-[2px]">
{days.map((day, index) => (
<div
key={index}
className={cn(
"flex-1 h-6 rounded-[5px] transition-colors",
day.completed ? "bg-green-600" : "bg-red-500",
)}
title={
day.date ? day.date.toLocaleDateString() : `Day ${index + 1}`
}
/>
))}
</div>
<div className="flex justify-between text-xs text-stone-500 dark:text-stone-400">
<span>30 DAYS AGO</span>
<span>TODAY</span>
</div>
</div>
);
};
export default ServiceTrackerClient;

View File

@@ -2,7 +2,6 @@ import { cn } from "@/lib/utils";
import { m } from "framer-motion";
import { useTranslation } from "react-i18next";
export default function TabSwitch({
tabs,
currentTab,
@@ -38,7 +37,7 @@ export default function TabSwitch({
/>
)}
<div className="relative z-20 flex items-center gap-1">
<p className="whitespace-nowrap">{t("tabSwitch."+tab)}</p>
<p className="whitespace-nowrap">{t("tabSwitch." + tab)}</p>
</div>
</div>
))}