import { z } from "zod" const context = { id: z.string().min(1), rosId: z.string().min(1), serverId: z.string().min(1), serverName: z.string(), family: z.enum(["ip", "ip6"]) } export const firewallFailureSchema = z.object({ serverId: z.string().min(1), serverName: z.string().optional(), family: z.enum(["ip", "ip6"]).optional(), table: z.enum(["filter", "nat", "mangle", "raw", "address-list"]).optional(), error: z.string() }) export type FirewallFailure = z.infer export const firewallResponseSchema = z.object({ failures: z.array(firewallFailureSchema).default([]), rules: z.array(z.object({ ...context, table: z.enum(["filter", "nat", "mangle", "raw"]), chain: z.string(), action: z.string(), proto: z.string(), src: z.string(), dst: z.string(), port: z.string(), iface: z.string(), comment: z.string(), enabled: z.boolean(), hits: z.number().int().nonnegative(), log: z.boolean().optional(), logPrefix: z.string().optional(), tlsHost: z.string().optional(), connRateLimit: z.string().optional(), layer7Proto: z.string().optional(), nfqueueId: z.number().int().nonnegative().optional(), })).refine(rows => new Set(rows.map(row => row.id)).size === rows.length), addressLists: z.array(z.object({ ...context, list: z.string(), address: z.string(), comment: z.string(), disabled: z.boolean(), timeout: z.string().optional() })) .refine(rows => new Set(rows.map(row => row.id)).size === rows.length), }) export const firewallServersSchema = z.array(z.object({ id: z.number().int().positive(), name: z.string(), host: z.string(), enabled: z.boolean(), country: z.string(), type: z.enum(["jump-host", "exit-node", "home-router"]), site: z.string().default(""), asn: z.string().default(""), status: z.enum(["online", "offline", "degraded"]).nullable().default(null), latency: z.number().finite().nullable().default(null), })).refine(rows => new Set(rows.map(row => row.id)).size === rows.length) export type FirewallBackendServer = z.infer[number] export function matchesFirewallFailure(failure: FirewallFailure, row: { serverId?: string; family?: string; table?: string }, addressList = false) { return failure.serverId === row.serverId && (!failure.family || failure.family === row.family) && (!failure.table || failure.table === (addressList ? "address-list" : row.table)) } export function mergeFirewallRows(previous: T[], incoming: T[], failures: FirewallFailure[], enabledServerIds: Set, addressList = false): T[] { return [...incoming.filter(row => !failures.some(f => matchesFirewallFailure(f, row, addressList))), ...previous.filter(row => !!row.serverId && enabledServerIds.has(row.serverId) && failures.some(f => matchesFirewallFailure(f, row, addressList)))] }