- 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]>
141 lines
3.8 KiB
TypeScript
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",
|
|
}))
|
|
}
|