refactor(traffic): update traffic flow host file generation and configuration scripts
Docker images / prepare-release (push) Successful in 6s
Docker images / backend-image (push) Successful in 1m39s
Docker images / frontend-image (push) Successful in 3m2s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 43s
Docker images / publish-release (push) Successful in 9s

- Renamed and refactored functions for better clarity, including `buildHostComposeSnippet` to `buildHostComposeOverride` and `buildHostNftSnippet` to `buildHostLinuxInstallSh`.
- Introduced a new script for Linux installation of WireGuard, enhancing the setup process for Docker hosts.
- Updated the `generateNativeConf` function to conditionally include the listen port in the configuration.
- Adjusted the `FlowOverlaySheet` component to default to the new Linux tab and improved the user interface for selecting jump-hosts.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-09-06 23:20:12 +07:00
co-authored by Cursor
parent f0dc5acfd3
commit 13889005f8
6 changed files with 149 additions and 90 deletions
+3 -3
View File
@@ -60,10 +60,10 @@ function highlightLine(line: string): string {
return "text-foreground/90"
}
function CodeBlock({ code }: { code: string }) {
function CodeBlock({ code, className }: { code: string; className?: string }) {
const lines = code.length ? code.split("\n") : [""]
return (
<pre className="px-4 py-3.5 text-[12px] font-mono leading-[1.65] whitespace-pre-wrap break-all select-all">
<pre className={cn("px-4 py-3.5 text-[12px] font-mono leading-[1.65] whitespace-pre-wrap break-all select-all", className)}>
{lines.map((line, i) => (
<span key={i} className={cn("block", highlightLine(line))}>
{line || " "}
@@ -281,4 +281,4 @@ function CodeExportSheet({
)
}
export { CodeExportSheet, downloadText }
export { CodeExportSheet, CodeBlock, downloadText }
+61 -48
View File
@@ -5,9 +5,17 @@ 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 { CodeBlock, downloadText } from "@/components/reui-kit/code-export-sheet"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { ScrollArea } from "@/components/ui/scroll-area"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
Sheet, SheetContent, SheetHeader, SheetTitle,
@@ -15,7 +23,7 @@ import {
} from "@/components/ui/sheet"
import { applyTrafficFlowOverlay } from "@/shared/api/traffic-flow"
import type { ServerRead } from "@mmapp/contracts/servers"
import type { TrafficFlowHostFile, TrafficFlowOverlayResult } from "@mmapp/contracts/traffic-flow"
import type { TrafficFlowOverlayResult } from "@mmapp/contracts/traffic-flow"
import {
CheckIcon,
CopyIcon,
@@ -45,13 +53,13 @@ function FlowOverlaySheet({
const [busy, setBusy] = useState(false)
const [result, setResult] = useState<TrafficFlowOverlayResult | null>(null)
const [copied, setCopied] = useState(false)
const [tab, setTab] = useState("wg-quick")
const [tab, setTab] = useState("linux")
useEffect(() => {
if (!open) return
setResult(null)
setCopied(false)
setTab("wg-quick")
setTab("linux")
const first = jumpHosts[0]
const nextId = first ? String(first.id) : ""
setServerId(nextId)
@@ -64,20 +72,12 @@ function FlowOverlaySheet({
if (selected) setEndpoint(selected.host)
}
const formats = useMemo((): TrafficFlowHostFile[] => {
if (!result) return []
return [
...result.hostFiles,
{
id: "peer",
label: "[Peer]",
filename: "wg-flow-peer.conf",
code: result.linuxPeerBlock,
},
]
}, [result])
const formats = result?.hostFiles ?? []
const active = formats.find((f) => f.id === tab) ?? formats[0]
const selectedHost = jumpHosts.find((s) => String(s.id) === serverId)
const selectedLabel = selectedHost
? `${selectedHost.name || selectedHost.host} (${selectedHost.host})`
: "Выберите сервер…"
const canSubmit = Boolean(serverId && endpoint.trim()) && !busy
async function handleSubmit() {
@@ -86,7 +86,7 @@ function FlowOverlaySheet({
try {
const res = await applyTrafficFlowOverlay(backendUrl, serverId, endpoint.trim())
setResult(res)
setTab(res.hostFiles[0]?.id ?? "peer")
setTab(res.hostFiles[0]?.id ?? "linux")
toast.success(`wg-flow на ${res.address}`)
onDone?.(res)
} catch (e) {
@@ -108,27 +108,35 @@ function FlowOverlaySheet({
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<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>
<SheetContent
side="right"
className="flex w-full flex-col gap-0 overflow-hidden p-0 sm:max-w-2xl"
>
<SheetHeader className="shrink-0 gap-1 border-b px-5 pt-5 pb-4 pr-12">
<SheetTitle className="text-base font-semibold tracking-tight">
Подключить jump-host
</SheetTitle>
<SheetDescription>
Создаст wg-flow на выбранном MikroTik (сервер, listen 13232) и сразу выдаст wg-quick / compose для Linux-хоста Docker MM (клиент).
Создаст wg-flow на MikroTik (сервер, listen 13232) и выдаст готовый bash для Linux-хоста Docker MM (клиент).
</SheetDescription>
</SheetHeader>
<div className="flex-1 min-h-0 overflow-y-auto px-6 py-5 flex flex-col gap-5">
<div className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto px-5 py-4">
<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"
value={serverId}
onChange={(e) => handleServerChange(e.target.value)}
<Select
value={serverId || undefined}
onValueChange={(v) => handleServerChange(String(v ?? ""))}
>
<option value="">Выберите сервер</option>
{jumpHosts.map((s) => (
<option key={s.id} value={s.id}>
{s.name || s.host} ({s.host})
</option>
))}
</select>
<SelectTrigger className="h-9 w-full min-w-0">
<SelectValue>{selectedLabel}</SelectValue>
</SelectTrigger>
<SelectContent align="start" className="min-w-(--anchor-width)">
{jumpHosts.map((s) => (
<SelectItem key={s.id} value={String(s.id)}>
{s.name || s.host} ({s.host})
</SelectItem>
))}
</SelectContent>
</Select>
</FormField>
<FormField
label="Публичный IP или DNS jump-host"
@@ -144,21 +152,24 @@ function FlowOverlaySheet({
/>
</FormField>
{result ? (
<div className="flex flex-col gap-4 min-h-0">
<Alert>
<div className="flex min-h-0 flex-col gap-4">
<Alert variant="success">
<InfoIcon />
<AlertTitle>Ключи и UDP 4739</AlertTitle>
<AlertTitle>Linux-хост /opt/cdn-mm</AlertTitle>
<AlertDescription>
Приватный ключ хоста в SQLite панели не кладите в git. UDP 4739 публикуйте только на WG-IP хоста, не на 0.0.0.0 контейнера.
Скопируйте вкладку Linux и выполните от root. WG клиент к JH:13232; контейнер слушает только WG-IP:4739, не 0.0.0.0. Ключ не кладите в git.
</AlertDescription>
</Alert>
<ul className="text-xs text-muted-foreground flex flex-col gap-1">
<ul className="flex flex-col gap-1.5">
{result.steps.map((s) => (
<li key={s}>{s}</li>
<li key={s} className="flex items-start gap-2 text-sm">
<CheckIcon className="mt-0.5 size-3.5 shrink-0 text-success" />
<span>{s}</span>
</li>
))}
</ul>
{formats.length > 0 && active ? (
<div className="flex flex-col gap-3 min-h-0">
<div className="flex min-h-0 flex-col gap-3">
<Tabs
value={tab}
onValueChange={(v) => {
@@ -169,17 +180,17 @@ function FlowOverlaySheet({
>
<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">
<TabsTrigger key={f.id} value={f.id} className="flex-1 px-2 text-xs sm:text-sm">
{f.label}
</TabsTrigger>
))}
</TabsList>
</Tabs>
<Frame dense className="flex min-h-0 flex-col">
<Frame dense className="flex min-h-0 flex-col overflow-hidden">
<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>
<ScrollArea className="h-full min-h-0 max-h-[min(52vh,22rem)]">
<CodeBlock code={active.code} className="whitespace-pre break-normal" />
</ScrollArea>
</FramePanel>
</Frame>
<div className="flex items-center justify-end gap-2">
@@ -202,8 +213,10 @@ function FlowOverlaySheet({
</div>
) : null}
</div>
<SheetFooter className="px-6 py-4 border-t shrink-0 flex-row gap-2">
<SheetClose render={<Button variant="outline" />}>Закрыть</SheetClose>
<SheetFooter className="shrink-0 flex-row items-center justify-between gap-3 border-t px-5 py-3.5 sm:flex-row">
<SheetClose render={<Button type="button" variant="outline" className="shrink-0" />}>
Закрыть
</SheetClose>
<Button disabled={!canSubmit} onClick={() => { void handleSubmit() }}>
{busy ? "Подключение…" : "Подключить"}
</Button>