mirror of
https://github.com/Buriburizaem0n/nezha-dash-v1.git
synced 2026-02-05 05:00:07 +00:00
feat: service tracker
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 } =
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
56
src/components/ServiceTracker.tsx
Normal file
56
src/components/ServiceTracker.tsx
Normal 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;
|
||||
62
src/components/ServiceTrackerClient.tsx
Normal file
62
src/components/ServiceTrackerClient.tsx
Normal 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;
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user