mirror of
https://github.com/woodchen-ink/nezha-dash-v1.git
synced 2025-07-18 17:41:56 +08:00
feat: enhance ServiceTracker with detailed uptime and delay visualization
This commit is contained in:
parent
94eb966d65
commit
0f81ef2f25
@ -19,10 +19,16 @@ export function ServiceTracker({ serverList }: { serverList: NezhaServer[] }) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const processServiceData = (serviceData: ServiceData) => {
|
const processServiceData = (serviceData: ServiceData) => {
|
||||||
const days = serviceData.up.map((up, index) => ({
|
const days = serviceData.up.map((up, index) => {
|
||||||
|
const totalChecks = up + serviceData.down[index]
|
||||||
|
const dailyUptime = totalChecks > 0 ? (up / totalChecks) * 100 : 0
|
||||||
|
return {
|
||||||
completed: up > serviceData.down[index],
|
completed: up > serviceData.down[index],
|
||||||
date: new Date(Date.now() - (29 - index) * 24 * 60 * 60 * 1000),
|
date: new Date(Date.now() - (29 - index) * 24 * 60 * 60 * 1000),
|
||||||
}))
|
uptime: dailyUptime,
|
||||||
|
delay: serviceData.delay[index] || 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const totalUp = serviceData.up.reduce((a, b) => a + b, 0)
|
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 totalChecks = serviceData.up.reduce((a, b) => a + b, 0) + serviceData.down.reduce((a, b) => a + b, 0)
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip"
|
||||||
import { Separator } from "./ui/separator"
|
import { Separator } from "./ui/separator"
|
||||||
|
|
||||||
interface ServiceTrackerProps {
|
interface ServiceTrackerProps {
|
||||||
days: Array<{
|
days: Array<{
|
||||||
completed: boolean
|
completed: boolean
|
||||||
date?: Date
|
date?: Date
|
||||||
|
uptime: number
|
||||||
|
delay: number
|
||||||
}>
|
}>
|
||||||
className?: string
|
className?: string
|
||||||
title?: string
|
title?: string
|
||||||
@ -18,6 +25,25 @@ interface ServiceTrackerProps {
|
|||||||
export const ServiceTrackerClient: React.FC<ServiceTrackerProps> = ({ days, className, title, uptime = 100, avgDelay = 0 }) => {
|
export const ServiceTrackerClient: React.FC<ServiceTrackerProps> = ({ days, className, title, uptime = 100, avgDelay = 0 }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined
|
const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined
|
||||||
|
|
||||||
|
const getUptimeColor = (uptime: number) => {
|
||||||
|
if (uptime >= 99) return "text-emerald-500"
|
||||||
|
if (uptime >= 95) return "text-amber-500"
|
||||||
|
return "text-rose-500"
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDelayColor = (delay: number) => {
|
||||||
|
if (delay < 100) return "text-emerald-500"
|
||||||
|
if (delay < 300) return "text-amber-500"
|
||||||
|
return "text-rose-500"
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusColor = (uptime: number) => {
|
||||||
|
if (uptime >= 99) return "bg-emerald-500"
|
||||||
|
if (uptime >= 95) return "bg-amber-500"
|
||||||
|
return "bg-rose-500"
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -30,27 +56,63 @@ export const ServiceTrackerClient: React.FC<ServiceTrackerProps> = ({ days, clas
|
|||||||
>
|
>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="flex items-center gap-2">
|
<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={cn("w-2.5 h-2.5 rounded-full transition-colors", getStatusColor(uptime))} />
|
||||||
<div className="w-3 h-3 rounded-full bg-white dark:bg-black" />
|
|
||||||
</div>
|
|
||||||
<span className="font-medium text-sm">{title}</span>
|
<span className="font-medium text-sm">{title}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-stone-600 dark:text-stone-400 font-medium text-sm">{avgDelay.toFixed(0)}ms</span>
|
<span className={cn(
|
||||||
<Separator className="h-4 mx-0" orientation="vertical" />
|
"font-medium text-sm transition-colors",
|
||||||
<span className="text-green-600 font-medium text-sm">
|
getDelayColor(avgDelay)
|
||||||
|
)}>
|
||||||
|
{avgDelay.toFixed(0)}ms
|
||||||
|
</span>
|
||||||
|
<Separator className="h-4" orientation="vertical" />
|
||||||
|
<span className={cn(
|
||||||
|
"font-medium text-sm transition-colors",
|
||||||
|
getUptimeColor(uptime)
|
||||||
|
)}>
|
||||||
{uptime.toFixed(1)}% {t("serviceTracker.uptime")}
|
{uptime.toFixed(1)}% {t("serviceTracker.uptime")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-[2px]">
|
<div className="flex gap-[3px] bg-muted/30 p-1 rounded-lg">
|
||||||
{days.map((day, index) => (
|
{days.map((day, index) => (
|
||||||
|
<TooltipProvider delayDuration={50} key={index}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
<div
|
<div
|
||||||
key={index}
|
className={cn(
|
||||||
className={cn("flex-1 h-6 rounded-[5px] transition-colors", day.completed ? "bg-green-600" : "bg-red-500/60")}
|
"relative flex-1 h-7 rounded-[4px] transition-all duration-200 cursor-help",
|
||||||
title={day.date ? day.date.toLocaleDateString() : `Day ${index + 1}`}
|
"before:absolute before:inset-0 before:rounded-[4px] before:opacity-0 hover:before:opacity-100 before:bg-white/10 before:transition-opacity",
|
||||||
|
"after:absolute after:inset-0 after:rounded-[4px] after:shadow-[inset_0_1px_theme(colors.white/10%)]",
|
||||||
|
day.completed
|
||||||
|
? "bg-gradient-to-b from-green-500/90 to-green-600 shadow-[0_1px_2px_theme(colors.green.600/30%)]"
|
||||||
|
: "bg-gradient-to-b from-red-500/80 to-red-600/90 shadow-[0_1px_2px_theme(colors.red.600/30%)]"
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent className="p-0 overflow-hidden">
|
||||||
|
<div className="px-3 py-2 bg-popover">
|
||||||
|
<p className="font-medium text-sm mb-2">{day.date?.toLocaleDateString()}</p>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<span className="text-xs text-muted-foreground">{t("serviceTracker.uptime")}:</span>
|
||||||
|
<span className={cn("text-xs font-medium", day.uptime > 95 ? "text-green-500" : "text-red-500")}>
|
||||||
|
{day.uptime.toFixed(1)}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<span className="text-xs text-muted-foreground">{t("serviceTracker.delay")}:</span>
|
||||||
|
<span className={cn("text-xs font-medium", day.delay < 100 ? "text-green-500" : day.delay < 300 ? "text-yellow-500" : "text-red-500")}>
|
||||||
|
{day.delay.toFixed(0)}ms
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
"serviceTracker": {
|
"serviceTracker": {
|
||||||
"noService": "No service data",
|
"noService": "No service data",
|
||||||
"uptime": "Uptime",
|
"uptime": "Uptime",
|
||||||
|
"delay": "Delay",
|
||||||
"daysAgo": "days ago",
|
"daysAgo": "days ago",
|
||||||
"today": "Today",
|
"today": "Today",
|
||||||
"loading": "Loading..."
|
"loading": "Loading..."
|
||||||
|
@ -44,8 +44,9 @@
|
|||||||
"nextUpdate": "下次更新"
|
"nextUpdate": "下次更新"
|
||||||
},
|
},
|
||||||
"serviceTracker": {
|
"serviceTracker": {
|
||||||
"noService": "没有服务监测数据",
|
"noService": "无服务数据",
|
||||||
"uptime": "可用率",
|
"uptime": "在线率",
|
||||||
|
"delay": "延迟",
|
||||||
"daysAgo": "天前",
|
"daysAgo": "天前",
|
||||||
"today": "今天",
|
"today": "今天",
|
||||||
"loading": "加载中..."
|
"loading": "加载中..."
|
||||||
|
@ -44,8 +44,9 @@
|
|||||||
"nextUpdate": "下次更新"
|
"nextUpdate": "下次更新"
|
||||||
},
|
},
|
||||||
"serviceTracker": {
|
"serviceTracker": {
|
||||||
"noService": "沒有服務監控數據",
|
"noService": "無服務數據",
|
||||||
"uptime": "可用率",
|
"uptime": "在線率",
|
||||||
|
"delay": "延遲",
|
||||||
"daysAgo": "天前",
|
"daysAgo": "天前",
|
||||||
"today": "今天",
|
"today": "今天",
|
||||||
"loading": "載入中..."
|
"loading": "載入中..."
|
||||||
|
Loading…
x
Reference in New Issue
Block a user