Files
MikrotikManager/backend/src/services/config-apply-plan.ts
T
DenozordecandCursor b4a3c3a925 feat(filters, recursive-routes): enhance configuration history and live data handling
- Introduced configuration history management in Filters and Recursive Routes pages, allowing users to view and restore previous configurations.
- Updated state management to handle live data loading and error states more effectively, improving user experience during data fetching.
- Added new components for displaying configuration history and integrated them into existing pages.
- Enhanced API interactions to support fetching and applying configuration revisions, ensuring data consistency across the application.
- Updated tests to cover new functionalities and ensure reliability.

Co-authored-by: Cursor <[email protected]>
2026-09-11 12:15:36 +07:00

141 lines
3.8 KiB
TypeScript

import {
hasManagedCommentPrefix,
isOwnedRecursiveComment,
stripManagedRecursiveComment,
} from "../managed-markers.js"
export type RosFilterRuleLike = {
".id"?: string
chain?: string
rule?: string
comment?: string
}
export type BgpInApplyAction = "patch" | "create" | "delete" | "noop"
export interface BgpInApplyPlan {
action: BgpInApplyAction
managedId?: string
conflictIds: string[]
}
function isInBgpIn(rule: RosFilterRuleLike): boolean {
return (rule.chain ?? "").trim().toLowerCase() === "bgp-in"
}
export function planBgpInApply(
existing: RosFilterRuleLike[],
rulesCount: number,
): BgpInApplyPlan {
const managed = existing.find(
(r) => isInBgpIn(r) && hasManagedCommentPrefix(r.comment ?? ""),
)
const conflictIds = existing
.filter((r) =>
isInBgpIn(r) &&
!hasManagedCommentPrefix(r.comment ?? "") &&
/bgp-communities/i.test(r.rule ?? ""),
)
.map((r) => r[".id"])
.filter((id): id is string => Boolean(id))
if (rulesCount > 0) {
return {
action: managed?.[".id"] ? "patch" : "create",
managedId: managed?.[".id"],
conflictIds,
}
}
if (managed?.[".id"]) {
return { action: "delete", managedId: managed[".id"], conflictIds }
}
return { action: "noop", conflictIds }
}
export type RosRouteLike = {
".id"?: string
comment?: string
static?: string
dynamic?: string
blackhole?: string
unreachable?: string
prohibit?: string
"dst-address"?: string
gateway?: string
}
export function planRecursiveApply(existing: RosRouteLike[]): { deleteIds: string[] } {
return {
deleteIds: existing
.filter((r) => isOwnedRecursiveComment(r.comment))
.map((r) => r[".id"])
.filter((id): id is string => Boolean(id)),
}
}
export function unmanagedRouteIds(existing: RosRouteLike[]): string[] {
return existing
.filter((r) => Boolean(r[".id"]) && !isOwnedRecursiveComment(r.comment))
.map((r) => r[".id"] as string)
}
export function userRecursiveComment(comment: string | undefined): string {
return stripManagedRecursiveComment(comment ?? "")
}
function isIpGateway(gw: string): boolean {
return /^\d{1,3}(\.\d{1,3}){3}(?:%\S+)?$/.test(gw.trim())
}
export function isManagedRecursiveRoute(r: RosRouteLike): boolean {
if ((r.static ?? "false") !== "true") return false
if ((r.dynamic ?? "false") === "true") return false
if ((r.blackhole ?? "false") === "true") return false
if ((r.unreachable ?? "false") === "true") return false
if ((r.prohibit ?? "false") === "true") return false
const dst = r["dst-address"] ?? ""
const gw = r.gateway ?? ""
if (!dst || !gw) return false
if (!isIpGateway(gw)) return false
return isOwnedRecursiveComment(r.comment)
}
export interface MappedRecursiveRoute {
id: string
dstAddress: string
gateway: string
distance: number
scope: number | null
targetScope: number | null
routingTable: string
checkGateway: string
country: string
comment: string
disabled: boolean
}
export function mapRosManagedRoutes(
rosRoutes: Array<RosRouteLike & {
distance?: string
scope?: string
"target-scope"?: string
"routing-table"?: string
"check-gateway"?: string
disabled?: string
}>,
): MappedRecursiveRoute[] {
return rosRoutes.filter(isManagedRecursiveRoute).map((r, i) => ({
id: r[".id"] ?? `ros-${i}`,
dstAddress: r["dst-address"] ?? "",
gateway: r.gateway ?? "",
distance: Number.parseInt(r.distance ?? "1", 10) || 1,
scope: r.scope ? (Number.parseInt(r.scope, 10) || null) : null,
targetScope: r["target-scope"] ? (Number.parseInt(r["target-scope"], 10) || null) : null,
routingTable: r["routing-table"] ?? "main",
checkGateway: r["check-gateway"] ?? "",
country: "",
comment: userRecursiveComment(r.comment),
disabled: r.disabled === "true",
}))
}