"use client" import { useState, useMemo } from "react" import { PageHeader } from "@/components/page-header" import { Card, CardContent, CardHeader, CardTitle, CardDescription, } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { PlusIcon, SearchIcon, TagIcon, ServerIcon, FilterIcon, ChevronRightIcon, CopyIcon, CheckIcon, TrashIcon, PencilIcon, } from "lucide-react" import { cn } from "@/lib/utils" import { filters } from "@/lib/data" // ─── types ──────────────────────────────────────────────────────────────────── type CommType = "standard" | "no-export" | "no-advertise" | "local-as" | "custom" interface Community { id: string value: string // e.g. "65001:100" name: string description: string type: CommType filterIds: string[] // which filters use this community serverCount: number prefixCount: number action: "permit" | "deny" | "local-pref" | "metric" actionValue?: number // e.g. local-pref value enabled: boolean } // ─── mock data ──────────────────────────────────────────────────────────────── const COMMUNITIES: Community[] = [ { id: "c1", value: "65001:100", name: "youtube-bypass", description: "Пометка для трафика YouTube — маршрутизация через SPB/FRA exit nodes", type: "standard", filterIds: ["f1"], serverCount: 4, prefixCount: 842, action: "local-pref", actionValue: 200, enabled: true, }, { id: "c2", value: "65001:200", name: "streaming-eu", description: "EU-стриминг (Netflix, Twitch) — выход через FRA/AMS", type: "standard", filterIds: ["f1", "f2"], serverCount: 3, prefixCount: 614, action: "local-pref", actionValue: 180, enabled: true, }, { id: "c3", value: "65001:300", name: "cdn-bypass", description: "Обход CDN-провайдеров через прямые пиринговые IX-точки", type: "standard", filterIds: ["f3"], serverCount: 5, prefixCount: 4218, action: "local-pref", actionValue: 210, enabled: true, }, { id: "c4", value: "65002:100", name: "cdn-secondary", description: "Резервный CDN-путь при деградации основного", type: "standard", filterIds: ["f3"], serverCount: 5, prefixCount: 1842, action: "metric", actionValue: 50, enabled: true, }, { id: "c5", value: "65001:400", name: "office-direct", description: "Office 365 / Teams — прямой выход без туннелирования", type: "standard", filterIds: ["f5"], serverCount: 6, prefixCount: 882, action: "permit", enabled: true, }, { id: "c6", value: "65001:500", name: "gaming-ll", description: "Low-latency gaming — приоритет по минимальному RTT (Discord, Steam)", type: "standard", filterIds: ["f6"], serverCount: 4, prefixCount: 1212, action: "local-pref", actionValue: 250, enabled: true, }, { id: "c7", value: "65002:200", name: "gaming-ll-backup", description: "Резервный путь для gaming-low-latency", type: "standard", filterIds: ["f6"], serverCount: 4, prefixCount: 412, action: "metric", actionValue: 80, enabled: true, }, { id: "c8", value: "65007:999", name: "school-block", description: "Блокировка соцсетей для школьного сегмента LAN", type: "standard", filterIds: ["f4"], serverCount: 2, prefixCount: 412, action: "deny", enabled: false, }, { id: "c9", value: "no-export", name: "no-export", description: "Не объявлять маршрут за пределы AS (RFC 1997)", type: "no-export", filterIds: [], serverCount: 1, prefixCount: 0, action: "permit", enabled: true, }, { id: "c10", value: "no-advertise", name: "no-advertise", description: "Не передавать маршрут ни одному BGP-пиру (RFC 1997)", type: "no-advertise", filterIds: [], serverCount: 1, prefixCount: 0, action: "permit", enabled: true, }, ] const TYPE_LABELS: Record = { "standard": "Стандартный", "no-export": "No-export", "no-advertise":"No-advertise", "local-as": "Local-AS", "custom": "Кастомный", } const ACTION_LABELS: Record = { "permit": "Permit", "deny": "Deny", "local-pref": "Local-pref", "metric": "MED/Metric", } const ACTION_COLOR: Record = { "permit": "text-emerald-500", "deny": "text-red-500", "local-pref": "text-blue-500", "metric": "text-amber-500", } // ─── page ───────────────────────────────────────────────────────────────────── export default function CommunitiesPage() { const [search, setSearch] = useState("") const [typeFilter, setTypeFilter] = useState("all") const [copied, setCopied] = useState(null) const [selected, setSelected] = useState(null) const filtered = useMemo(() => { const q = search.toLowerCase() return COMMUNITIES.filter(c => { const matchQ = !q || c.value.toLowerCase().includes(q) || c.name.toLowerCase().includes(q) || c.description.toLowerCase().includes(q) const matchT = typeFilter === "all" || c.type === typeFilter return matchQ && matchT }) }, [search, typeFilter]) const handleCopy = (value: string) => { navigator.clipboard.writeText(value).catch(() => {}) setCopied(value) setTimeout(() => setCopied(null), 1500) } const filterName = (id: string) => filters.find(f => f.id === id)?.name ?? id return (
Добавить } />
{/* ── summary ── */}
{[ { label: "Всего communities", value: String(COMMUNITIES.length) }, { label: "Активных", value: String(COMMUNITIES.filter(c => c.enabled).length) }, { label: "Стандартных", value: String(COMMUNITIES.filter(c => c.type === "standard").length) }, { label: "Использует фильтры",value: String(new Set(COMMUNITIES.flatMap(c => c.filterIds)).size) }, ].map(({ label, value }) => (

{label}

{value}

))}
{/* ── main table ── */}
{/* toolbar */}
setSearch(e.target.value)} />
{(["all", "standard", "no-export", "no-advertise", "custom"] as const).map(t => ( ))}
{/* list */}
{filtered.map(c => ( setSelected(c)} className={cn( "border-b last:border-0 cursor-pointer hover:bg-muted/40 transition-colors", selected?.id === c.id && "bg-primary/5", !c.enabled && "opacity-50", )} > ))}
Community Имя / описание Тип Действие Маршрутов Серверов
{c.value}

{c.name}

{c.description}

{TYPE_LABELS[c.type]} {ACTION_LABELS[c.action]}{c.actionValue !== undefined ? ` ${c.actionValue}` : ""} {c.prefixCount > 0 ? c.prefixCount.toLocaleString() : "—"}
{c.serverCount}
{filtered.length === 0 && (

Ничего не найдено

)}
{/* ── detail panel ── */} {selected ? (
{selected.enabled ? "Активен" : "Выключен"}
{selected.value} {selected.name}

{selected.description}

{[ ["Тип", TYPE_LABELS[selected.type]], ["Действие", `${ACTION_LABELS[selected.action]}${selected.actionValue !== undefined ? ` ${selected.actionValue}` : ""}`], ["Маршрутов", selected.prefixCount > 0 ? selected.prefixCount.toLocaleString() : "—"], ["Серверов", String(selected.serverCount)], ].map(([k, v]) => (
{k} {v}
))}
{selected.filterIds.length > 0 && (

Использующие фильтры

{selected.filterIds.map(fid => ( {filterName(fid)} ))}
)}
) : (

Выберите community
для просмотра деталей

)}
) }