fix(health): показывать пинг при наведении на график uptime
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 6s
quality / changes (push) Successful in 9s
quality / api (push) Skipped
quality / docker-check (push) Skipped
quality / web (push) Successful in 1m11s
CD / quality (push) Successful in 1m23s
CD / publish (push) Successful in 1m57s
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 6s
quality / changes (push) Successful in 9s
quality / api (push) Skipped
quality / docker-check (push) Skipped
quality / web (push) Successful in 1m11s
CD / quality (push) Successful in 1m23s
CD / publish (push) Successful in 1m57s
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useId, useMemo, useState } from 'react'
|
import { useId, useMemo, useState } from 'react'
|
||||||
import { ActivityIcon, InfoIcon, TrendingDownIcon, TrendingUpIcon } from 'lucide-react'
|
import { ActivityIcon, InfoIcon, TrendingDownIcon, TrendingUpIcon } from 'lucide-react'
|
||||||
import { Area, ComposedChart, Line, XAxis } from 'recharts'
|
import { Area, ComposedChart, Line, XAxis, YAxis } from 'recharts'
|
||||||
|
|
||||||
import { EmptyState } from '@/components/empty-state'
|
import { EmptyState } from '@/components/empty-state'
|
||||||
import { Badge } from '@/components/reui/badge'
|
import { Badge } from '@/components/reui/badge'
|
||||||
@@ -226,14 +226,14 @@ export function UptimeChart({
|
|||||||
}: UptimeChartProps) {
|
}: UptimeChartProps) {
|
||||||
const gradientId = useId().replace(/:/g, '')
|
const gradientId = useId().replace(/:/g, '')
|
||||||
const [internalPeriod, setInternalPeriod] = useState<UptimePeriodKey>('5D')
|
const [internalPeriod, setInternalPeriod] = useState<UptimePeriodKey>('5D')
|
||||||
const [pinnedIndex, setPinnedIndex] = useState<number | undefined>()
|
const [hovered, setHovered] = useState<AlignedChartPoint | null>(null)
|
||||||
const period = periodProp ?? internalPeriod
|
const period = periodProp ?? internalPeriod
|
||||||
const days = UPTIME_PERIODS.find((entry) => entry.key === period)?.days ?? 5
|
const days = UPTIME_PERIODS.find((entry) => entry.key === period)?.days ?? 5
|
||||||
|
|
||||||
function handlePeriodChange(next: UptimePeriodKey) {
|
function handlePeriodChange(next: UptimePeriodKey) {
|
||||||
onPeriodChange?.(next)
|
onPeriodChange?.(next)
|
||||||
if (periodProp == null) setInternalPeriod(next)
|
if (periodProp == null) setInternalPeriod(next)
|
||||||
setPinnedIndex(undefined)
|
setHovered(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { points, keys } = useMemo(
|
const { points, keys } = useMemo(
|
||||||
@@ -246,9 +246,25 @@ export function UptimeChart({
|
|||||||
const tileClass = lastOk ? 'text-success' : 'text-destructive'
|
const tileClass = lastOk ? 'text-success' : 'text-destructive'
|
||||||
const single = keys.length <= 1
|
const single = keys.length <= 1
|
||||||
const areaKey = keys[0] ?? 'local'
|
const areaKey = keys[0] ?? 'local'
|
||||||
|
const hoverPings = hovered
|
||||||
|
? keys.map((key) => {
|
||||||
|
const ok = hovered[`${key}Ok`]
|
||||||
|
const label = chartConfig[key].label
|
||||||
|
return `${label} ${formatPing(hovered[key], ok)}`
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
|
||||||
|
function syncHover(state: {
|
||||||
|
activeTooltipIndex?: unknown
|
||||||
|
activeIndex?: unknown
|
||||||
|
}) {
|
||||||
|
const index = Number(state.activeTooltipIndex ?? state.activeIndex)
|
||||||
|
if (!Number.isFinite(index)) return
|
||||||
|
setHovered(points[index] ?? null)
|
||||||
|
}
|
||||||
|
|
||||||
const panel = (
|
const panel = (
|
||||||
<FramePanel className="flex flex-col gap-6">
|
<FramePanel className="flex flex-col gap-6 overflow-visible">
|
||||||
{hideHeader ? null : (
|
{hideHeader ? null : (
|
||||||
<div className="border-border flex items-center justify-between gap-2 border-b border-dashed pb-4">
|
<div className="border-border flex items-center justify-between gap-2 border-b border-dashed pb-4">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
@@ -316,22 +332,33 @@ export function UptimeChart({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{hoverPings.length > 0 ? (
|
||||||
|
<p className="text-muted-foreground min-h-4 min-w-0 text-xs tabular-nums">
|
||||||
|
<span className="text-foreground font-medium">Пинг</span>
|
||||||
|
{' · '}
|
||||||
|
{formatDate(hovered?.at)}
|
||||||
|
{' · '}
|
||||||
|
{hoverPings.join(' · ')}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-muted-foreground min-h-4 text-xs">
|
||||||
|
Наведите на точку графика
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="h-40 w-full overflow-visible">
|
<div className="h-40 w-full overflow-visible">
|
||||||
<ChartContainer
|
<ChartContainer
|
||||||
config={chartConfig}
|
config={chartConfig}
|
||||||
className="h-full w-full overflow-visible rounded-b-xl"
|
className="[&_.recharts-tooltip-wrapper]:z-50 [&_.recharts-wrapper]:overflow-visible h-full w-full overflow-visible rounded-b-xl"
|
||||||
initialDimension={{ width: 320, height: 160 }}
|
initialDimension={{ width: 320, height: 160 }}
|
||||||
>
|
>
|
||||||
<ComposedChart
|
<ComposedChart
|
||||||
data={points}
|
data={points}
|
||||||
margin={{ top: 16, left: 8, right: 8, bottom: 4 }}
|
margin={{ top: 24, left: 8, right: 8, bottom: 8 }}
|
||||||
accessibilityLayer
|
accessibilityLayer
|
||||||
onClick={(state) => {
|
onMouseMove={syncHover}
|
||||||
const index = state.activeTooltipIndex ?? state.activeIndex
|
onMouseLeave={() => setHovered(null)}
|
||||||
if (index == null) return
|
onClick={syncHover}
|
||||||
const next = Number(index)
|
|
||||||
if (Number.isFinite(next)) setPinnedIndex(next)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
|
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
|
||||||
@@ -347,13 +374,14 @@ export function UptimeChart({
|
|||||||
/>
|
/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<XAxis dataKey="period" hide />
|
<XAxis dataKey="at" hide />
|
||||||
|
<YAxis hide domain={['auto', 'auto']} />
|
||||||
<ChartTooltip
|
<ChartTooltip
|
||||||
key={pinnedIndex ?? 'hover'}
|
|
||||||
cursor={{ stroke: 'var(--border)', strokeDasharray: '4 4' }}
|
cursor={{ stroke: 'var(--border)', strokeDasharray: '4 4' }}
|
||||||
filterNull={false}
|
filterNull={false}
|
||||||
defaultIndex={pinnedIndex}
|
shared
|
||||||
active={pinnedIndex != null ? true : undefined}
|
isAnimationActive={false}
|
||||||
|
allowEscapeViewBox={{ x: true, y: true }}
|
||||||
wrapperStyle={{ zIndex: 50, pointerEvents: 'none' }}
|
wrapperStyle={{ zIndex: 50, pointerEvents: 'none' }}
|
||||||
content={
|
content={
|
||||||
<ChartTooltipContent
|
<ChartTooltipContent
|
||||||
@@ -393,6 +421,7 @@ export function UptimeChart({
|
|||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
connectNulls={false}
|
connectNulls={false}
|
||||||
isAnimationActive={false}
|
isAnimationActive={false}
|
||||||
|
dot={{ r: 3, strokeWidth: 1, stroke: 'var(--background)' }}
|
||||||
activeDot={{
|
activeDot={{
|
||||||
r: 6,
|
r: 6,
|
||||||
stroke: 'var(--background)',
|
stroke: 'var(--background)',
|
||||||
|
|||||||
Reference in New Issue
Block a user