fix(ui): адаптировать строки Ping-проб для телефона
This commit is contained in:
1 parent
dfb993e1a3
commit
e9e5030047
5 files changed
+133
-38
No files matched your search
+35
-32
@@ -1,5 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import probeStyles from "./probe-rows.module.css"
|
||||
|
||||
import { Suspense, useState, useMemo, useEffect, useRef, useCallback } from "react"
|
||||
import Link from "next/link"
|
||||
import { useSearchParams } from "next/navigation"
|
||||
@@ -2422,7 +2424,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
</div>
|
||||
|
||||
{/* ── probe groups ── */}
|
||||
<div className="p-6 flex flex-col gap-4">
|
||||
<div className="p-4 md:p-6 flex flex-col gap-4">
|
||||
|
||||
{grouped.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center py-20 text-muted-foreground gap-2">
|
||||
@@ -2439,18 +2441,18 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
const warnCount = allServerProbes.filter(p => p.status === "warn").length
|
||||
|
||||
return (
|
||||
<Frame key={srv.id} dense className="w-full overflow-hidden">
|
||||
<Frame key={srv.id} dense className={cn("w-full overflow-hidden", probeStyles.container)}>
|
||||
<FramePanel className="p-0 overflow-hidden">
|
||||
|
||||
{/* server header */}
|
||||
<button onClick={() => toggleCollapse(srv.id)}
|
||||
className="w-full flex items-center gap-2.5 px-4 py-3 hover:bg-muted/30 transition-colors text-left border-b">
|
||||
<button type="button" aria-expanded={!isCollapsed} onClick={() => toggleCollapse(srv.id)}
|
||||
className="w-full flex flex-wrap items-center gap-2.5 px-4 py-3 hover:bg-muted/30 transition-colors text-left border-b">
|
||||
{isCollapsed
|
||||
? <ChevronRightIcon className="size-4 text-muted-foreground shrink-0" />
|
||||
: <ChevronDownIcon className="size-4 text-muted-foreground shrink-0" />}
|
||||
<StatusDot status={srv.status} pulse={srv.status === "online"} />
|
||||
<Flag code={srv.country} size={16} />
|
||||
<span className="font-mono text-sm font-semibold">{srv.name}</span>
|
||||
<span className={cn(probeStyles.serverName, "font-mono text-sm font-semibold min-w-0 wrap-anywhere")}>{srv.name}</span>
|
||||
<TypeChip type={srv.type} />
|
||||
<span className="text-xs text-muted-foreground">{srv.site}</span>
|
||||
<div className="ml-auto flex items-center gap-3 text-xs">
|
||||
@@ -2475,10 +2477,10 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
const groupBusy = !!probeGroupPingBusy[groupBusyKey]
|
||||
return (
|
||||
<div key={`${group.name}|${group.target}`}>
|
||||
<div className="px-4 py-2 border-b bg-muted/20 flex items-center gap-2 text-xs">
|
||||
<div className="px-4 py-2 border-b bg-muted/20 flex flex-wrap items-center gap-2 text-xs">
|
||||
<ArrowRightIcon className="size-3.5 text-muted-foreground/60 shrink-0" />
|
||||
<span className="font-medium truncate min-w-0">{group.name}</span>
|
||||
<span className="font-mono text-muted-foreground shrink-0">{group.target}</span>
|
||||
<span className="font-medium min-w-0 wrap-anywhere">{group.name}</span>
|
||||
<span className="font-mono text-muted-foreground min-w-0 wrap-anywhere">{group.target}</span>
|
||||
<div className="ml-auto flex items-center gap-2 shrink-0">
|
||||
<span className="text-muted-foreground whitespace-nowrap">{group.probes.length} интерф.</span>
|
||||
<Button
|
||||
@@ -2496,8 +2498,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid items-center gap-3 px-4 py-1.5 bg-muted/30 border-b text-[10px] font-semibold uppercase tracking-widest text-muted-foreground"
|
||||
style={{ gridTemplateColumns: "36px 16px 130px 120px 140px 70px 44px minmax(132px,1fr) 96px 36px 72px" }}>
|
||||
<div className={cn(probeStyles.header, "items-center gap-3 px-4 py-1.5 bg-muted/30 border-b text-[10px] font-semibold uppercase tracking-widest text-muted-foreground")}>
|
||||
<span />
|
||||
<span />
|
||||
<span>Интерфейс</span>
|
||||
@@ -2517,33 +2518,33 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
<div key={p.id} data-probe-id={p.id} className="border-b border-border/40 last:border-b-0">
|
||||
<div
|
||||
className={cn(
|
||||
"grid items-center gap-3 px-4 py-2.5 hover:bg-muted/20 transition-colors",
|
||||
probeStyles.row, "items-center gap-3 px-4 py-2.5 hover:bg-muted/20 transition-colors",
|
||||
!p.enabled && "opacity-40",
|
||||
)}
|
||||
style={{ gridTemplateColumns: "36px 16px 130px 120px 140px 70px 44px minmax(132px,1fr) 96px 36px 72px" }}>
|
||||
<FormToggle checked={p.enabled} onChange={v => toggleProbe(p.id, v)} />
|
||||
<StatusDot
|
||||
>
|
||||
<label className={probeStyles.toggle}><span className="sr-only">Включить пробу {p.name}</span><span className={probeStyles.label} aria-hidden="true">Включена</span><FormToggle checked={p.enabled} onChange={v => toggleProbe(p.id, v)} /></label>
|
||||
<div className={probeStyles.status}><StatusDot
|
||||
status={p.status === "up" ? "online" : p.status === "warn" ? "degraded" : "offline"}
|
||||
pulse={p.status === "up" && p.enabled}
|
||||
/>
|
||||
<span className="font-mono text-xs text-muted-foreground truncate">{p.srcInterface || "auto"}</span>
|
||||
<span className="text-sm font-medium truncate">{p.name}</span>
|
||||
{p.filter === "—"
|
||||
/><span className={probeStyles.mobile}>{!p.enabled ? "Отключена" : p.status === "up" ? "Доступна" : p.status === "warn" ? "Предупреждение" : "Недоступна"}</span></div>
|
||||
<div className={probeStyles.iface}><span className={probeStyles.label}>Интерфейс</span><span className="font-mono text-xs text-muted-foreground">{p.srcInterface || "auto"}</span></div>
|
||||
<div className={probeStyles.name}><span className="text-sm font-medium">{p.name}</span></div>
|
||||
<div className={probeStyles.filter}><span className={probeStyles.label}>Фильтр</span>{p.filter === "—"
|
||||
? <span className="text-muted-foreground/40 text-xs">—</span>
|
||||
: <span className="text-[11px] font-medium text-sky-600 dark:text-sky-400 truncate">{p.filter}</span>
|
||||
}
|
||||
<span className={cn("font-mono text-sm font-semibold tabular-nums", rttColor(p.rtt, p.loss))}>
|
||||
: <span className="text-[11px] font-medium text-sky-600 dark:text-sky-400">{p.filter}</span>
|
||||
}</div>
|
||||
<div className={probeStyles.rtt}><span className={probeStyles.label}>RTT</span><span className={cn("font-mono text-sm font-semibold tabular-nums", rttColor(p.rtt, p.loss))}>
|
||||
{p.rtt === null ? "—" : `${p.rtt}мс`}
|
||||
</span>
|
||||
<span className={cn("font-mono text-xs tabular-nums",
|
||||
</span></div>
|
||||
<div className={probeStyles.loss}><span className={probeStyles.label}>Потери</span><span className={cn("font-mono text-xs tabular-nums",
|
||||
p.loss === 0 ? "text-muted-foreground" :
|
||||
p.loss >= 100 ? "text-red-600 dark:text-red-400" :
|
||||
p.loss > 5 ? "text-red-600 dark:text-red-400" :
|
||||
"text-amber-600 dark:text-amber-400",
|
||||
)}>
|
||||
{p.loss}%
|
||||
</span>
|
||||
<div className="min-w-0 flex flex-col gap-0.5 justify-center">
|
||||
</span></div>
|
||||
<div className={cn(probeStyles.bt, "min-w-0 flex flex-col gap-0.5 justify-center")}><span className={probeStyles.label}>BT · ping</span>
|
||||
{!linkedSp?.lastRunAt ? (
|
||||
<span className="text-muted-foreground/35 text-[10px]">—</span>
|
||||
) : linkedSp.lastStatus === "error" && linkedSp.lastError ? (
|
||||
@@ -2577,8 +2578,8 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Sparkline data={p.series} width={96} height={24} color={probeSparkColor(p.status)} filled />
|
||||
<div className="flex justify-center">
|
||||
<div className={probeStyles.graph}><span className={probeStyles.label}>График RTT</span><Sparkline data={p.series} width={96} height={24} color={probeSparkColor(p.status)} filled /></div>
|
||||
<div className={probeStyles.star}><span className={probeStyles.label}>На дашборде</span>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
@@ -2586,6 +2587,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
className="size-7 p-0 text-muted-foreground hover:text-amber-500"
|
||||
onClick={() => toggleDashboardStar(p.id)}
|
||||
title={p.showOnDashboard ? "Убрать с дашборда" : "Показать на дашборде"}
|
||||
aria-label={p.showOnDashboard ? "Убрать с дашборда" : "Показать на дашборде"}
|
||||
aria-pressed={p.showOnDashboard === true}
|
||||
>
|
||||
<StarIcon
|
||||
@@ -2598,15 +2600,15 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex justify-end gap-0.5">
|
||||
<div className={cn(probeStyles.actions, "flex justify-end gap-1")}>
|
||||
<Button size="sm" variant="ghost"
|
||||
className="size-7 p-0 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => openEditProbe(p)} title="Редактировать пробу">
|
||||
type="button" aria-label="Редактировать пробу" onClick={() => openEditProbe(p)} title="Редактировать пробу">
|
||||
<PencilIcon className="size-3.5" />
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost"
|
||||
className="size-7 p-0 text-muted-foreground hover:text-destructive"
|
||||
onClick={() => deleteProbe(p.id)} title="Удалить пробу">
|
||||
type="button" aria-label="Удалить пробу" onClick={() => deleteProbe(p.id)} title="Удалить пробу">
|
||||
<TrashIcon className="size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -2615,7 +2617,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
open={probeRttChartOpen[p.id] ?? false}
|
||||
onOpenChange={(open) => setProbeRttChartOpen((m) => ({ ...m, [p.id]: open }))}
|
||||
>
|
||||
<CollapsibleTrigger
|
||||
<CollapsibleTrigger type="button"
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 border-t border-border/50 bg-muted/15 px-4 py-1.5 text-left text-xs text-muted-foreground",
|
||||
"outline-none hover:bg-muted/30 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
@@ -2657,7 +2659,8 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
setNewName(group.name); setNewTarget(group.target); setNewFilter("—")
|
||||
setSheetOpen(true)
|
||||
}}
|
||||
className="w-full flex items-center gap-2 px-4 py-2 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/20 transition-colors border-t">
|
||||
type="button"
|
||||
className="w-full flex items-center gap-2 px-4 py-2 text-left wrap-anywhere text-xs text-muted-foreground hover:text-foreground hover:bg-muted/20 transition-colors border-t">
|
||||
<PlusIcon className="size-3.5" />
|
||||
Добавить интерфейс для {group.name} ({group.target})
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
.container { container-type: inline-size; }
|
||||
.header { display: none; }
|
||||
.serverName { flex: 1 1 160px; }
|
||||
.row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
grid-template-areas: "name name" "status toggle" "iface filter" "rtt loss" "bt graph" "star actions";
|
||||
}
|
||||
.row > * { min-width: 0; overflow-wrap: anywhere; }
|
||||
.name { grid-area: name; }
|
||||
.status { grid-area: status; display: flex; align-items: center; gap: 0.5rem; font-size: 0.75rem; }
|
||||
.toggle { grid-area: toggle; display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; justify-content: flex-end; }
|
||||
.iface { grid-area: iface; }
|
||||
.filter { grid-area: filter; }
|
||||
.rtt { grid-area: rtt; }
|
||||
.loss { grid-area: loss; }
|
||||
.bt { grid-area: bt; }
|
||||
.graph { grid-area: graph; }
|
||||
.star { grid-area: star; display: flex; flex-wrap: wrap; gap: 0.25rem; align-items: center; }
|
||||
.actions { grid-area: actions; }
|
||||
.label { display: block; font-size: 0.625rem; color: var(--muted-foreground); }
|
||||
.actions button, .star button { min-width: 2rem; min-height: 2rem; }
|
||||
|
||||
@container (min-width: 1100px) {
|
||||
.header, .row {
|
||||
display: grid;
|
||||
grid-template-columns: 36px 16px 130px 120px 140px 70px 44px minmax(132px, 1fr) 96px 36px 72px;
|
||||
grid-template-areas: "toggle status iface name filter rtt loss bt graph star actions";
|
||||
}
|
||||
.label, .mobile { display: none; }
|
||||
.toggle, .star { justify-content: center; }
|
||||
.actions button, .star button { min-width: unset; min-height: unset; }
|
||||
}
|
||||
Reference in new issue
Block a user