quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 8s
quality / changes (push) Successful in 5s
quality / docker-check (push) Skipped
quality / web (push) Successful in 51s
quality / api (push) Successful in 43s
CD / quality (push) Successful in 1m43s
CD / publish (push) Successful in 1m38s
- Added functionality to toggle individual IPs for services, allowing for dynamic management of IP health status. - Enhanced the health check configuration in the UI, enabling users to set parameters directly from the settings page. - Updated service views to include IP health tracking, improving visibility into the status of each IP associated with a service. - Refactored relevant components to support the new IP toggling feature, ensuring a seamless user experience. This commit significantly enhances the health management capabilities of services, providing users with more control over IP configurations and health monitoring.
888 lines
31 KiB
JavaScript
888 lines
31 KiB
JavaScript
// src/constants.ts
|
|
var SYNC_SYNCED = "synced";
|
|
var SYNC_PENDING_PUSH = "pending_push";
|
|
var SYNC_PENDING_DELETE = "pending_delete";
|
|
var SYNC_CONFLICT = "conflict";
|
|
var SYNC_ERROR = "error";
|
|
var CERT_OK = "ok";
|
|
var CERT_WARNING = "warning";
|
|
var CERT_EXPIRED = "expired";
|
|
var CERT_ERROR = "error";
|
|
var CERT_UNKNOWN = "unknown";
|
|
var CERT_MONITOR_AUTO = "auto";
|
|
var CERT_MONITOR_REQUIRED = "required";
|
|
var CERT_MONITOR_SKIPPED = "skipped";
|
|
var CERT_MONITORING_VALUES = [
|
|
CERT_MONITOR_AUTO,
|
|
CERT_MONITOR_REQUIRED,
|
|
CERT_MONITOR_SKIPPED
|
|
];
|
|
|
|
// src/validators.ts
|
|
var NAME_RE = /^(@|\*|[a-zA-Z0-9_]([a-zA-Z0-9_-]*[a-zA-Z0-9_])?(\.[a-zA-Z0-9_]([a-zA-Z0-9_-]*[a-zA-Z0-9_])?)*)$/;
|
|
var IPV4_RE = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
|
|
var IPV6_RE = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
|
var ALLOWED_TYPES = ["A", "AAAA", "CNAME", "TXT", "MX", "NS", "SRV", "CAA"];
|
|
var ValidationError = class extends Error {
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = "ValidationError";
|
|
}
|
|
};
|
|
function validateDnsRecord(recordType, name, content, ttl, proxied) {
|
|
const rt = recordType.toUpperCase();
|
|
if (!ALLOWED_TYPES.includes(rt)) {
|
|
throw new ValidationError(`unsupported record type: ${recordType}`);
|
|
}
|
|
if (!NAME_RE.test(name)) {
|
|
throw new ValidationError(`invalid record name: ${name}`);
|
|
}
|
|
if (ttl !== 1 && (ttl < 60 || ttl > 86400)) {
|
|
throw new ValidationError("ttl must be 1 (auto) or 60-86400");
|
|
}
|
|
if (proxied && !["A", "AAAA", "CNAME"].includes(rt)) {
|
|
throw new ValidationError("proxied only allowed for A, AAAA, CNAME");
|
|
}
|
|
switch (rt) {
|
|
case "A":
|
|
if (!IPV4_RE.test(content)) {
|
|
throw new ValidationError("A record requires valid IPv4");
|
|
}
|
|
break;
|
|
case "AAAA":
|
|
if (!IPV6_RE.test(content)) {
|
|
throw new ValidationError("AAAA record requires valid IPv6");
|
|
}
|
|
break;
|
|
case "CNAME":
|
|
case "NS":
|
|
if (!content || content.includes(" ")) {
|
|
throw new ValidationError("CNAME/NS requires valid hostname");
|
|
}
|
|
break;
|
|
case "TXT":
|
|
if (!content || content.length > 2048) {
|
|
throw new ValidationError("TXT content length 1-2048");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
function certStatusFromExpiry(daysLeft) {
|
|
if (daysLeft < 0) return CERT_EXPIRED;
|
|
if (daysLeft <= 30) return CERT_WARNING;
|
|
return CERT_OK;
|
|
}
|
|
function shouldMonitorService(service, group) {
|
|
if (!service.enabled) return false;
|
|
if (!service.service_group_id) return true;
|
|
return group?.enabled ?? false;
|
|
}
|
|
function isValidIpv4(ip) {
|
|
const parts = ip.split(".");
|
|
if (parts.length !== 4) return false;
|
|
return parts.every((p) => {
|
|
const n = Number(p);
|
|
return Number.isInteger(n) && n >= 0 && n <= 255;
|
|
});
|
|
}
|
|
function isIpLiteral(value) {
|
|
const v = value.trim();
|
|
if (!v) return false;
|
|
if (isValidIpv4(v)) return true;
|
|
return IPV6_RE.test(v);
|
|
}
|
|
|
|
// src/subdomain.ts
|
|
function dnsNameToSubdomainLabel(recordName, zoneName) {
|
|
const rn = recordName.trim().replace(/\.+$/, "");
|
|
const zn = zoneName.trim().replace(/\.+$/, "");
|
|
if (!rn || !zn) return null;
|
|
if (rn === "*") return "*";
|
|
const wildcardFqdn = `*.${zn}`;
|
|
if (rn.toLowerCase() === wildcardFqdn.toLowerCase()) return "*";
|
|
if (rn.toLowerCase() === zn.toLowerCase()) return "@";
|
|
const zoneSuffix = `.${zn}`;
|
|
if (rn.toLowerCase().endsWith(zoneSuffix.toLowerCase())) {
|
|
const prefix = rn.slice(0, rn.length - zoneSuffix.length);
|
|
return prefix || "@";
|
|
}
|
|
if (!rn.includes(".")) return rn;
|
|
return null;
|
|
}
|
|
function subdomainLabelToFqdn(label, zoneName) {
|
|
return label === "@" ? zoneName : `${label}.${zoneName}`;
|
|
}
|
|
function normalizeDnsRecordName(recordName, zoneName) {
|
|
const label = dnsNameToSubdomainLabel(recordName, zoneName);
|
|
if (label == null) {
|
|
return recordName.trim().replace(/\.+$/, "");
|
|
}
|
|
return subdomainLabelToFqdn(label, zoneName);
|
|
}
|
|
function dnsRecordNamesMatch(left, right, zoneName) {
|
|
return normalizeDnsRecordName(left, zoneName).toLowerCase() === normalizeDnsRecordName(right, zoneName).toLowerCase();
|
|
}
|
|
|
|
// src/parse-fqdn.ts
|
|
function fqdnToDisplay(hostname, zoneName) {
|
|
if (hostname === "@") {
|
|
return zoneName;
|
|
}
|
|
return `${hostname}.${zoneName}`;
|
|
}
|
|
function parseFqdn(fqdn, knownZones) {
|
|
const normalized = fqdn.trim().toLowerCase();
|
|
if (!normalized) {
|
|
return null;
|
|
}
|
|
const zones = [...knownZones].sort((a, b) => b.length - a.length);
|
|
for (const zone of zones) {
|
|
const zoneLower = zone.toLowerCase();
|
|
if (normalized === zoneLower) {
|
|
return {
|
|
zoneName: zone,
|
|
hostname: "@",
|
|
fqdn: fqdnToDisplay("@", zone)
|
|
};
|
|
}
|
|
const suffix = `.${zoneLower}`;
|
|
if (normalized.endsWith(suffix)) {
|
|
const prefix = normalized.slice(0, -suffix.length);
|
|
if (prefix) {
|
|
return {
|
|
zoneName: zone,
|
|
hostname: prefix,
|
|
fqdn: fqdnToDisplay(prefix, zone)
|
|
};
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function bindingToFqdn(binding) {
|
|
return binding.fqdn ?? fqdnToDisplay(binding.hostname, binding.zone_name);
|
|
}
|
|
|
|
// src/schemas.ts
|
|
import { z } from "zod";
|
|
var certMonitoringSchema = z.enum(["auto", "required", "skipped"]);
|
|
var lbModeSchema = z.enum(["round_robin", "failover", "weighted"]);
|
|
var healthCheckTypeSchema = z.enum(["tcp", "http", "ping", "dns"]);
|
|
var domainEnvironmentSchema = z.enum(["prod", "staging", "dev"]);
|
|
var domainMonitorTypeSchema = z.enum(["http", "ping", "dns"]);
|
|
var ipHealthStateSchema = z.enum(["up", "down", "degraded", "unknown"]);
|
|
var nodeHealthStateSchema = z.enum([
|
|
"unknown",
|
|
"checking",
|
|
"healthy",
|
|
"degraded",
|
|
"unhealthy",
|
|
"disabled"
|
|
]);
|
|
var healthCheckProviderSchema = z.enum(["local", "cloudflare"]);
|
|
var healthCheckScopeSchema = z.enum(["binding", "group"]);
|
|
var ipHealthStatusSchema = z.object({
|
|
scope: healthCheckScopeSchema,
|
|
ref_id: z.number(),
|
|
ip: z.string(),
|
|
status: ipHealthStateSchema,
|
|
latency_ms: z.number().nullable(),
|
|
consecutive_failures: z.number(),
|
|
consecutive_successes: z.number().optional().default(0),
|
|
last_checked_at: z.string().nullable(),
|
|
last_error: z.string().nullable()
|
|
});
|
|
var serviceIpHealthSchema = z.object({
|
|
ip: z.string(),
|
|
status: ipHealthStateSchema,
|
|
latency_ms: z.number().nullable()
|
|
});
|
|
var groupSchema = z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
slug: z.string(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var groupWithStatsSchema = groupSchema.extend({
|
|
domain_count: z.number()
|
|
});
|
|
var serviceGroupTypeSchema = z.enum([
|
|
"vpn",
|
|
"network",
|
|
"internet",
|
|
"bgp",
|
|
"custom"
|
|
]);
|
|
var serviceGroupSchema = z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
type: serviceGroupTypeSchema.catch("custom"),
|
|
icon: z.string().nullable().default(null),
|
|
domain: z.string().nullable().default(null),
|
|
enabled: z.coerce.boolean(),
|
|
lb_mode: lbModeSchema.catch("round_robin"),
|
|
health_check_enabled: z.coerce.boolean().default(false),
|
|
health_check_type: healthCheckTypeSchema.catch("tcp"),
|
|
health_check_port: z.number().nullable().default(null),
|
|
health_check_path: z.string().nullable().default(null),
|
|
health_check_expected_status: z.number().nullable().default(null),
|
|
health_check_interval_sec: z.number().default(30),
|
|
health_check_timeout_ms: z.number().default(3e3),
|
|
health_check_verify_tls: z.coerce.boolean().default(false),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var serviceSchema = z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
slug: z.string(),
|
|
service_group_id: z.number().nullable().optional(),
|
|
subdomain: z.string().optional(),
|
|
enabled: z.coerce.boolean().optional(),
|
|
computed_fqdn: z.string().nullable().optional(),
|
|
lb_weight: z.number().default(1),
|
|
lb_priority: z.number().default(1),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var serviceDomainBindingSchema = z.object({
|
|
binding_id: z.number(),
|
|
domain_id: z.number(),
|
|
zone_name: z.string(),
|
|
hostname: z.string(),
|
|
fqdn: z.string(),
|
|
record_type: z.enum(["A", "CNAME"]).default("A"),
|
|
target_ips: z.array(z.string()).optional(),
|
|
target_ip: z.string().nullable().optional(),
|
|
target_ip_weights: z.record(z.string(), z.coerce.number()).optional(),
|
|
target_ip_priorities: z.record(z.string(), z.coerce.number()).optional(),
|
|
target_cname: z.string().nullable().optional(),
|
|
lb_mode: lbModeSchema.catch("round_robin"),
|
|
health_check_enabled: z.coerce.boolean().default(false),
|
|
health_check_type: healthCheckTypeSchema.catch("tcp"),
|
|
health_check_port: z.number().nullable().default(null),
|
|
health_check_path: z.string().nullable().default(null),
|
|
health_check_expected_status: z.number().nullable().default(null),
|
|
health_check_interval_sec: z.number().default(30),
|
|
health_check_timeout_ms: z.number().default(3e3),
|
|
health_check_verify_tls: z.coerce.boolean().default(false),
|
|
sync_status: z.string().nullable().default(null)
|
|
}).transform((binding) => ({
|
|
...binding,
|
|
target_ips: binding.target_ips && binding.target_ips.length > 0 ? binding.target_ips : binding.target_ip ? [binding.target_ip] : [],
|
|
target_ip_weights: binding.target_ip_weights ?? {},
|
|
target_ip_priorities: binding.target_ip_priorities ?? {},
|
|
target_cname: binding.target_cname?.trim() || null,
|
|
record_type: binding.target_cname?.trim() ? "CNAME" : binding.record_type ?? "A"
|
|
}));
|
|
var serviceViewSchema = serviceSchema.extend({
|
|
subdomain: z.string().default(""),
|
|
enabled: z.coerce.boolean().default(false),
|
|
ips: z.array(z.string()).default([]),
|
|
domains: z.array(serviceDomainBindingSchema).default([]),
|
|
health_status: ipHealthStateSchema.default("unknown"),
|
|
health_latency_ms: z.number().nullable().default(null),
|
|
ip_health: z.array(serviceIpHealthSchema).default([]),
|
|
ip_enabled: z.record(z.string(), z.boolean()).default({})
|
|
});
|
|
var serviceGroupViewSchema = serviceGroupSchema.extend({
|
|
services: z.array(serviceViewSchema).default([]),
|
|
health_status: ipHealthStateSchema.default("unknown"),
|
|
health_latency_ms: z.number().nullable().default(null)
|
|
});
|
|
var serviceGroupsResponseSchema = z.object({
|
|
groups: z.array(serviceGroupViewSchema).default([]),
|
|
ungrouped: z.array(serviceViewSchema).default([])
|
|
});
|
|
var domainSchema = z.object({
|
|
id: z.number(),
|
|
group_id: z.number().nullable(),
|
|
zone_name: z.string(),
|
|
cf_zone_id: z.string(),
|
|
status: z.string(),
|
|
cert_monitoring: certMonitoringSchema.default("auto"),
|
|
environment: domainEnvironmentSchema.nullable().optional().default(null),
|
|
last_synced_at: z.string().nullable(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var domainListItemSchema = domainSchema.extend({
|
|
group_name: z.string().nullable(),
|
|
service_count: z.number(),
|
|
health_status: ipHealthStateSchema.default("unknown"),
|
|
health_latency_ms: z.number().nullable().default(null),
|
|
tags: z.array(z.string()).default([])
|
|
});
|
|
var serviceBindingSchema = z.object({
|
|
id: z.number(),
|
|
domain_id: z.number(),
|
|
service_id: z.number(),
|
|
hostname: z.string(),
|
|
dns_record_id: z.number().nullable(),
|
|
zone_name: z.string(),
|
|
group_id: z.number().nullable(),
|
|
group_name: z.string().nullable(),
|
|
service_name: z.string(),
|
|
service_slug: z.string(),
|
|
target_ip: z.string().nullable(),
|
|
target_ips: z.array(z.string()).optional(),
|
|
target_ip_weights: z.record(z.string(), z.coerce.number()).optional(),
|
|
target_ip_priorities: z.record(z.string(), z.coerce.number()).optional(),
|
|
lb_mode: lbModeSchema.catch("round_robin"),
|
|
health_check_enabled: z.coerce.boolean().default(false),
|
|
health_check_type: healthCheckTypeSchema.catch("tcp"),
|
|
health_check_port: z.number().nullable().default(null),
|
|
health_check_path: z.string().nullable().default(null),
|
|
health_check_expected_status: z.number().nullable().default(null),
|
|
health_check_interval_sec: z.number().default(30),
|
|
health_check_timeout_ms: z.number().default(3e3),
|
|
health_check_verify_tls: z.coerce.boolean().default(false),
|
|
sync_status: z.string().nullable().default(null),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
}).transform((binding) => ({
|
|
...binding,
|
|
target_ips: binding.target_ips && binding.target_ips.length > 0 ? binding.target_ips : binding.target_ip ? [binding.target_ip] : [],
|
|
target_ip_weights: binding.target_ip_weights ?? {},
|
|
target_ip_priorities: binding.target_ip_priorities ?? {}
|
|
}));
|
|
var dnsRecordSchema = z.object({
|
|
id: z.number(),
|
|
domain_id: z.number(),
|
|
cf_record_id: z.string().nullable(),
|
|
record_type: z.string(),
|
|
name: z.string(),
|
|
content: z.string(),
|
|
ttl: z.number(),
|
|
proxied: z.boolean(),
|
|
priority: z.number().nullable(),
|
|
sync_status: z.string(),
|
|
origin: z.string(),
|
|
last_error: z.string().nullable(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var certificateSchema = z.object({
|
|
id: z.number(),
|
|
domain_id: z.number(),
|
|
subdomain_id: z.number().nullable(),
|
|
hostname: z.string(),
|
|
expires_at: z.string().nullable(),
|
|
last_checked_at: z.string().nullable(),
|
|
last_error: z.string().nullable(),
|
|
status: z.string(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var createGroupSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435"),
|
|
slug: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 slug")
|
|
});
|
|
var ipv4Schema = z.string().regex(
|
|
/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/,
|
|
"\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 IPv4"
|
|
);
|
|
var nodeAddressSchema = z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 IP \u0438\u043B\u0438 hostname").max(255);
|
|
var healthCheckConfigFields = {
|
|
health_check_enabled: z.boolean().optional(),
|
|
health_check_type: healthCheckTypeSchema.optional(),
|
|
health_check_port: z.number().int().min(1).max(65535).nullable().optional(),
|
|
health_check_path: z.string().nullable().optional(),
|
|
health_check_expected_status: z.number().int().min(100).max(599).nullable().optional(),
|
|
health_check_interval_sec: z.number().int().min(5).max(3600).optional(),
|
|
health_check_timeout_ms: z.number().int().min(100).max(3e4).optional(),
|
|
health_check_verify_tls: z.boolean().optional()
|
|
};
|
|
var healthCheckConfigSchema = z.object(healthCheckConfigFields);
|
|
var serviceDomainInputSchema = z.object({
|
|
fqdn: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 FQDN"),
|
|
target_ips: z.array(ipv4Schema).optional(),
|
|
target_cname: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 CNAME-\u0446\u0435\u043B\u044C").optional(),
|
|
target_ip_weights: z.record(ipv4Schema, z.number().int().min(1).max(100)).optional(),
|
|
target_ip_priorities: z.record(ipv4Schema, z.number().int().min(1).max(100)).optional(),
|
|
lb_mode: lbModeSchema.optional(),
|
|
...healthCheckConfigFields
|
|
}).superRefine((data, ctx) => {
|
|
const hasIps = (data.target_ips?.length ?? 0) > 0;
|
|
const hasCname = Boolean(data.target_cname?.trim());
|
|
if (!hasIps && !hasCname) {
|
|
ctx.addIssue({
|
|
code: "custom",
|
|
message: "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 IP \u0438\u043B\u0438 CNAME-\u0446\u0435\u043B\u044C",
|
|
path: ["target_ips"]
|
|
});
|
|
}
|
|
if (hasIps && hasCname) {
|
|
ctx.addIssue({
|
|
code: "custom",
|
|
message: "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043B\u0438\u0431\u043E IP, \u043B\u0438\u0431\u043E CNAME-\u0446\u0435\u043B\u044C",
|
|
path: ["target_cname"]
|
|
});
|
|
}
|
|
});
|
|
var createServiceSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435"),
|
|
slug: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 slug")
|
|
});
|
|
var createServiceWithConfigSchema = createServiceSchema.extend({
|
|
service_group_id: z.number().nullable().optional(),
|
|
ips: z.array(ipv4Schema).default([]),
|
|
lb_weight: z.number().int().min(1).max(100).optional(),
|
|
lb_priority: z.number().int().min(1).max(100).optional(),
|
|
domains: z.array(serviceDomainInputSchema).default([])
|
|
});
|
|
var createServiceBindingSchema = z.object({
|
|
domain_id: z.string().min(1, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043E\u043C\u0435\u043D"),
|
|
service_id: z.string().min(1, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0435\u0440\u0432\u0438\u0441"),
|
|
hostname: z.string().optional(),
|
|
target_ip: z.string().optional()
|
|
});
|
|
var updateDomainGroupSchema = z.object({
|
|
group_id: z.number().nullable(),
|
|
status: z.string().optional()
|
|
});
|
|
var createDomainSchema = z.object({
|
|
zone_name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F \u0437\u043E\u043D\u044B"),
|
|
group_id: z.string()
|
|
});
|
|
var loginSchema = z.object({
|
|
username: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F"),
|
|
password: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043F\u0430\u0440\u043E\u043B\u044C")
|
|
});
|
|
var createDnsRecordSchema = z.object({
|
|
record_type: z.enum(["A", "AAAA", "CNAME", "TXT", "MX"]),
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F"),
|
|
content: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435"),
|
|
ttl: z.number().int().min(1),
|
|
proxied: z.boolean()
|
|
});
|
|
var subdomainSchema = z.object({
|
|
id: z.number(),
|
|
domain_id: z.number(),
|
|
name: z.string(),
|
|
fqdn: z.string(),
|
|
enabled: z.boolean(),
|
|
cert_monitoring: certMonitoringSchema.default("auto"),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var createSubdomainSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F \u043F\u043E\u0434\u0434\u043E\u043C\u0435\u043D\u0430")
|
|
});
|
|
var updateSubdomainSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F \u043F\u043E\u0434\u0434\u043E\u043C\u0435\u043D\u0430").optional(),
|
|
enabled: z.boolean().optional(),
|
|
cert_monitoring: certMonitoringSchema.optional()
|
|
});
|
|
var updateDomainSchema = z.object({
|
|
group_id: z.number().nullable().optional(),
|
|
status: z.string().optional(),
|
|
cert_monitoring: certMonitoringSchema.optional(),
|
|
environment: domainEnvironmentSchema.nullable().optional(),
|
|
tags: z.array(z.string().min(1).max(64)).max(20).optional()
|
|
});
|
|
var bulkUpdateDomainsSchema = z.object({
|
|
ids: z.array(z.number().int().positive()).min(1),
|
|
group_id: z.number().nullable().optional(),
|
|
environment: domainEnvironmentSchema.nullable().optional(),
|
|
tags_add: z.array(z.string().min(1).max(64)).max(20).optional()
|
|
});
|
|
var domainMonitorSchema = z.object({
|
|
id: z.number(),
|
|
domain_id: z.number(),
|
|
hostname: z.string(),
|
|
type: domainMonitorTypeSchema,
|
|
enabled: z.coerce.boolean(),
|
|
interval_sec: z.number(),
|
|
timeout_ms: z.number(),
|
|
path: z.string().nullable(),
|
|
expected_status: z.number().nullable(),
|
|
last_status: ipHealthStateSchema.default("unknown"),
|
|
last_latency_ms: z.number().nullable(),
|
|
last_checked_at: z.string().nullable(),
|
|
last_error: z.string().nullable(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var createDomainMonitorSchema = z.object({
|
|
hostname: z.string().min(1),
|
|
type: domainMonitorTypeSchema,
|
|
enabled: z.boolean().optional().default(true),
|
|
interval_sec: z.number().int().min(10).max(3600).optional().default(60),
|
|
timeout_ms: z.number().int().min(500).max(3e4).optional().default(5e3),
|
|
path: z.string().nullable().optional(),
|
|
expected_status: z.number().int().min(100).max(599).nullable().optional()
|
|
});
|
|
var domainMonitorResultSchema = z.object({
|
|
id: z.number(),
|
|
monitor_id: z.number(),
|
|
status: ipHealthStateSchema,
|
|
latency_ms: z.number().nullable(),
|
|
error: z.string().nullable(),
|
|
checked_at: z.string()
|
|
});
|
|
var notificationLogSchema = z.object({
|
|
id: z.number(),
|
|
kind: z.string(),
|
|
ref_type: z.string(),
|
|
ref_id: z.number().nullable(),
|
|
title: z.string(),
|
|
message: z.string(),
|
|
created_at: z.string()
|
|
});
|
|
var updateServiceConfigSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435").optional(),
|
|
slug: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 slug").optional(),
|
|
service_group_id: z.number().nullable().optional(),
|
|
ips: z.array(ipv4Schema).optional(),
|
|
lb_weight: z.number().int().min(1).max(100).optional(),
|
|
lb_priority: z.number().int().min(1).max(100).optional(),
|
|
domains: z.array(serviceDomainInputSchema).optional()
|
|
});
|
|
var createServiceGroupSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435"),
|
|
type: serviceGroupTypeSchema.default("custom"),
|
|
icon: z.string().nullable().optional(),
|
|
domain: z.string().nullable().optional(),
|
|
lb_mode: lbModeSchema.optional(),
|
|
...healthCheckConfigFields
|
|
});
|
|
var updateServiceGroupSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435").optional(),
|
|
type: serviceGroupTypeSchema.optional(),
|
|
icon: z.string().nullable().optional(),
|
|
domain: z.string().nullable().optional(),
|
|
lb_mode: lbModeSchema.optional(),
|
|
...healthCheckConfigFields
|
|
});
|
|
var toggleEnabledSchema = z.object({
|
|
enabled: z.boolean()
|
|
});
|
|
var toggleServiceIpSchema = z.object({
|
|
ip: ipv4Schema,
|
|
enabled: z.boolean()
|
|
});
|
|
var reorderServicesSchema = z.object({
|
|
group_id: z.union([z.number(), z.null()]).optional().default(null),
|
|
service_ids: z.array(z.number().int().positive()).min(1)
|
|
});
|
|
var healthStatusQuerySchema = z.object({
|
|
scope: healthCheckScopeSchema,
|
|
ref_id: z.coerce.number().int().positive()
|
|
});
|
|
var serviceNodeSchema = z.object({
|
|
id: z.number(),
|
|
service_id: z.number(),
|
|
address: z.string(),
|
|
protocol: z.string(),
|
|
port: z.number().nullable(),
|
|
enabled: z.coerce.boolean(),
|
|
priority: z.number(),
|
|
weight: z.number(),
|
|
health_status: nodeHealthStateSchema,
|
|
health_check_id: z.number().nullable(),
|
|
consecutive_failures: z.number(),
|
|
consecutive_successes: z.number(),
|
|
last_check_at: z.string().nullable(),
|
|
last_failure_reason: z.string().nullable(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var createServiceNodeSchema = z.object({
|
|
address: nodeAddressSchema,
|
|
protocol: z.enum(["tcp", "http", "https"]).optional().default("tcp"),
|
|
port: z.number().int().min(1).max(65535).nullable().optional(),
|
|
enabled: z.boolean().optional().default(true),
|
|
priority: z.number().int().min(1).max(100).optional().default(1),
|
|
weight: z.number().int().min(1).max(100).optional().default(1),
|
|
health_check_id: z.number().int().positive().nullable().optional()
|
|
});
|
|
var updateServiceNodeSchema = createServiceNodeSchema.partial();
|
|
var originHealthCheckSchema = z.object({
|
|
id: z.number(),
|
|
provider: healthCheckProviderSchema,
|
|
cf_healthcheck_id: z.string().nullable(),
|
|
cf_zone_id: z.string().nullable(),
|
|
name: z.string(),
|
|
protocol: z.string(),
|
|
path: z.string().nullable(),
|
|
method: z.string().nullable(),
|
|
timeout: z.number(),
|
|
interval_sec: z.number(),
|
|
retries: z.number(),
|
|
expected_status: z.number().nullable(),
|
|
consecutive_fails: z.number(),
|
|
consecutive_successes: z.number(),
|
|
suspended: z.coerce.boolean(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var createOriginHealthCheckSchema = z.object({
|
|
provider: healthCheckProviderSchema,
|
|
name: z.string().min(1).max(64),
|
|
cf_zone_id: z.string().nullable().optional(),
|
|
protocol: z.enum(["HTTP", "HTTPS", "TCP", "tcp", "http", "https"]).optional(),
|
|
path: z.string().nullable().optional(),
|
|
method: z.string().nullable().optional(),
|
|
timeout: z.number().int().min(1).max(60).optional(),
|
|
interval_sec: z.number().int().min(5).max(3600).optional(),
|
|
retries: z.number().int().min(0).max(10).optional(),
|
|
expected_status: z.number().int().min(100).max(599).nullable().optional(),
|
|
consecutive_fails: z.number().int().min(1).max(20).optional(),
|
|
consecutive_successes: z.number().int().min(1).max(20).optional(),
|
|
suspended: z.boolean().optional(),
|
|
node_id: z.number().int().positive().optional()
|
|
});
|
|
var changeIpSchema = z.object({
|
|
from_ip: z.string().optional(),
|
|
to_ip: z.string().min(1).optional(),
|
|
node_id: z.number().int().positive().optional(),
|
|
dry_run: z.boolean().optional().default(false)
|
|
});
|
|
var changeDomainSchema = z.object({
|
|
from_domain_id: z.number().int().positive(),
|
|
to_domain_id: z.number().int().positive(),
|
|
hostnames: z.array(z.string().min(1)).optional(),
|
|
dry_run: z.boolean().optional().default(false)
|
|
});
|
|
|
|
// src/app-switcher.ts
|
|
import { z as z2 } from "zod";
|
|
var appSwitcherIconSchema = z2.enum([
|
|
"server",
|
|
"cloud",
|
|
"globe",
|
|
"dashboard",
|
|
"chart"
|
|
]);
|
|
var appSwitcherEntrySchema = z2.object({
|
|
id: z2.string(),
|
|
name: z2.string(),
|
|
subtitle: z2.string().optional(),
|
|
url: z2.string().url(),
|
|
icon: appSwitcherIconSchema.default("server"),
|
|
enabled: z2.boolean().optional(),
|
|
sort: z2.number().optional(),
|
|
shortcut: z2.string().optional()
|
|
});
|
|
var appSwitcherConfigSchema = z2.object({
|
|
menuLabel: z2.string().default("\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F"),
|
|
apps: z2.array(appSwitcherEntrySchema).min(1)
|
|
});
|
|
|
|
// src/integration-vps-tracker.ts
|
|
import { z as z3 } from "zod";
|
|
var cfdmBindingSyncItemSchema = z3.object({
|
|
bindingId: z3.number().int().positive(),
|
|
serviceId: z3.number().int().positive(),
|
|
serviceName: z3.string().min(1),
|
|
serviceSlug: z3.string().min(1),
|
|
fqdn: z3.string().min(1),
|
|
zoneName: z3.string().min(1),
|
|
hostname: z3.string(),
|
|
ips: z3.array(z3.string()),
|
|
/** CNAME-цель (FQDN), если binding — CNAME; для матчинга в VPS Tracker. */
|
|
cnameTarget: z3.string().optional(),
|
|
deleted: z3.boolean().optional()
|
|
});
|
|
var cfdmSyncBindingsBodySchema = z3.object({
|
|
bindings: z3.array(cfdmBindingSyncItemSchema),
|
|
/** Полная пересинхронизация: удалить CFDM-домены, которых нет в payload. */
|
|
fullSync: z3.boolean().optional()
|
|
}).refine((data) => data.fullSync === true || data.bindings.length >= 1, {
|
|
message: "bindings \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D, \u0435\u0441\u043B\u0438 fullSync \u043D\u0435 \u0437\u0430\u0434\u0430\u043D",
|
|
path: ["bindings"]
|
|
});
|
|
var appSettingsPatchSchema = z3.object({
|
|
vpsTrackerUrl: z3.string().url().or(z3.literal("")).optional(),
|
|
vpsTrackerIntegrationToken: z3.string().optional(),
|
|
vpsTrackerSyncEnabled: z3.boolean().optional(),
|
|
showQuickActions: z3.boolean().optional(),
|
|
healthCheckCron: z3.string().trim().min(1).max(64).optional(),
|
|
healthDegradedFailures: z3.number().int().min(1).max(20).optional(),
|
|
healthDownFailures: z3.number().int().min(1).max(50).optional(),
|
|
healthLatencyWarnMs: z3.number().int().min(50).max(6e4).optional(),
|
|
healthSuccessRecoveries: z3.number().int().min(1).max(20).optional()
|
|
}).superRefine((data, ctx) => {
|
|
if (data.healthDegradedFailures != null && data.healthDownFailures != null && data.healthDownFailures < data.healthDegradedFailures) {
|
|
ctx.addIssue({
|
|
code: "custom",
|
|
message: "\u043E\u0448\u0438\u0431\u043E\u043A \u0434\u043E down \u043D\u0435 \u043C\u0435\u043D\u044C\u0448\u0435, \u0447\u0435\u043C \u0434\u043E degraded",
|
|
path: ["healthDownFailures"]
|
|
});
|
|
}
|
|
});
|
|
var vpsTrackerEventSchema = z3.object({
|
|
event: z3.enum(["vps_down", "vps_up"]),
|
|
vps: z3.array(
|
|
z3.object({
|
|
id: z3.string().min(1),
|
|
ip: z3.string().optional(),
|
|
label: z3.string().optional()
|
|
})
|
|
),
|
|
timestamp: z3.string().datetime().optional()
|
|
});
|
|
|
|
// src/audit.ts
|
|
import { z as z4 } from "zod";
|
|
var AUDIT_SEVERITIES = ["info", "warning", "critical"];
|
|
var auditSeveritySchema = z4.enum(AUDIT_SEVERITIES);
|
|
var AUDIT_SOURCE_APPS = [
|
|
"portal",
|
|
"vps",
|
|
"cfdm",
|
|
"bgp",
|
|
"fw"
|
|
];
|
|
var auditSourceAppSchema = z4.enum(AUDIT_SOURCE_APPS);
|
|
var AUDIT_TARGET_TYPES = [
|
|
"user",
|
|
"settings",
|
|
"session",
|
|
"system",
|
|
"app_resource"
|
|
];
|
|
var auditTargetTypeSchema = z4.enum(AUDIT_TARGET_TYPES);
|
|
var auditLogEntrySchema = z4.object({
|
|
id: z4.string(),
|
|
event_id: z4.string().nullable(),
|
|
source_app: auditSourceAppSchema,
|
|
action: z4.string(),
|
|
severity: auditSeveritySchema,
|
|
actor_user_id: z4.string().nullable(),
|
|
actor_email: z4.string().nullable(),
|
|
actor_name: z4.string().nullable(),
|
|
target_type: auditTargetTypeSchema.nullable(),
|
|
target_id: z4.string().nullable(),
|
|
summary: z4.string(),
|
|
details: z4.record(z4.string(), z4.unknown()).nullable(),
|
|
ip: z4.string().nullable(),
|
|
created_at: z4.string()
|
|
});
|
|
var auditListQuerySchema = z4.object({
|
|
action: z4.string().optional(),
|
|
severity: auditSeveritySchema.optional(),
|
|
user_id: z4.string().optional(),
|
|
source_app: auditSourceAppSchema.optional(),
|
|
limit: z4.coerce.number().int().min(1).max(500).default(200)
|
|
});
|
|
var ingestAuditEventSchema = z4.object({
|
|
event_id: z4.string().min(1).max(128),
|
|
source_app: z4.enum(["vps", "cfdm", "bgp", "fw"]),
|
|
action: z4.string().min(1).max(200),
|
|
severity: auditSeveritySchema.optional(),
|
|
actor_user_id: z4.string().nullable().optional(),
|
|
actor_email: z4.string().email().nullable().optional(),
|
|
actor_name: z4.string().nullable().optional(),
|
|
target_type: auditTargetTypeSchema.nullable().optional(),
|
|
target_id: z4.string().nullable().optional(),
|
|
summary: z4.string().min(1).max(500),
|
|
details: z4.record(z4.string(), z4.unknown()).nullable().optional(),
|
|
ip: z4.string().nullable().optional(),
|
|
created_at: z4.string().optional()
|
|
});
|
|
export {
|
|
AUDIT_SEVERITIES,
|
|
AUDIT_SOURCE_APPS,
|
|
AUDIT_TARGET_TYPES,
|
|
CERT_ERROR,
|
|
CERT_EXPIRED,
|
|
CERT_MONITORING_VALUES,
|
|
CERT_MONITOR_AUTO,
|
|
CERT_MONITOR_REQUIRED,
|
|
CERT_MONITOR_SKIPPED,
|
|
CERT_OK,
|
|
CERT_UNKNOWN,
|
|
CERT_WARNING,
|
|
SYNC_CONFLICT,
|
|
SYNC_ERROR,
|
|
SYNC_PENDING_DELETE,
|
|
SYNC_PENDING_PUSH,
|
|
SYNC_SYNCED,
|
|
ValidationError,
|
|
appSettingsPatchSchema,
|
|
appSwitcherConfigSchema,
|
|
appSwitcherEntrySchema,
|
|
appSwitcherIconSchema,
|
|
auditListQuerySchema,
|
|
auditLogEntrySchema,
|
|
auditSeveritySchema,
|
|
auditSourceAppSchema,
|
|
auditTargetTypeSchema,
|
|
bindingToFqdn,
|
|
bulkUpdateDomainsSchema,
|
|
certMonitoringSchema,
|
|
certStatusFromExpiry,
|
|
certificateSchema,
|
|
cfdmBindingSyncItemSchema,
|
|
cfdmSyncBindingsBodySchema,
|
|
changeDomainSchema,
|
|
changeIpSchema,
|
|
createDnsRecordSchema,
|
|
createDomainMonitorSchema,
|
|
createDomainSchema,
|
|
createGroupSchema,
|
|
createOriginHealthCheckSchema,
|
|
createServiceBindingSchema,
|
|
createServiceGroupSchema,
|
|
createServiceNodeSchema,
|
|
createServiceSchema,
|
|
createServiceWithConfigSchema,
|
|
createSubdomainSchema,
|
|
dnsNameToSubdomainLabel,
|
|
dnsRecordNamesMatch,
|
|
dnsRecordSchema,
|
|
domainEnvironmentSchema,
|
|
domainListItemSchema,
|
|
domainMonitorResultSchema,
|
|
domainMonitorSchema,
|
|
domainMonitorTypeSchema,
|
|
domainSchema,
|
|
fqdnToDisplay,
|
|
groupSchema,
|
|
groupWithStatsSchema,
|
|
healthCheckConfigSchema,
|
|
healthCheckProviderSchema,
|
|
healthCheckScopeSchema,
|
|
healthCheckTypeSchema,
|
|
healthStatusQuerySchema,
|
|
ingestAuditEventSchema,
|
|
ipHealthStateSchema,
|
|
ipHealthStatusSchema,
|
|
isIpLiteral,
|
|
isValidIpv4,
|
|
lbModeSchema,
|
|
loginSchema,
|
|
nodeHealthStateSchema,
|
|
normalizeDnsRecordName,
|
|
notificationLogSchema,
|
|
originHealthCheckSchema,
|
|
parseFqdn,
|
|
reorderServicesSchema,
|
|
serviceBindingSchema,
|
|
serviceDomainBindingSchema,
|
|
serviceGroupSchema,
|
|
serviceGroupTypeSchema,
|
|
serviceGroupViewSchema,
|
|
serviceGroupsResponseSchema,
|
|
serviceIpHealthSchema,
|
|
serviceNodeSchema,
|
|
serviceSchema,
|
|
serviceViewSchema,
|
|
shouldMonitorService,
|
|
subdomainLabelToFqdn,
|
|
subdomainSchema,
|
|
toggleEnabledSchema,
|
|
toggleServiceIpSchema,
|
|
updateDomainGroupSchema,
|
|
updateDomainSchema,
|
|
updateServiceConfigSchema,
|
|
updateServiceGroupSchema,
|
|
updateServiceNodeSchema,
|
|
updateSubdomainSchema,
|
|
validateDnsRecord,
|
|
vpsTrackerEventSchema
|
|
};
|