- {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"}
-
- )
- })}
-
-
- {code.split("\n").map((line, i) => {
- const isComment = line.startsWith("#")
- const isSection = isComment && line.includes("──")
- const isKey = /^\s+[a-z]/.test(line)
- return (
-
- {line}{"\n"}
-
- )
- })}
-
-
- {code.split("\n").map((line, i) => {
- const isComment = line.startsWith("#")
- const isSection = isComment && line.includes("──")
- const isKey = /^\s+[a-z]/.test(line)
- return (
-
- {line}
- {"\n"}
-
- )
- })}
-
-
- {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"}
-
- )
- })}
-
- {s.label}
-{s.value}
-- WireGuard — live-интеграция RouterOS 7.x -
-- {isLive - ? "Опрос /interface/wireguard на включённых серверах. Создание, импорт .rsc/.conf и экспорт с роутера." - : "Сейчас mock-режим. Переключитесь в live в настройках, чтобы применять изменения на MikroTik."} -
-{b.title}
-
+
{b.lines.join("\n")}
+ {lines.map((line, i) => (
+
+ {line || " "}
+
+ ))}
+
+ )
+}
+
+export interface CodeExportSheetProps {
+ open: boolean
+ onClose: () => void
+ title: string
+ description?: ReactNode
+ formats: CodeExportFormat[]
+ /** Initial format id when sheet opens */
+ initialFormatId?: string
+ /** Optional live fetch from device */
+ onLiveFetch?: (formatId: string) => void
+ liveBusy?: boolean
+ liveLabel?: string
+ className?: string
+ /** Extra content under format tabs (e.g. meta strip) */
+ beforeCode?: ReactNode
+}
+
+/**
+ * Shared code export Sheet — ReUI Frame + sticky footer.
+ * Preview DNA: https://reui.io/preview/base/sheet-8 · Frame https://reui.io/docs/components/base/frame
+ */
+function CodeExportSheet({
+ open,
+ onClose,
+ title,
+ description,
+ formats,
+ initialFormatId,
+ onLiveFetch,
+ liveBusy,
+ liveLabel = "Подтянуть с роутера (с private-key)",
+ className,
+ beforeCode,
+}: CodeExportSheetProps) {
+ const firstId = formats[0]?.id ?? "default"
+ const [tab, setTab] = useState(initialFormatId ?? firstId)
+ const [copied, setCopied] = useState(false)
+
+ useEffect(() => {
+ if (!open) return
+ setTab(initialFormatId && formats.some((f) => f.id === initialFormatId)
+ ? initialFormatId
+ : firstId)
+ setCopied(false)
+ }, [open, initialFormatId, firstId, formats])
+
+ const active = useMemo(
+ () => formats.find((f) => f.id === tab) ?? formats[0],
+ [formats, tab],
+ )
+
+ const code = active?.code ?? ""
+ const emptyMessage = active?.emptyMessage
+ const filename = active?.filename ?? "export.txt"
+ const canCopy = Boolean(code) && !emptyMessage
+
+ function handleCopy() {
+ if (!canCopy) return
+ void navigator.clipboard.writeText(code).then(() => {
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ })
+ }
+
+ const showTabs = formats.length > 1
+
+ return (
+ {emptyMessage}
+ ) : null} +
- {code.split("\n").map((line, i) => {
- const isComment = line.startsWith("#")
- const isCmd = line.trimStart().startsWith("/interface") || line.trimStart().startsWith("/ip")
- const isSection = line.startsWith("[")
- const isParam = /^\s+[a-z]/.test(line) || /^[A-Za-z]+=/.test(line)
- return (
-
- {line}{"\n"}
-
+
+ onRequestLiveExport(
+ formatId === "peer" ? "peer-conf" : formatId === "conf" ? "conf" : "rsc",
)
- })}
-
-