fix(traffic): спрашивать endpoint MM в sheet подключения JH
Docker images / prepare-release (push) Successful in 6s
Docker images / backend-image (push) Successful in 1m31s
Docker images / frontend-image (push) Failing after 2m38s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 46s
Docker images / publish-release (push) Skipped

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-09-06 22:16:41 +07:00
co-authored by Cursor
parent 1e9312acbd
commit 95dcd3df58
7 changed files with 194 additions and 56 deletions
+118 -27
View File
@@ -3,15 +3,25 @@
import { useEffect, useMemo, useState } from "react"
import { toast } from "sonner"
import { FormField } from "@/components/form-kit"
import { Alert, AlertDescription, AlertTitle } from "@/components/reui/alert"
import { Frame, FramePanel } from "@/components/reui/frame"
import { downloadText } from "@/components/reui-kit/code-export-sheet"
import { Button } from "@/components/ui/button"
import { CopyIcon } from "lucide-react"
import { Input } from "@/components/ui/input"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
Sheet, SheetContent, SheetHeader, SheetTitle,
SheetDescription, SheetFooter, SheetClose,
} from "@/components/ui/sheet"
import { applyTrafficFlowOverlay } from "@/shared/api/traffic-flow"
import { applyTrafficFlowOverlay, getTrafficFlowSettings } from "@/shared/api/traffic-flow"
import type { ServerRead } from "@mmapp/contracts/servers"
import type { TrafficFlowOverlayResult } from "@mmapp/contracts/traffic-flow"
import type { TrafficFlowHostFile, TrafficFlowOverlayResult } from "@mmapp/contracts/traffic-flow"
import {
CheckIcon,
CopyIcon,
DownloadIcon,
InfoIcon,
} from "lucide-react"
function FlowOverlaySheet({
open,
@@ -31,21 +41,46 @@ function FlowOverlaySheet({
[servers],
)
const [serverId, setServerId] = useState("")
const [endpoint, setEndpoint] = useState("")
const [busy, setBusy] = useState(false)
const [result, setResult] = useState<TrafficFlowOverlayResult | null>(null)
const [copied, setCopied] = useState(false)
const [tab, setTab] = useState("wg-quick")
useEffect(() => {
if (!open) return
setResult(null)
setCopied(false)
setTab("wg-quick")
setServerId(jumpHosts[0] ? String(jumpHosts[0].id) : "")
}, [open, jumpHosts])
void getTrafficFlowSettings(backendUrl)
.then((s) => setEndpoint(s.publicEndpoint))
.catch(() => setEndpoint(""))
}, [open, jumpHosts, backendUrl])
const formats = useMemo((): TrafficFlowHostFile[] => {
if (!result) return []
return [
...result.hostFiles,
{
id: "peer",
label: "[Peer]",
filename: "wg-flow-peer.conf",
code: result.linuxPeerBlock,
},
]
}, [result])
const active = formats.find((f) => f.id === tab) ?? formats[0]
const canSubmit = Boolean(serverId && endpoint.trim()) && !busy
async function handleSubmit() {
if (!serverId) return
if (!serverId || !endpoint.trim()) return
setBusy(true)
try {
const res = await applyTrafficFlowOverlay(backendUrl, serverId)
const res = await applyTrafficFlowOverlay(backendUrl, serverId, endpoint.trim())
setResult(res)
setTab(res.hostFiles[0]?.id ?? "peer")
toast.success(`wg-flow на ${res.address}`)
onDone?.(res)
} catch (e) {
@@ -55,16 +90,26 @@ function FlowOverlaySheet({
}
}
function handleCopy() {
const code = active?.code ?? ""
if (!code) return
void navigator.clipboard.writeText(code).then(() => {
setCopied(true)
toast.success("Скопировано")
setTimeout(() => setCopied(false), 1800)
})
}
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent side="right" className="w-full sm:max-w-md flex flex-col gap-0 p-0">
<SheetContent side="right" className="w-full sm:max-w-xl flex flex-col gap-0 p-0">
<SheetHeader className="px-6 pt-6 pb-4 border-b shrink-0">
<SheetTitle>Подключить jump-host</SheetTitle>
<SheetDescription>
Создать wg-flow на выбранном MikroTik и направить Traffic Flow на collector MM. Хост Docker уже должен слушать WireGuard.
Настроит wg-flow и Traffic Flow на MikroTik и сразу выдаст файлы для Linux-хоста Docker MM (wg-quick / compose / firewall).
</SheetDescription>
</SheetHeader>
<div className="flex-1 overflow-y-auto px-6 py-5 flex flex-col gap-5">
<div className="flex-1 min-h-0 overflow-y-auto px-6 py-5 flex flex-col gap-5">
<FormField label="Jump-host" required>
<select
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs outline-none"
@@ -79,35 +124,81 @@ function FlowOverlaySheet({
))}
</select>
</FormField>
<FormField
label="Публичный IP или DNS хоста Docker MM"
required
hint="Откуда JH стучится на WG listen (51821). Хост с wg-quick, не контейнер backend."
>
<Input
className="font-mono"
value={endpoint}
onChange={(e) => setEndpoint(e.target.value)}
placeholder="203.0.113.10"
autoComplete="off"
/>
</FormField>
{result ? (
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between gap-2">
<p className="text-xs text-muted-foreground">Пир для хоста MM (`wg set` или допишите conf):</p>
<Button
type="button"
size="sm"
variant="outline"
onClick={() => {
void navigator.clipboard.writeText(result.linuxPeerBlock)
toast.success("Скопировано")
}}
>
<CopyIcon className="size-3.5" />
Копировать
</Button>
</div>
<pre className="text-[11px] font-mono bg-muted/40 border rounded-md p-3 whitespace-pre-wrap">{result.linuxPeerBlock}</pre>
<div className="flex flex-col gap-4 min-h-0">
<Alert>
<InfoIcon />
<AlertTitle>Ключи и UDP 4739</AlertTitle>
<AlertDescription>
Приватный ключ хоста в SQLite панели не кладите в git. UDP 4739 публикуйте только на WG-IP, не на 0.0.0.0.
</AlertDescription>
</Alert>
<ul className="text-xs text-muted-foreground flex flex-col gap-1">
{result.steps.map((s) => (
<li key={s}>{s}</li>
))}
</ul>
{formats.length > 0 && active ? (
<div className="flex flex-col gap-3 min-h-0">
<Tabs
value={tab}
onValueChange={(v) => {
setTab(String(v))
setCopied(false)
}}
className="shrink-0 gap-0"
>
<TabsList className="h-9 w-full">
{formats.map((f) => (
<TabsTrigger key={f.id} value={f.id} className="flex-1 px-1.5 text-xs sm:text-sm">
{f.label}
</TabsTrigger>
))}
</TabsList>
</Tabs>
<Frame dense className="flex min-h-0 flex-col">
<FramePanel className="relative flex min-h-0 flex-col overflow-hidden p-0">
<pre className="px-4 py-3.5 text-[12px] font-mono leading-[1.65] whitespace-pre-wrap break-all select-all min-h-[12rem]">
{active.code}
</pre>
</FramePanel>
</Frame>
<div className="flex items-center justify-end gap-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => downloadText(active.filename, active.code)}
>
<DownloadIcon className="size-3.5" />
Файл
</Button>
<Button type="button" size="sm" onClick={handleCopy}>
{copied ? <CheckIcon className="size-3.5" /> : <CopyIcon className="size-3.5" />}
{copied ? "Скопировано" : "Копировать"}
</Button>
</div>
</div>
) : null}
</div>
) : null}
</div>
<SheetFooter className="px-6 py-4 border-t shrink-0 flex-row gap-2">
<SheetClose render={<Button variant="outline" />}>Закрыть</SheetClose>
<Button disabled={!serverId || busy} onClick={() => { void handleSubmit() }}>
<Button disabled={!canSubmit} onClick={() => { void handleSubmit() }}>
{busy ? "Подключение…" : "Подключить"}
</Button>
</SheetFooter>