fix(ui): ограничить раскрытие большого списка пиров WireGuard

This commit is contained in:
Denozordec committed 2026-10-04 12:16:34 +07:00
1 parent 747f544462
commit 837298a171
7 files changed
+74 -21

No files matched your search

@@ -122,6 +122,7 @@ function WireguardDataGrid({
expandedContent: (row: WgIfaceWithServer) => (
<WireGuardPeersDetail
peers={row.peers}
interfaceName={row.name}
onDeletePeer={onDeletePeer ? (peerId) => onDeletePeer(row, peerId) : undefined}
onExportPeer={onExportPeer ? (peerId) => onExportPeer(row, peerId) : undefined}
/>
@@ -1,5 +1,6 @@
"use client"
import { useState } from "react"
import type { WireGuardPeer } from "@/lib/data"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
@@ -30,13 +31,20 @@ function peerKey(peer: WireGuardPeer, index: number): string {
function WireGuardPeersDetail({
peers,
interfaceName,
onDeletePeer,
onExportPeer,
}: {
peers: WireGuardPeer[]
interfaceName: string
onDeletePeer?: (peerId: string) => void
onExportPeer?: (peerId: string) => void
}) {
const [page, setPage] = useState(0)
const lastPage = Math.max(0, Math.ceil(peers.length / 50) - 1)
const currentPage = Math.min(page, lastPage)
const offset = currentPage * 50
if (page > lastPage) setPage(lastPage)
if (peers.length === 0) {
return (
<div className="px-5 py-4 text-xs text-muted-foreground text-center border-t border-border/50">
@@ -46,30 +54,30 @@ function WireGuardPeersDetail({
}
return (
<div className="border-t border-border/50">
<div className="grid grid-cols-[1fr_1fr_auto_auto_auto_auto] gap-3 px-5 py-1.5 bg-muted/10 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
<span>Public Key</span>
<span>Allowed IPs</span>
<span>Последнее рукопожатие</span>
<span>RX / TX</span>
<span>Endpoint</span>
<span className="sr-only">Действия</span>
<section aria-label={"Пиры: " + interfaceName} className="sticky left-0 w-full max-w-[calc(100vw-2rem)] border-t border-border/50 md:max-w-none">
<div className="flex flex-wrap items-center justify-between gap-2 px-4 py-2 bg-muted/10">
<p className="text-xs text-muted-foreground" aria-live="polite">Пиры {offset + 1}–{Math.min(offset + 50, peers.length)} из {peers.length}</p>
{peers.length > 50 && <div className="flex gap-1">
<Button type="button" variant="outline" className="min-h-11" disabled={currentPage === 0} aria-label="Предыдущая страница пиров" onClick={() => setPage(currentPage - 1)}>Назад</Button>
<Button type="button" variant="outline" className="min-h-11" disabled={currentPage >= lastPage} aria-label="Следующая страница пиров" onClick={() => setPage(currentPage + 1)}>Далее</Button>
</div>}
</div>
{peers.map((peer, index) => {
const id = peerKey(peer, index)
{peers.slice(offset, offset + 50).map((peer, index) => {
const id = peerKey(peer, offset + index)
return (
<div
key={id}
className="grid grid-cols-[1fr_1fr_auto_auto_auto_auto] gap-3 px-5 py-2.5 items-center text-xs border-t border-border/50 bg-muted/20"
data-wireguard-peer={id}
className="grid grid-cols-2 gap-3 px-4 py-3 items-center text-xs border-t border-border/50 bg-muted/20 md:grid-cols-[1fr_1fr_auto_auto_auto_auto]"
>
<div className="flex items-center gap-1.5 min-w-0">
<KeyRoundIcon className="size-3 text-muted-foreground shrink-0" />
<span className="font-mono text-muted-foreground truncate" title={peer.publicKey}>
{truncKey(peer.publicKey)}
<span className="font-mono text-muted-foreground break-all" title={peer.publicKey}>
{peer.name || truncKey(peer.publicKey)}
</span>
</div>
<div className="font-mono text-muted-foreground truncate">
{peer.allowedIps.join(", ")}
<div className="min-w-0 break-all font-mono text-muted-foreground">
<span className="sr-only">Allowed IPs: </span><span>{peer.allowedIps.join(", ")}</span>
</div>
<span
className={cn(
@@ -77,7 +85,7 @@ function WireGuardPeersDetail({
peer.latestHandshake ? "text-success" : "text-muted-foreground",
)}
>
{peer.latestHandshake ?? "нет рукопожатия"}
<span className="sr-only">Последнее рукопожатие: </span>{peer.latestHandshake ?? "нет рукопожатия"}
</span>
<div className="flex items-center gap-2 text-muted-foreground whitespace-nowrap">
<span aria-label="RX" className="flex items-center gap-0.5">
@@ -89,14 +97,14 @@ function WireGuardPeersDetail({
{fmtBytes(peer.transferTx)}
</span>
</div>
<span className="font-mono text-muted-foreground/60 text-[11px]">{peer.endpoint ?? "—"}</span>
<span className="min-w-0 break-all font-mono text-muted-foreground/60 text-[11px]"><span className="sr-only">Endpoint: </span>{peer.endpoint ?? "—"}</span>
<div className="flex items-center gap-1 justify-end">
{onExportPeer && (
<Button
type="button"
variant="ghost"
size="icon"
className="size-7"
className="size-11"
aria-label="Экспорт peer .conf"
onClick={(e) => {
e.stopPropagation()
@@ -111,7 +119,7 @@ function WireGuardPeersDetail({
type="button"
variant="ghost"
size="icon"
className="size-7 text-destructive"
className="size-11 text-destructive"
aria-label="Удалить пира"
onClick={(e) => {
e.stopPropagation()
@@ -125,7 +133,7 @@ function WireGuardPeersDetail({
</div>
)
})}
</div>
</section>
)
}