"use client" import { useMemo, useState } from "react" import { PageHeader } from "@/components/page-header" import { routerContainers, servers } from "@/lib/data" import type { RouterContainer } from "@/lib/data" import { Flag } from "@/components/flag" import { Frame, FramePanel } from "@/components/reui/frame" import { IconTile } from "@/components/reui/icon-tile" import { OpsPanel } from "@/components/ops-panel" import { Button } from "@/components/ui/button" import { cn } from "@/lib/utils" import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, } from "@/components/ui/dropdown-menu" import { BoxIcon, PlayIcon, StopCircleIcon, SearchIcon, MoreHorizontalIcon, Trash2Icon, PencilIcon, PowerIcon, CodeXmlIcon, CopyIcon, CheckIcon, ActivityIcon, ServerIcon, TerminalIcon, AlertCircleIcon, } from "lucide-react" import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter, SheetClose, } from "@/components/ui/sheet" // ─── helpers ────────────────────────────────────────────────────────────────── function serverFor(id: string) { return servers.find((s) => s.id === id) } function statusConfig(status: RouterContainer["status"]) { return { running: { dot: "bg-emerald-500 animate-pulse", badge: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20", label: "Running", }, stopped: { dot: "bg-muted-foreground", badge: "bg-muted/50 text-muted-foreground border-border", label: "Stopped", }, error: { dot: "bg-red-500 animate-pulse", badge: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20", label: "Error", }, }[status] } // ─── RSC generator ──────────────────────────────────────────────────────────── function generateContainerRsc(c: RouterContainer): string { const srv = serverFor(c.serverId) const lines: string[] = [] lines.push(`# RouterOS Container — ${c.name}`) if (srv) lines.push(`# Сервер: ${srv.name} (${srv.host})`) lines.push(`# Образ: ${c.image}:${c.tag}`) lines.push(`# RouterOS 7.4+ · /container`) lines.push(``) // interface for (const iface of c.interfaces) { lines.push(`/interface/veth/add name=${iface} address=172.17.0.2/24 gateway=172.17.0.1`) } lines.push(``) // envs if (c.envs.length > 0) { lines.push(`/container/envs/add name=${c.name}-envs \\`) for (const { key, value } of c.envs) { lines.push(` ${key}="${value}" \\`) } lines.push(``) } // mounts for (const m of c.mounts) { lines.push(`/container/mounts/add name=${c.name}-mount-${m.dst.replace(/\//g, "-").slice(1)} \\`) if (m.src) lines.push(` src=${m.src} \\`) lines.push(` dst=${m.dst}`) lines.push(``) } // container lines.push(`/container/add \\`) lines.push(` remote-image=${c.image}:${c.tag} \\`) lines.push(` interface=${c.interfaces[0] ?? "veth-container"} \\`) if (c.envs.length > 0) lines.push(` envlist=${c.name}-envs \\`) if (c.mounts.length > 0) lines.push(` mounts=${c.mounts.map((m) => `${c.name}-mount-${m.dst.replace(/\//g, "-").slice(1)}`).join(",")} \\`) if (c.cmd) lines.push(` cmd="${c.cmd}" \\`) if (c.startOnBoot) lines.push(` start-on-boot=yes \\`) if (c.comment) lines.push(` comment="${c.comment}" \\`) lines.push(` logging=yes`) return lines.join("\n") } // ─── Export Sheet ───────────────────────────────────────────────────────────── function ExportSheet({ open, container, onClose }: { open: boolean; container: RouterContainer | null; onClose: () => void }) { const [copied, setCopied] = useState(false) const code = useMemo(() => container ? generateContainerRsc(container) : "", [container]) function handleCopy() { navigator.clipboard.writeText(code).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000) }) } return ( { if (!v) onClose() }}>
Экспорт Container RouterOS 7.4+ · /container · /interface/veth
            {code.split("\n").map((line, i) => {
              const isComment = line.startsWith("#")
              const isCmd = /^\//.test(line.trimStart())
              const isParam = /^\s+[a-z]/.test(line)
              return (
                
                  {line}{"\n"}
                
              )
            })}
          
}>Закрыть
) } // ─── Container card ─────────────────────────────────────────────────────────── function ContainerCard({ container, onExport, }: { container: RouterContainer onExport: () => void }) { const srv = serverFor(container.serverId) const cfg = statusConfig(container.status) return (
{container.name} {cfg.label}
} /> {container.status === "running" ? ( Остановить ) : ( Запустить )} Логи Редактировать Экспорт .rsc Перезапустить Удалить
{/* image */}
{container.image}:{container.tag}
{/* server */} {srv && (
{srv.name}
)} {/* uptime + stats */} {container.status === "running" && (
{container.uptime && (
{container.uptime}
)} {container.cpu !== undefined && (
CPU 50 ? "text-amber-500" : "text-foreground")}> {container.cpu}%
)} {container.memMb !== undefined && (
RAM {container.memMb} МБ
)}
)} {/* interfaces */} {container.interfaces.length > 0 && (
{container.interfaces.map((i) => ( {i} ))}
)} {/* mounts */} {container.mounts.length > 0 && (
{container.mounts.map((m, idx) => (
{m.dst} {m.src && <>{m.src}}
))}
)} {container.comment && (

{container.comment}

)}
) } // ════════════════════════════════════════════════════════════════════════════ export default function ContainersPage() { const [search, setSearch] = useState("") const [statusFilter, setStatusFilter] = useState("all") const [exportContainer, setExportContainer] = useState(null) const filtered = useMemo(() => { return routerContainers.filter((c) => { if (statusFilter !== "all" && c.status !== statusFilter) return false if (!search) return true const q = search.toLowerCase() return ( c.name.toLowerCase().includes(q) || c.image.toLowerCase().includes(q) || (serverFor(c.serverId)?.name.toLowerCase().includes(q) ?? false) ) }) }, [search, statusFilter]) const running = routerContainers.filter((c) => c.status === "running").length const stopped = routerContainers.filter((c) => c.status === "stopped").length const errors = routerContainers.filter((c) => c.status === "error").length return (
Новый контейнер } />
{/* KPI */}
{[ { label: "Всего", value: routerContainers.length, icon: }, { label: "Running", value: running, icon: }, { label: "Stopped", value: stopped, icon: }, { label: "Ошибок", value: errors, icon: }, ].map((s) => (

{s.label}

{s.value}

))}
{/* Info banner */}

Контейнеры доступны с RouterOS 7.4+

Поддерживаются Docker-совместимые образы. Требуется установка пакета container. Интерфейсы veth создаются автоматически.

{/* Toolbar */}
setSearch(e.target.value)} />
{(["all", "running", "stopped", "error"] as const).map((s) => ( ))}
{filtered.length} контейнеров
{/* Grid */} {filtered.length === 0 ? (

Контейнеры не найдены

Добавьте первый контейнер или измените фильтр

) : (
{filtered.map((c) => ( setExportContainer(c)} /> ))}
)} {/* RouterOS reference */}
{[ { title: "Установка пакета", lines: [ "# Скачать пакет container:", "# mikrotik.com → Software", "", "/system/package/install", " container", "", "# Перезагрузить:", "/system/reboot", ], }, { title: "Создать контейнер", lines: [ "# veth интерфейс:", "/interface/veth/add \\", " name=veth-nginx \\", " address=172.17.0.2/24 \\", " gateway=172.17.0.1", "", "# Контейнер:", "/container/add \\", " remote-image=nginx:alpine \\", " interface=veth-nginx", ], }, { title: "Управление", lines: [ "# Список:", "/container/print", "", "# Запуск:", "/container/start 0", "", "# Остановка:", "/container/stop 0", "", "# Логи:", "/container/shell 0", ], }, ].map((b) => (

{b.title}

                      {b.lines.join("\n")}
                    
))}
setExportContainer(null)} />
) }