feat(gre): enhance GRE tunnel status handling and IPsec configuration
- Introduced a new `greStatusMeta` function to streamline status retrieval and default handling. - Updated GRE tunnel command generation to improve IPsec configuration handling, including optional fields for encryption and authentication algorithms. - Enhanced the GRE tunnels data grid to display IPsec settings more clearly, including handling cases where certain parameters may be undefined. - Refactored status display logic in the GRE page to utilize the new status meta function for consistency. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
+17
-10
@@ -68,6 +68,10 @@ const STATUS_MAP: Record<GreStatus, { label: string; dot: string }> = {
|
|||||||
down: { label: "Down", dot: "bg-red-500" },
|
down: { label: "Down", dot: "bg-red-500" },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function greStatusMeta(status: GreStatus | undefined) {
|
||||||
|
return STATUS_MAP[status ?? "degraded"] ?? STATUS_MAP.degraded
|
||||||
|
}
|
||||||
|
|
||||||
// ─── RouterOS code generator ─────────────────────────────────────────────────
|
// ─── RouterOS code generator ─────────────────────────────────────────────────
|
||||||
|
|
||||||
function generateRosCommands(t: GreTunnel, serverById: Record<string, Server>): string {
|
function generateRosCommands(t: GreTunnel, serverById: Record<string, Server>): string {
|
||||||
@@ -103,10 +107,10 @@ function generateRosCommands(t: GreTunnel, serverById: Record<string, Server>):
|
|||||||
lines.push(` address=${t.localInnerIp} \\`)
|
lines.push(` address=${t.localInnerIp} \\`)
|
||||||
lines.push(` interface=${t.name}`)
|
lines.push(` interface=${t.name}`)
|
||||||
|
|
||||||
// IPsec manual equivalent
|
// IPsec: live CHR имеет только ipsec-secret; proposal — у моков/формы
|
||||||
if (t.ipsec) {
|
if (t.ipsec?.encAlg && t.ipsec.authAlg) {
|
||||||
const ikeMode = t.ipsec.ikeVersion === "ikev2" ? "ike2" : "ike1"
|
const ikeMode = t.ipsec.ikeVersion === "ikev1" ? "ike1" : "ike2"
|
||||||
const pfsGroup = t.ipsec.pfs ? t.ipsec.dhGroup : "none"
|
const pfsGroup = t.ipsec.pfs ? (t.ipsec.dhGroup ?? "none") : "none"
|
||||||
|
|
||||||
lines.push("")
|
lines.push("")
|
||||||
lines.push("# ── IPsec (авто через ipsec-secret; ручной эквивалент) ───────")
|
lines.push("# ── IPsec (авто через ipsec-secret; ручной эквивалент) ───────")
|
||||||
@@ -123,13 +127,16 @@ function generateRosCommands(t: GreTunnel, serverById: Record<string, Server>):
|
|||||||
lines.push(` enc-algorithms=${ENC_ROS[t.ipsec.encAlg]} \\`)
|
lines.push(` enc-algorithms=${ENC_ROS[t.ipsec.encAlg]} \\`)
|
||||||
lines.push(` auth-algorithms=${AUTH_ROS[t.ipsec.authAlg]} \\`)
|
lines.push(` auth-algorithms=${AUTH_ROS[t.ipsec.authAlg]} \\`)
|
||||||
lines.push(` pfs-group=${pfsGroup} \\`)
|
lines.push(` pfs-group=${pfsGroup} \\`)
|
||||||
lines.push(` lifetime=${t.ipsec.lifetime}`)
|
lines.push(` lifetime=${t.ipsec.lifetime ?? "1d"}`)
|
||||||
lines.push("")
|
lines.push("")
|
||||||
lines.push(`/ip ipsec policy add \\`)
|
lines.push(`/ip ipsec policy add \\`)
|
||||||
lines.push(` src-address=${t.localAddress !== "0.0.0.0" ? t.localAddress + "/32" : "0.0.0.0/0"} \\`)
|
lines.push(` src-address=${t.localAddress !== "0.0.0.0" ? t.localAddress + "/32" : "0.0.0.0/0"} \\`)
|
||||||
lines.push(` dst-address=${t.remoteAddress}/32 \\`)
|
lines.push(` dst-address=${t.remoteAddress}/32 \\`)
|
||||||
lines.push(` proposal=${t.name} \\`)
|
lines.push(` proposal=${t.name} \\`)
|
||||||
lines.push(` tunnel=yes`)
|
lines.push(` tunnel=yes`)
|
||||||
|
} else if (t.ipsec) {
|
||||||
|
lines.push("")
|
||||||
|
lines.push("# IPsec: peer/policy создаёт RouterOS по ipsec-secret")
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join("\n")
|
return lines.join("\n")
|
||||||
@@ -138,7 +145,7 @@ function generateRosCommands(t: GreTunnel, serverById: Record<string, Server>):
|
|||||||
// ─── small ui helpers ────────────────────────────────────────────────────────
|
// ─── small ui helpers ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function TunnelStatus({ status }: { status: GreStatus }) {
|
function TunnelStatus({ status }: { status: GreStatus }) {
|
||||||
const s = STATUS_MAP[status]
|
const s = greStatusMeta(status)
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1.5 text-sm">
|
<span className="inline-flex items-center gap-1.5 text-sm">
|
||||||
<span className={`size-1.5 rounded-full ${s.dot}`} />
|
<span className={`size-1.5 rounded-full ${s.dot}`} />
|
||||||
@@ -737,7 +744,7 @@ export default function GrePage() {
|
|||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{poolTunnels.map((t, tunnelIndex) => (
|
{poolTunnels.map((t, tunnelIndex) => (
|
||||||
<div key={`${t.id}:${t.serverId}:${t.name}:${tunnelIndex}`} className="flex items-center gap-2 border border-border rounded-md px-3 py-1.5 bg-muted/30 text-xs">
|
<div key={`${t.id}:${t.serverId}:${t.name}:${tunnelIndex}`} className="flex items-center gap-2 border border-border rounded-md px-3 py-1.5 bg-muted/30 text-xs">
|
||||||
<span className={`size-1.5 rounded-full ${STATUS_MAP[t.status].dot}`} />
|
<span className={`size-1.5 rounded-full ${greStatusMeta(t.status).dot}`} />
|
||||||
<span className="font-mono font-medium">{t.name}</span>
|
<span className="font-mono font-medium">{t.name}</span>
|
||||||
<span className="text-muted-foreground">{t.localInnerIp} ↔ {t.remoteInnerIp}</span>
|
<span className="text-muted-foreground">{t.localInnerIp} ↔ {t.remoteInnerIp}</span>
|
||||||
{t.ipsec && <LockIcon className="size-3 text-emerald-400" />}
|
{t.ipsec && <LockIcon className="size-3 text-emerald-400" />}
|
||||||
@@ -798,8 +805,8 @@ export default function GrePage() {
|
|||||||
codePreviewTunnel ? (
|
codePreviewTunnel ? (
|
||||||
<div className="flex flex-wrap gap-3 text-xs shrink-0">
|
<div className="flex flex-wrap gap-3 text-xs shrink-0">
|
||||||
<span className="flex items-center gap-1.5">
|
<span className="flex items-center gap-1.5">
|
||||||
<span className={`size-1.5 rounded-full ${STATUS_MAP[codePreviewTunnel.status].dot}`} />
|
<span className={`size-1.5 rounded-full ${greStatusMeta(codePreviewTunnel.status).dot}`} />
|
||||||
{STATUS_MAP[codePreviewTunnel.status].label}
|
{greStatusMeta(codePreviewTunnel.status).label}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-muted-foreground">·</span>
|
<span className="text-muted-foreground">·</span>
|
||||||
<span>{serverById[codePreviewTunnel.serverId]?.name}</span>
|
<span>{serverById[codePreviewTunnel.serverId]?.name}</span>
|
||||||
@@ -814,7 +821,7 @@ export default function GrePage() {
|
|||||||
<span className="text-muted-foreground">·</span>
|
<span className="text-muted-foreground">·</span>
|
||||||
<span className="flex items-center gap-1 text-success">
|
<span className="flex items-center gap-1 text-success">
|
||||||
<LockIcon className="size-3" />
|
<LockIcon className="size-3" />
|
||||||
IPsec {IKE_LABELS[codePreviewTunnel.ipsec.ikeVersion]}
|
IPsec {codePreviewTunnel.ipsec.ikeVersion ? IKE_LABELS[codePreviewTunnel.ipsec.ikeVersion] : "PSK"}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const STATUS_MAP: Record<GreStatus, { label: string; dot: string }> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TunnelStatus({ status }: { status: GreStatus }) {
|
function TunnelStatus({ status }: { status: GreStatus }) {
|
||||||
const s = STATUS_MAP[status]
|
const s = STATUS_MAP[status] ?? STATUS_MAP.degraded
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1.5 text-sm">
|
<span className="inline-flex items-center gap-1.5 text-sm">
|
||||||
<span className={cn("size-1.5 rounded-full", s.dot)} />
|
<span className={cn("size-1.5 rounded-full", s.dot)} />
|
||||||
@@ -208,14 +208,21 @@ function GreTunnelsDataGrid({
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const t = row.original
|
const t = row.original
|
||||||
if (!t.ipsec) return <span className="text-xs text-muted-foreground">—</span>
|
if (!t.ipsec) return <span className="text-xs text-muted-foreground">—</span>
|
||||||
|
const enc = t.ipsec.encAlg ? ENC_LABELS[t.ipsec.encAlg] : undefined
|
||||||
|
const auth = t.ipsec.authAlg ? AUTH_LABELS[t.ipsec.authAlg] : undefined
|
||||||
|
const dh = t.ipsec.dhGroup ? DH_LABELS[t.ipsec.dhGroup] : undefined
|
||||||
|
const ike = t.ipsec.ikeVersion ? IKE_LABELS[t.ipsec.ikeVersion] : undefined
|
||||||
|
if (!enc && !auth && !dh && !ike) {
|
||||||
|
return <span className="text-xs text-muted-foreground">PSK · auto</span>
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<span className="text-xs font-mono">
|
<span className="text-xs font-mono">
|
||||||
{ENC_LABELS[t.ipsec.encAlg]} / {AUTH_LABELS[t.ipsec.authAlg]}
|
{[enc, auth].filter(Boolean).join(" / ") || "PSK"}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-muted-foreground font-mono">
|
<span className="text-xs text-muted-foreground font-mono">
|
||||||
{DH_LABELS[t.ipsec.dhGroup].split(" ")[0]} · {IKE_LABELS[t.ipsec.ikeVersion]}
|
{[dh?.split(" ")[0], ike].filter(Boolean).join(" · ")}
|
||||||
{t.ipsec.pfs && " · PFS"}
|
{t.ipsec.pfs ? " · PFS" : ""}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
+7
-6
@@ -531,12 +531,13 @@ export type DscpMode = "inherit" | number
|
|||||||
|
|
||||||
export interface GreIpsec {
|
export interface GreIpsec {
|
||||||
secret: string // ipsec-secret → auto-creates peer+policy+proposal
|
secret: string // ipsec-secret → auto-creates peer+policy+proposal
|
||||||
encAlg: IpsecEncAlg // proposal enc-algorithms
|
/** Live CHR отдаёт только secret; proposal-поля есть у моков / формы */
|
||||||
authAlg: IpsecAuthAlg // proposal auth-algorithms
|
encAlg?: IpsecEncAlg
|
||||||
dhGroup: IpsecDhGroup // proposal pfs-group / peer dh-group
|
authAlg?: IpsecAuthAlg
|
||||||
ikeVersion: IkeVersion // peer exchange-mode
|
dhGroup?: IpsecDhGroup
|
||||||
lifetime: string // proposal lifetime (e.g. "1d 00:00:00")
|
ikeVersion?: IkeVersion
|
||||||
pfs: boolean // perfect forward secrecy
|
lifetime?: string
|
||||||
|
pfs?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GreTunnel {
|
export interface GreTunnel {
|
||||||
|
|||||||
Reference in New Issue
Block a user