feat(health): добавить Globalping и мультивыбор источников проб
CD / update-wiki (push) Successful in 8s
quality / commitlint (push) Skipped
quality / changes (push) Successful in 5s
quality / docker-check (push) Skipped
quality / web (push) Successful in 54s
quality / api (push) Successful in 46s
CD / quality (push) Successful in 1m49s
CD / publish (push) Successful in 1m40s

Несколько источников проб сразу и правило агрегации на сервисе вместо XOR Local/Cloudflare.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-08-19 18:32:18 +07:00
co-authored by Cursor
parent 4c4908558b
commit b9bea44dce
31 changed files with 2671 additions and 271 deletions
+257 -20
View File
@@ -15,6 +15,36 @@ declare const CERT_MONITOR_REQUIRED = "required";
declare const CERT_MONITOR_SKIPPED = "skipped";
declare const CERT_MONITORING_VALUES: readonly ["auto", "required", "skipped"];
declare const HEALTH_CHECK_PROVIDERS: readonly ["local", "cloudflare", "globalping"];
type HealthCheckProvider = (typeof HEALTH_CHECK_PROVIDERS)[number];
declare const HEALTH_STATUS_PROVIDERS: readonly ["local", "cloudflare", "globalping", "aggregate"];
type HealthStatusProvider = (typeof HEALTH_STATUS_PROVIDERS)[number];
declare const HEALTH_CHECK_AGGREGATES: readonly ["any", "all", "majority"];
type HealthCheckAggregate = (typeof HEALTH_CHECK_AGGREGATES)[number];
declare function normalizeProbeProvider(value: unknown): HealthCheckProvider;
declare function normalizeStatusProvider(value: unknown): HealthStatusProvider;
declare function uniqueHealthProviders(values: readonly unknown[]): HealthCheckProvider[];
declare function parseHealthProviders(json: unknown, fallback?: unknown): HealthCheckProvider[];
declare function serializeHealthProviders(providers: readonly HealthCheckProvider[]): string;
declare function parseHealthAggregate(value: unknown): HealthCheckAggregate;
declare function derivePrimaryProvider(providers: readonly HealthCheckProvider[]): HealthCheckProvider;
declare function targetProviders(target: {
providers?: readonly HealthCheckProvider[] | null;
provider?: HealthCheckProvider | null;
}): HealthCheckProvider[];
declare function targetHasProvider(target: {
providers?: readonly HealthCheckProvider[] | null;
provider?: HealthCheckProvider | null;
}, provider: HealthCheckProvider): boolean;
/**
* any — Down if at least one source is Down (ok only if all ok).
* all — Down only if every source is Down (ok if any ok).
* majority — Down if a strict majority of sources are Down (2 → both, 3 → ≥2).
*/
declare function aggregateHealthOk(oks: readonly boolean[], policy: HealthCheckAggregate): boolean;
declare function clampGlobalpingLimit(value: unknown, fallback?: number): number;
declare function parseGlobalpingLocations(value: unknown): string[];
interface ServiceGroup$1 {
id: number;
name: string;
@@ -32,6 +62,8 @@ interface ServiceGroup$1 {
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: HealthCheckProvider;
health_check_providers: HealthCheckProvider[];
health_check_aggregate: HealthCheckAggregate;
created_at: string;
updated_at: string;
}
@@ -62,6 +94,8 @@ interface ServiceBinding {
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: HealthCheckProvider;
health_check_providers: HealthCheckProvider[];
health_check_aggregate: HealthCheckAggregate;
routing_strategy: LbMode;
operation_version: number;
created_at: string;
@@ -93,6 +127,8 @@ interface ServiceBindingView {
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: HealthCheckProvider;
health_check_providers: HealthCheckProvider[];
health_check_aggregate: HealthCheckAggregate;
sync_status: string | null;
created_at: string;
updated_at: string;
@@ -118,6 +154,8 @@ interface ServiceDomainBindingView {
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: HealthCheckProvider;
health_check_providers: HealthCheckProvider[];
health_check_aggregate: HealthCheckAggregate;
sync_status: string | null;
}
interface ServiceView$1 {
@@ -189,7 +227,6 @@ type LbMode = "round_robin" | "failover" | "weighted";
type HealthCheckType = "tcp" | "http" | "ping" | "dns";
type IpHealthState = "up" | "down" | "degraded" | "unknown";
type NodeHealthState = "unknown" | "checking" | "healthy" | "degraded" | "unhealthy" | "disabled";
type HealthCheckProvider = "local" | "cloudflare";
type HealthCheckScope = "binding" | "group";
interface IpHealthStatus {
scope: HealthCheckScope;
@@ -202,7 +239,7 @@ interface IpHealthStatus {
last_checked_at: string | null;
last_error: string | null;
colo?: string | null;
provider?: HealthCheckProvider;
provider?: HealthStatusProvider;
}
interface ServiceIpHealth$1 {
ip: string;
@@ -210,7 +247,7 @@ interface ServiceIpHealth$1 {
latency_ms: number | null;
last_checked_at?: string | null;
last_error?: string | null;
provider?: HealthCheckProvider;
provider?: HealthStatusProvider;
colo?: string | null;
}
interface ServiceNode {
@@ -290,6 +327,8 @@ interface HealthCheckTarget {
timeout_ms: number;
verify_tls: boolean;
provider: HealthCheckProvider;
providers?: HealthCheckProvider[];
aggregate?: HealthCheckAggregate;
}
declare class ValidationError extends Error {
@@ -370,7 +409,24 @@ declare const nodeHealthStateSchema: z.ZodEnum<{
declare const healthCheckProviderSchema: z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>;
declare const healthStatusProviderSchema: z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
aggregate: "aggregate";
}>;
declare const healthCheckAggregateSchema: z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>;
declare const healthCheckProvidersSchema: z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>;
declare const healthCheckScopeSchema: z.ZodEnum<{
binding: "binding";
group: "group";
@@ -397,6 +453,8 @@ declare const ipHealthStatusSchema: z.ZodObject<{
provider: z.ZodOptional<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
aggregate: "aggregate";
}>>;
}, z.core.$strip>;
declare const serviceIpHealthSchema: z.ZodObject<{
@@ -413,6 +471,8 @@ declare const serviceIpHealthSchema: z.ZodObject<{
provider: z.ZodOptional<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
aggregate: "aggregate";
}>>;
colo: z.ZodOptional<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>;
@@ -425,6 +485,7 @@ declare const healthProbeLogSchema: z.ZodObject<{
provider: z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>;
status: z.ZodEnum<{
unknown: "unknown";
@@ -455,21 +516,21 @@ declare const groupWithStatsSchema: z.ZodObject<{
domain_count: z.ZodNumber;
}, z.core.$strip>;
declare const serviceGroupTypeSchema: z.ZodEnum<{
custom: "custom";
vpn: "vpn";
network: "network";
internet: "internet";
bgp: "bgp";
custom: "custom";
}>;
declare const serviceGroupSchema: z.ZodObject<{
id: z.ZodNumber;
name: z.ZodString;
type: z.ZodCatch<z.ZodEnum<{
custom: "custom";
vpn: "vpn";
network: "network";
internet: "internet";
bgp: "bgp";
custom: "custom";
}>>;
icon: z.ZodDefault<z.ZodNullable<z.ZodString>>;
domain: z.ZodDefault<z.ZodNullable<z.ZodString>>;
@@ -495,6 +556,17 @@ declare const serviceGroupSchema: z.ZodObject<{
health_check_provider: z.ZodCatch<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
created_at: z.ZodString;
updated_at: z.ZodString;
@@ -548,6 +620,17 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
health_check_provider: z.ZodCatch<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>, z.ZodTransform<{
@@ -570,7 +653,9 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
health_check_interval_sec: number;
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: "local" | "cloudflare";
health_check_provider: "local" | "cloudflare" | "globalping";
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
health_check_aggregate: "any" | "all" | "majority";
sync_status: string | null;
target_ip?: string | null | undefined;
}, {
@@ -589,7 +674,9 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
health_check_interval_sec: number;
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: "local" | "cloudflare";
health_check_provider: "local" | "cloudflare" | "globalping";
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
health_check_aggregate: "any" | "all" | "majority";
sync_status: string | null;
target_ips?: string[] | undefined;
target_ip?: string | null | undefined;
@@ -646,6 +733,17 @@ declare const serviceViewSchema: z.ZodObject<{
health_check_provider: z.ZodCatch<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>, z.ZodTransform<{
@@ -668,7 +766,9 @@ declare const serviceViewSchema: z.ZodObject<{
health_check_interval_sec: number;
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: "local" | "cloudflare";
health_check_provider: "local" | "cloudflare" | "globalping";
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
health_check_aggregate: "any" | "all" | "majority";
sync_status: string | null;
target_ip?: string | null | undefined;
}, {
@@ -687,7 +787,9 @@ declare const serviceViewSchema: z.ZodObject<{
health_check_interval_sec: number;
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: "local" | "cloudflare";
health_check_provider: "local" | "cloudflare" | "globalping";
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
health_check_aggregate: "any" | "all" | "majority";
sync_status: string | null;
target_ips?: string[] | undefined;
target_ip?: string | null | undefined;
@@ -716,6 +818,8 @@ declare const serviceViewSchema: z.ZodObject<{
provider: z.ZodOptional<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
aggregate: "aggregate";
}>>;
colo: z.ZodOptional<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>>>;
@@ -725,11 +829,11 @@ declare const serviceGroupViewSchema: z.ZodObject<{
id: z.ZodNumber;
name: z.ZodString;
type: z.ZodCatch<z.ZodEnum<{
custom: "custom";
vpn: "vpn";
network: "network";
internet: "internet";
bgp: "bgp";
custom: "custom";
}>>;
icon: z.ZodDefault<z.ZodNullable<z.ZodString>>;
domain: z.ZodDefault<z.ZodNullable<z.ZodString>>;
@@ -755,6 +859,17 @@ declare const serviceGroupViewSchema: z.ZodObject<{
health_check_provider: z.ZodCatch<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
created_at: z.ZodString;
updated_at: z.ZodString;
@@ -807,6 +922,17 @@ declare const serviceGroupViewSchema: z.ZodObject<{
health_check_provider: z.ZodCatch<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>, z.ZodTransform<{
@@ -829,7 +955,9 @@ declare const serviceGroupViewSchema: z.ZodObject<{
health_check_interval_sec: number;
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: "local" | "cloudflare";
health_check_provider: "local" | "cloudflare" | "globalping";
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
health_check_aggregate: "any" | "all" | "majority";
sync_status: string | null;
target_ip?: string | null | undefined;
}, {
@@ -848,7 +976,9 @@ declare const serviceGroupViewSchema: z.ZodObject<{
health_check_interval_sec: number;
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: "local" | "cloudflare";
health_check_provider: "local" | "cloudflare" | "globalping";
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
health_check_aggregate: "any" | "all" | "majority";
sync_status: string | null;
target_ips?: string[] | undefined;
target_ip?: string | null | undefined;
@@ -877,6 +1007,8 @@ declare const serviceGroupViewSchema: z.ZodObject<{
provider: z.ZodOptional<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
aggregate: "aggregate";
}>>;
colo: z.ZodOptional<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>>>;
@@ -895,11 +1027,11 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
id: z.ZodNumber;
name: z.ZodString;
type: z.ZodCatch<z.ZodEnum<{
custom: "custom";
vpn: "vpn";
network: "network";
internet: "internet";
bgp: "bgp";
custom: "custom";
}>>;
icon: z.ZodDefault<z.ZodNullable<z.ZodString>>;
domain: z.ZodDefault<z.ZodNullable<z.ZodString>>;
@@ -925,6 +1057,17 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
health_check_provider: z.ZodCatch<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
created_at: z.ZodString;
updated_at: z.ZodString;
@@ -977,6 +1120,17 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
health_check_provider: z.ZodCatch<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>, z.ZodTransform<{
@@ -999,7 +1153,9 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
health_check_interval_sec: number;
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: "local" | "cloudflare";
health_check_provider: "local" | "cloudflare" | "globalping";
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
health_check_aggregate: "any" | "all" | "majority";
sync_status: string | null;
target_ip?: string | null | undefined;
}, {
@@ -1018,7 +1174,9 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
health_check_interval_sec: number;
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: "local" | "cloudflare";
health_check_provider: "local" | "cloudflare" | "globalping";
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
health_check_aggregate: "any" | "all" | "majority";
sync_status: string | null;
target_ips?: string[] | undefined;
target_ip?: string | null | undefined;
@@ -1047,6 +1205,8 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
provider: z.ZodOptional<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
aggregate: "aggregate";
}>>;
colo: z.ZodOptional<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>>>;
@@ -1109,6 +1269,17 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
health_check_provider: z.ZodCatch<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>, z.ZodTransform<{
@@ -1131,7 +1302,9 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
health_check_interval_sec: number;
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: "local" | "cloudflare";
health_check_provider: "local" | "cloudflare" | "globalping";
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
health_check_aggregate: "any" | "all" | "majority";
sync_status: string | null;
target_ip?: string | null | undefined;
}, {
@@ -1150,7 +1323,9 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
health_check_interval_sec: number;
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: "local" | "cloudflare";
health_check_provider: "local" | "cloudflare" | "globalping";
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
health_check_aggregate: "any" | "all" | "majority";
sync_status: string | null;
target_ips?: string[] | undefined;
target_ip?: string | null | undefined;
@@ -1179,6 +1354,8 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
provider: z.ZodOptional<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
aggregate: "aggregate";
}>>;
colo: z.ZodOptional<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>>>;
@@ -1387,6 +1564,17 @@ declare const healthCheckConfigSchema: z.ZodObject<{
health_check_provider: z.ZodOptional<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodOptional<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
}, z.core.$strip>;
type HealthCheckConfig = z.infer<typeof healthCheckConfigSchema>;
@@ -1418,6 +1606,17 @@ declare const createServiceWithConfigSchema: z.ZodObject<{
health_check_provider: z.ZodOptional<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodOptional<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
fqdn: z.ZodString;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -1610,6 +1809,17 @@ declare const updateServiceConfigSchema: z.ZodObject<{
health_check_provider: z.ZodOptional<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodOptional<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
fqdn: z.ZodString;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -1641,14 +1851,25 @@ declare const createServiceGroupSchema: z.ZodObject<{
health_check_provider: z.ZodOptional<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodOptional<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
name: z.ZodString;
type: z.ZodDefault<z.ZodEnum<{
custom: "custom";
vpn: "vpn";
network: "network";
internet: "internet";
bgp: "bgp";
custom: "custom";
}>>;
icon: z.ZodOptional<z.ZodNullable<z.ZodString>>;
domain: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -1675,14 +1896,25 @@ declare const updateServiceGroupSchema: z.ZodObject<{
health_check_provider: z.ZodOptional<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>;
health_check_providers: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
health_check_aggregate: z.ZodOptional<z.ZodEnum<{
any: "any";
all: "all";
majority: "majority";
}>>;
name: z.ZodOptional<z.ZodString>;
type: z.ZodOptional<z.ZodEnum<{
custom: "custom";
vpn: "vpn";
network: "network";
internet: "internet";
bgp: "bgp";
custom: "custom";
}>>;
icon: z.ZodOptional<z.ZodNullable<z.ZodString>>;
domain: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -1776,6 +2008,7 @@ declare const originHealthCheckSchema: z.ZodObject<{
provider: z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>;
cf_healthcheck_id: z.ZodNullable<z.ZodString>;
cf_zone_id: z.ZodNullable<z.ZodString>;
@@ -1797,6 +2030,7 @@ declare const createOriginHealthCheckSchema: z.ZodObject<{
provider: z.ZodEnum<{
local: "local";
cloudflare: "cloudflare";
globalping: "globalping";
}>;
name: z.ZodString;
cf_zone_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -1924,6 +2158,9 @@ declare const appSettingsPatchSchema: z.ZodObject<{
healthSuccessRecoveries: z.ZodOptional<z.ZodNumber>;
healthWorkerUrl: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodLiteral<"">]>>;
healthWorkerToken: z.ZodOptional<z.ZodString>;
globalpingToken: z.ZodOptional<z.ZodString>;
globalpingLocations: z.ZodOptional<z.ZodString>;
globalpingLimit: z.ZodOptional<z.ZodNumber>;
}, z.core.$strip>;
type AppSettingsPatch = z.infer<typeof appSettingsPatchSchema>;
declare const vpsTrackerEventSchema: z.ZodObject<{
@@ -2085,4 +2322,4 @@ declare const ingestAuditEventSchema: z.ZodObject<{
}, z.core.$strip>;
type IngestAuditEvent = z.infer<typeof ingestAuditEventSchema>;
export { AUDIT_SEVERITIES, AUDIT_SOURCE_APPS, AUDIT_TARGET_TYPES, type AppSettingsPatch, type AppSwitcherConfig, type AppSwitcherEntry, type AuditListQuery, type AuditLogEntry, type AuditSeverity, type AuditSourceApp, type AuditTargetType, type BulkUpdateDomainsInput, CERT_ERROR, CERT_EXPIRED, CERT_MONITORING_VALUES, CERT_MONITOR_AUTO, CERT_MONITOR_REQUIRED, CERT_MONITOR_SKIPPED, CERT_OK, CERT_UNKNOWN, CERT_WARNING, type CertMonitoring, type Certificate, type CfDnsRecord, type CfHealthCheck, type CfZone, type CfdmBindingSyncItem, type ChangeDomainInput, type ChangeIpInput, type CreateDnsRecordInput, type CreateDnsRecordPayload, type CreateDomainInput, type CreateDomainMonitorInput, type CreateGroupInput, type CreateOriginHealthCheckInput, type CreateServiceBindingInput, type CreateServiceGroupInput, type CreateServiceInput, type CreateServiceNodeInput, type CreateServiceWithConfigInput, type CreateSubdomainInput, type DnsRecord, type Domain, type DomainEnvironment, type DomainListItem, type DomainMonitor, type DomainMonitorResult, type DomainMonitorType, type Group, type GroupWithStats, HEALTH_KV_CURSOR_KEY, HEALTH_KV_RESULTS_KEY, HEALTH_KV_TARGETS_KEY, HEALTH_PROBE_BATCH, HEALTH_PROBE_CONCURRENCY, HEALTH_PROBE_KV_TITLE, HEALTH_PROBE_SCRIPT_NAME, type HealthCheckConfig, type HealthCheckProvider, type HealthCheckScope, type HealthCheckTarget, type HealthCheckType, type HealthProbeLog, type HealthProbeResultItem, type HealthProbeResultsDoc, type HealthProbeTargetItem, type HealthProbeTargetsDoc, type HealthStatusQuery, type HealthWorkerStatus, type IngestAuditEvent, type IpHealthState, type IpHealthStatus, type JwtClaims, type LbMode, type LoginInput, type LoginRequest, type LoginResponse, type NodeHealthState, type NotificationLog, type OriginHealthCheck, type OriginHealthCheckRecord, type ParsedFqdn, type PatchDnsRecordPayload, type ReorderServicesInput, SYNC_CONFLICT, SYNC_ERROR, SYNC_PENDING_DELETE, SYNC_PENDING_PUSH, SYNC_SYNCED, type Service, type ServiceBinding, type ServiceBindingView, type ServiceDomainBinding, type ServiceDomainBindingView, type ServiceGroup, type ServiceGroupView, type ServiceGroupsResponse, type ServiceIpHealth, type ServiceNode, type ServiceNodeRecord, type ServiceOverview, type ServiceView, type Subdomain, type SubdomainRecord, type SyncJob, type ToggleEnabledInput, type ToggleServiceIpInput, type UpdateDomainInput, type UpdateServiceConfigInput, type UpdateServiceGroupInput, type UpdateServiceNodeInput, type UpdateSubdomainInput, ValidationError, type VpsTrackerEvent, 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, healthProbeLogSchema, 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 };
export { AUDIT_SEVERITIES, AUDIT_SOURCE_APPS, AUDIT_TARGET_TYPES, type AppSettingsPatch, type AppSwitcherConfig, type AppSwitcherEntry, type AuditListQuery, type AuditLogEntry, type AuditSeverity, type AuditSourceApp, type AuditTargetType, type BulkUpdateDomainsInput, CERT_ERROR, CERT_EXPIRED, CERT_MONITORING_VALUES, CERT_MONITOR_AUTO, CERT_MONITOR_REQUIRED, CERT_MONITOR_SKIPPED, CERT_OK, CERT_UNKNOWN, CERT_WARNING, type CertMonitoring, type Certificate, type CfDnsRecord, type CfHealthCheck, type CfZone, type CfdmBindingSyncItem, type ChangeDomainInput, type ChangeIpInput, type CreateDnsRecordInput, type CreateDnsRecordPayload, type CreateDomainInput, type CreateDomainMonitorInput, type CreateGroupInput, type CreateOriginHealthCheckInput, type CreateServiceBindingInput, type CreateServiceGroupInput, type CreateServiceInput, type CreateServiceNodeInput, type CreateServiceWithConfigInput, type CreateSubdomainInput, type DnsRecord, type Domain, type DomainEnvironment, type DomainListItem, type DomainMonitor, type DomainMonitorResult, type DomainMonitorType, type Group, type GroupWithStats, HEALTH_CHECK_AGGREGATES, HEALTH_CHECK_PROVIDERS, HEALTH_KV_CURSOR_KEY, HEALTH_KV_RESULTS_KEY, HEALTH_KV_TARGETS_KEY, HEALTH_PROBE_BATCH, HEALTH_PROBE_CONCURRENCY, HEALTH_PROBE_KV_TITLE, HEALTH_PROBE_SCRIPT_NAME, HEALTH_STATUS_PROVIDERS, type HealthCheckAggregate, type HealthCheckConfig, type HealthCheckProvider, type HealthCheckScope, type HealthCheckTarget, type HealthCheckType, type HealthProbeLog, type HealthProbeResultItem, type HealthProbeResultsDoc, type HealthProbeTargetItem, type HealthProbeTargetsDoc, type HealthStatusProvider, type HealthStatusQuery, type HealthWorkerStatus, type IngestAuditEvent, type IpHealthState, type IpHealthStatus, type JwtClaims, type LbMode, type LoginInput, type LoginRequest, type LoginResponse, type NodeHealthState, type NotificationLog, type OriginHealthCheck, type OriginHealthCheckRecord, type ParsedFqdn, type PatchDnsRecordPayload, type ReorderServicesInput, SYNC_CONFLICT, SYNC_ERROR, SYNC_PENDING_DELETE, SYNC_PENDING_PUSH, SYNC_SYNCED, type Service, type ServiceBinding, type ServiceBindingView, type ServiceDomainBinding, type ServiceDomainBindingView, type ServiceGroup, type ServiceGroupView, type ServiceGroupsResponse, type ServiceIpHealth, type ServiceNode, type ServiceNodeRecord, type ServiceOverview, type ServiceView, type Subdomain, type SubdomainRecord, type SyncJob, type ToggleEnabledInput, type ToggleServiceIpInput, type UpdateDomainInput, type UpdateServiceConfigInput, type UpdateServiceGroupInput, type UpdateServiceNodeInput, type UpdateSubdomainInput, ValidationError, type VpsTrackerEvent, aggregateHealthOk, appSettingsPatchSchema, appSwitcherConfigSchema, appSwitcherEntrySchema, appSwitcherIconSchema, auditListQuerySchema, auditLogEntrySchema, auditSeveritySchema, auditSourceAppSchema, auditTargetTypeSchema, bindingToFqdn, bulkUpdateDomainsSchema, certMonitoringSchema, certStatusFromExpiry, certificateSchema, cfdmBindingSyncItemSchema, cfdmSyncBindingsBodySchema, changeDomainSchema, changeIpSchema, clampGlobalpingLimit, createDnsRecordSchema, createDomainMonitorSchema, createDomainSchema, createGroupSchema, createOriginHealthCheckSchema, createServiceBindingSchema, createServiceGroupSchema, createServiceNodeSchema, createServiceSchema, createServiceWithConfigSchema, createSubdomainSchema, derivePrimaryProvider, dnsNameToSubdomainLabel, dnsRecordNamesMatch, dnsRecordSchema, domainEnvironmentSchema, domainListItemSchema, domainMonitorResultSchema, domainMonitorSchema, domainMonitorTypeSchema, domainSchema, fqdnToDisplay, groupSchema, groupWithStatsSchema, healthCheckAggregateSchema, healthCheckConfigSchema, healthCheckProviderSchema, healthCheckProvidersSchema, healthCheckScopeSchema, healthCheckTypeSchema, healthProbeLogSchema, healthStatusProviderSchema, healthStatusQuerySchema, ingestAuditEventSchema, ipHealthStateSchema, ipHealthStatusSchema, isIpLiteral, isValidIpv4, lbModeSchema, loginSchema, nodeHealthStateSchema, normalizeDnsRecordName, normalizeProbeProvider, normalizeStatusProvider, notificationLogSchema, originHealthCheckSchema, parseFqdn, parseGlobalpingLocations, parseHealthAggregate, parseHealthProviders, reorderServicesSchema, serializeHealthProviders, serviceBindingSchema, serviceDomainBindingSchema, serviceGroupSchema, serviceGroupTypeSchema, serviceGroupViewSchema, serviceGroupsResponseSchema, serviceIpHealthSchema, serviceNodeSchema, serviceSchema, serviceViewSchema, shouldMonitorService, subdomainLabelToFqdn, subdomainSchema, targetHasProvider, targetProviders, toggleEnabledSchema, toggleServiceIpSchema, uniqueHealthProviders, updateDomainGroupSchema, updateDomainSchema, updateServiceConfigSchema, updateServiceGroupSchema, updateServiceNodeSchema, updateSubdomainSchema, validateDnsRecord, vpsTrackerEventSchema };
+132 -5
View File
@@ -165,6 +165,100 @@ function bindingToFqdn(binding) {
// src/schemas.ts
import { z } from "zod";
// src/health-providers.ts
var HEALTH_CHECK_PROVIDERS = [
"local",
"cloudflare",
"globalping"
];
var HEALTH_STATUS_PROVIDERS = [
...HEALTH_CHECK_PROVIDERS,
"aggregate"
];
var HEALTH_CHECK_AGGREGATES = ["any", "all", "majority"];
var PROVIDER_SET = new Set(HEALTH_CHECK_PROVIDERS);
var STATUS_SET = new Set(HEALTH_STATUS_PROVIDERS);
var AGGREGATE_SET = new Set(HEALTH_CHECK_AGGREGATES);
function normalizeProbeProvider(value) {
return value === "cloudflare" || value === "globalping" || value === "local" ? value : "local";
}
function normalizeStatusProvider(value) {
if (typeof value === "string" && STATUS_SET.has(value)) {
return value;
}
return "local";
}
function uniqueHealthProviders(values) {
const out = [];
for (const value of values) {
if (!PROVIDER_SET.has(String(value))) continue;
const next = value;
if (!out.includes(next)) out.push(next);
}
return out;
}
function parseHealthProviders(json, fallback) {
if (Array.isArray(json)) {
const parsed = uniqueHealthProviders(json);
if (parsed.length > 0) return parsed;
}
if (typeof json === "string" && json.trim()) {
const trimmed = json.trim();
if (trimmed.startsWith("[")) {
try {
const parsed = uniqueHealthProviders(JSON.parse(trimmed));
if (parsed.length > 0) return parsed;
} catch {
}
}
const one = uniqueHealthProviders(trimmed.split(","));
if (one.length > 0) return one;
}
return [normalizeProbeProvider(fallback)];
}
function serializeHealthProviders(providers) {
const unique = uniqueHealthProviders(providers);
return JSON.stringify(unique.length > 0 ? unique : ["local"]);
}
function parseHealthAggregate(value) {
if (typeof value === "string" && AGGREGATE_SET.has(value)) {
return value;
}
return "majority";
}
function derivePrimaryProvider(providers) {
return uniqueHealthProviders(providers)[0] ?? "local";
}
function targetProviders(target) {
if (target.providers && target.providers.length > 0) {
return uniqueHealthProviders(target.providers);
}
return [normalizeProbeProvider(target.provider)];
}
function targetHasProvider(target, provider) {
return targetProviders(target).includes(provider);
}
function aggregateHealthOk(oks, policy) {
const n = oks.length;
if (n === 0) return false;
const down = oks.filter((ok) => !ok).length;
if (policy === "any") return down === 0;
if (policy === "all") return down < n;
return down < Math.floor(n / 2) + 1;
}
function clampGlobalpingLimit(value, fallback = 3) {
const n = typeof value === "number" ? value : Number(value);
if (!Number.isFinite(n)) return fallback;
return Math.min(10, Math.max(1, Math.trunc(n)));
}
function parseGlobalpingLocations(value) {
const raw = typeof value === "string" ? value : "";
const parts = raw.split(",").map((part) => part.trim()).filter(Boolean);
return parts.length > 0 ? parts : ["World"];
}
// src/schemas.ts
var certMonitoringSchema = z.enum(["auto", "required", "skipped"]);
var lbModeSchema = z.enum(["round_robin", "failover", "weighted"]);
var healthCheckTypeSchema = z.enum(["tcp", "http", "ping", "dns"]);
@@ -179,7 +273,13 @@ var nodeHealthStateSchema = z.enum([
"unhealthy",
"disabled"
]);
var healthCheckProviderSchema = z.enum(["local", "cloudflare"]);
var healthCheckProviderSchema = z.enum(HEALTH_CHECK_PROVIDERS);
var healthStatusProviderSchema = z.enum(HEALTH_STATUS_PROVIDERS);
var healthCheckAggregateSchema = z.enum(HEALTH_CHECK_AGGREGATES);
var healthCheckProvidersSchema = z.array(healthCheckProviderSchema).min(1).transform((arr) => {
const unique = uniqueHealthProviders(arr);
return unique.length > 0 ? unique : ["local"];
});
var healthCheckScopeSchema = z.enum(["binding", "group"]);
var ipHealthStatusSchema = z.object({
scope: healthCheckScopeSchema,
@@ -192,7 +292,7 @@ var ipHealthStatusSchema = z.object({
last_checked_at: z.string().nullable(),
last_error: z.string().nullable(),
colo: z.string().nullable().optional(),
provider: healthCheckProviderSchema.optional()
provider: healthStatusProviderSchema.optional()
});
var serviceIpHealthSchema = z.object({
ip: z.string(),
@@ -200,7 +300,7 @@ var serviceIpHealthSchema = z.object({
latency_ms: z.number().nullable(),
last_checked_at: z.string().nullable().optional(),
last_error: z.string().nullable().optional(),
provider: healthCheckProviderSchema.optional(),
provider: healthStatusProviderSchema.optional(),
colo: z.string().nullable().optional()
});
var healthProbeLogSchema = z.object({
@@ -250,6 +350,8 @@ var serviceGroupSchema = z.object({
health_check_timeout_ms: z.number().default(3e3),
health_check_verify_tls: z.coerce.boolean().default(false),
health_check_provider: healthCheckProviderSchema.catch("local"),
health_check_providers: healthCheckProvidersSchema.catch(["local"]),
health_check_aggregate: healthCheckAggregateSchema.catch("majority"),
created_at: z.string(),
updated_at: z.string()
});
@@ -288,6 +390,8 @@ var serviceDomainBindingSchema = z.object({
health_check_timeout_ms: z.number().default(3e3),
health_check_verify_tls: z.coerce.boolean().default(false),
health_check_provider: healthCheckProviderSchema.catch("local"),
health_check_providers: healthCheckProvidersSchema.catch(["local"]),
health_check_aggregate: healthCheckAggregateSchema.catch("majority"),
sync_status: z.string().nullable().default(null)
}).transform((binding) => ({
...binding,
@@ -414,7 +518,9 @@ var healthCheckConfigFields = {
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(),
health_check_provider: healthCheckProviderSchema.optional()
health_check_provider: healthCheckProviderSchema.optional(),
health_check_providers: healthCheckProvidersSchema.optional(),
health_check_aggregate: healthCheckAggregateSchema.optional()
};
var healthCheckConfigSchema = z.object(healthCheckConfigFields);
var serviceDomainInputSchema = z.object({
@@ -727,7 +833,10 @@ var appSettingsPatchSchema = z3.object({
healthLatencyWarnMs: z3.number().int().min(50).max(6e4).optional(),
healthSuccessRecoveries: z3.number().int().min(1).max(20).optional(),
healthWorkerUrl: z3.string().url().or(z3.literal("")).optional(),
healthWorkerToken: z3.string().optional()
healthWorkerToken: z3.string().optional(),
globalpingToken: z3.string().optional(),
globalpingLocations: z3.string().trim().max(200).optional(),
globalpingLimit: z3.number().int().min(1).max(10).optional()
}).superRefine((data, ctx) => {
if (data.healthDegradedFailures != null && data.healthDownFailures != null && data.healthDownFailures < data.healthDegradedFailures) {
ctx.addIssue({
@@ -829,6 +938,8 @@ export {
CERT_OK,
CERT_UNKNOWN,
CERT_WARNING,
HEALTH_CHECK_AGGREGATES,
HEALTH_CHECK_PROVIDERS,
HEALTH_KV_CURSOR_KEY,
HEALTH_KV_RESULTS_KEY,
HEALTH_KV_TARGETS_KEY,
@@ -836,12 +947,14 @@ export {
HEALTH_PROBE_CONCURRENCY,
HEALTH_PROBE_KV_TITLE,
HEALTH_PROBE_SCRIPT_NAME,
HEALTH_STATUS_PROVIDERS,
SYNC_CONFLICT,
SYNC_ERROR,
SYNC_PENDING_DELETE,
SYNC_PENDING_PUSH,
SYNC_SYNCED,
ValidationError,
aggregateHealthOk,
appSettingsPatchSchema,
appSwitcherConfigSchema,
appSwitcherEntrySchema,
@@ -860,6 +973,7 @@ export {
cfdmSyncBindingsBodySchema,
changeDomainSchema,
changeIpSchema,
clampGlobalpingLimit,
createDnsRecordSchema,
createDomainMonitorSchema,
createDomainSchema,
@@ -871,6 +985,7 @@ export {
createServiceSchema,
createServiceWithConfigSchema,
createSubdomainSchema,
derivePrimaryProvider,
dnsNameToSubdomainLabel,
dnsRecordNamesMatch,
dnsRecordSchema,
@@ -883,11 +998,14 @@ export {
fqdnToDisplay,
groupSchema,
groupWithStatsSchema,
healthCheckAggregateSchema,
healthCheckConfigSchema,
healthCheckProviderSchema,
healthCheckProvidersSchema,
healthCheckScopeSchema,
healthCheckTypeSchema,
healthProbeLogSchema,
healthStatusProviderSchema,
healthStatusQuerySchema,
ingestAuditEventSchema,
ipHealthStateSchema,
@@ -898,10 +1016,16 @@ export {
loginSchema,
nodeHealthStateSchema,
normalizeDnsRecordName,
normalizeProbeProvider,
normalizeStatusProvider,
notificationLogSchema,
originHealthCheckSchema,
parseFqdn,
parseGlobalpingLocations,
parseHealthAggregate,
parseHealthProviders,
reorderServicesSchema,
serializeHealthProviders,
serviceBindingSchema,
serviceDomainBindingSchema,
serviceGroupSchema,
@@ -915,8 +1039,11 @@ export {
shouldMonitorService,
subdomainLabelToFqdn,
subdomainSchema,
targetHasProvider,
targetProviders,
toggleEnabledSchema,
toggleServiceIpSchema,
uniqueHealthProviders,
updateDomainGroupSchema,
updateDomainSchema,
updateServiceConfigSchema,
+143
View File
@@ -0,0 +1,143 @@
export const HEALTH_CHECK_PROVIDERS = [
"local",
"cloudflare",
"globalping",
] as const;
export type HealthCheckProvider = (typeof HEALTH_CHECK_PROVIDERS)[number];
export const HEALTH_STATUS_PROVIDERS = [
...HEALTH_CHECK_PROVIDERS,
"aggregate",
] as const;
export type HealthStatusProvider = (typeof HEALTH_STATUS_PROVIDERS)[number];
export const HEALTH_CHECK_AGGREGATES = ["any", "all", "majority"] as const;
export type HealthCheckAggregate = (typeof HEALTH_CHECK_AGGREGATES)[number];
const PROVIDER_SET = new Set<string>(HEALTH_CHECK_PROVIDERS);
const STATUS_SET = new Set<string>(HEALTH_STATUS_PROVIDERS);
const AGGREGATE_SET = new Set<string>(HEALTH_CHECK_AGGREGATES);
export function normalizeProbeProvider(value: unknown): HealthCheckProvider {
return value === "cloudflare" || value === "globalping" || value === "local"
? value
: "local";
}
export function normalizeStatusProvider(value: unknown): HealthStatusProvider {
if (typeof value === "string" && STATUS_SET.has(value)) {
return value as HealthStatusProvider;
}
return "local";
}
export function uniqueHealthProviders(
values: readonly unknown[],
): HealthCheckProvider[] {
const out: HealthCheckProvider[] = [];
for (const value of values) {
if (!PROVIDER_SET.has(String(value))) continue;
const next = value as HealthCheckProvider;
if (!out.includes(next)) out.push(next);
}
return out;
}
export function parseHealthProviders(
json: unknown,
fallback?: unknown,
): HealthCheckProvider[] {
if (Array.isArray(json)) {
const parsed = uniqueHealthProviders(json);
if (parsed.length > 0) return parsed;
}
if (typeof json === "string" && json.trim()) {
const trimmed = json.trim();
if (trimmed.startsWith("[")) {
try {
const parsed = uniqueHealthProviders(JSON.parse(trimmed) as unknown[]);
if (parsed.length > 0) return parsed;
} catch {
// fall through to single-provider
}
}
const one = uniqueHealthProviders(trimmed.split(","));
if (one.length > 0) return one;
}
return [normalizeProbeProvider(fallback)];
}
export function serializeHealthProviders(
providers: readonly HealthCheckProvider[],
): string {
const unique = uniqueHealthProviders(providers);
return JSON.stringify(unique.length > 0 ? unique : ["local"]);
}
export function parseHealthAggregate(value: unknown): HealthCheckAggregate {
if (typeof value === "string" && AGGREGATE_SET.has(value)) {
return value as HealthCheckAggregate;
}
return "majority";
}
export function derivePrimaryProvider(
providers: readonly HealthCheckProvider[],
): HealthCheckProvider {
return uniqueHealthProviders(providers)[0] ?? "local";
}
export function targetProviders(target: {
providers?: readonly HealthCheckProvider[] | null;
provider?: HealthCheckProvider | null;
}): HealthCheckProvider[] {
if (target.providers && target.providers.length > 0) {
return uniqueHealthProviders(target.providers);
}
return [normalizeProbeProvider(target.provider)];
}
export function targetHasProvider(
target: {
providers?: readonly HealthCheckProvider[] | null;
provider?: HealthCheckProvider | null;
},
provider: HealthCheckProvider,
): boolean {
return targetProviders(target).includes(provider);
}
/**
* any — Down if at least one source is Down (ok only if all ok).
* all — Down only if every source is Down (ok if any ok).
* majority — Down if a strict majority of sources are Down (2 → both, 3 → ≥2).
*/
export function aggregateHealthOk(
oks: readonly boolean[],
policy: HealthCheckAggregate,
): boolean {
const n = oks.length;
if (n === 0) return false;
const down = oks.filter((ok) => !ok).length;
if (policy === "any") return down === 0;
if (policy === "all") return down < n;
return down < Math.floor(n / 2) + 1;
}
export function clampGlobalpingLimit(value: unknown, fallback = 3): number {
const n = typeof value === "number" ? value : Number(value);
if (!Number.isFinite(n)) return fallback;
return Math.min(10, Math.max(1, Math.trunc(n)));
}
export function parseGlobalpingLocations(value: unknown): string[] {
const raw = typeof value === "string" ? value : "";
const parts = raw
.split(",")
.map((part) => part.trim())
.filter(Boolean);
return parts.length > 0 ? parts : ["World"];
}
+1 -1
View File
@@ -6,6 +6,7 @@ export * from "./schemas.js";
export * from "./app-switcher.js";
export * from "./integration-vps-tracker.js";
export * from "./health-probe-mailbox.js";
export * from "./health-providers.js";
export * from "./audit.js";
export type {
CfZone,
@@ -23,7 +24,6 @@ export type {
HealthCheckType,
IpHealthState,
NodeHealthState,
HealthCheckProvider,
HealthCheckScope,
IpHealthStatus,
HealthCheckTarget,
@@ -37,6 +37,9 @@ export const appSettingsPatchSchema = z.object({
healthSuccessRecoveries: z.number().int().min(1).max(20).optional(),
healthWorkerUrl: z.string().url().or(z.literal("")).optional(),
healthWorkerToken: z.string().optional(),
globalpingToken: z.string().optional(),
globalpingLocations: z.string().trim().max(200).optional(),
globalpingLimit: z.number().int().min(1).max(10).optional(),
}).superRefine((data, ctx) => {
if (
data.healthDegradedFailures != null &&
+27 -4
View File
@@ -1,4 +1,10 @@
import { z } from 'zod'
import {
HEALTH_CHECK_AGGREGATES,
HEALTH_CHECK_PROVIDERS,
HEALTH_STATUS_PROVIDERS,
uniqueHealthProviders,
} from './health-providers.js'
export const certMonitoringSchema = z.enum(['auto', 'required', 'skipped'])
@@ -29,8 +35,19 @@ export const nodeHealthStateSchema = z.enum([
])
export type NodeHealthState = z.infer<typeof nodeHealthStateSchema>
export const healthCheckProviderSchema = z.enum(['local', 'cloudflare'])
export type HealthCheckProvider = z.infer<typeof healthCheckProviderSchema>
export const healthCheckProviderSchema = z.enum(HEALTH_CHECK_PROVIDERS)
export const healthStatusProviderSchema = z.enum(HEALTH_STATUS_PROVIDERS)
export const healthCheckAggregateSchema = z.enum(HEALTH_CHECK_AGGREGATES)
export const healthCheckProvidersSchema = z
.array(healthCheckProviderSchema)
.min(1)
.transform((arr) => {
const unique = uniqueHealthProviders(arr)
return unique.length > 0 ? unique : (['local'] as const)
})
export const healthCheckScopeSchema = z.enum(['binding', 'group'])
export type HealthCheckScope = z.infer<typeof healthCheckScopeSchema>
@@ -46,7 +63,7 @@ export const ipHealthStatusSchema = z.object({
last_checked_at: z.string().nullable(),
last_error: z.string().nullable(),
colo: z.string().nullable().optional(),
provider: healthCheckProviderSchema.optional(),
provider: healthStatusProviderSchema.optional(),
})
export type IpHealthStatus = z.infer<typeof ipHealthStatusSchema>
@@ -57,7 +74,7 @@ export const serviceIpHealthSchema = z.object({
latency_ms: z.number().nullable(),
last_checked_at: z.string().nullable().optional(),
last_error: z.string().nullable().optional(),
provider: healthCheckProviderSchema.optional(),
provider: healthStatusProviderSchema.optional(),
colo: z.string().nullable().optional(),
})
@@ -116,6 +133,8 @@ export const serviceGroupSchema = z.object({
health_check_timeout_ms: z.number().default(3000),
health_check_verify_tls: z.coerce.boolean().default(false),
health_check_provider: healthCheckProviderSchema.catch('local'),
health_check_providers: healthCheckProvidersSchema.catch(['local']),
health_check_aggregate: healthCheckAggregateSchema.catch('majority'),
created_at: z.string(),
updated_at: z.string(),
})
@@ -157,6 +176,8 @@ export const serviceDomainBindingSchema = z
health_check_timeout_ms: z.number().default(3000),
health_check_verify_tls: z.coerce.boolean().default(false),
health_check_provider: healthCheckProviderSchema.catch('local'),
health_check_providers: healthCheckProvidersSchema.catch(['local']),
health_check_aggregate: healthCheckAggregateSchema.catch('majority'),
sync_status: z.string().nullable().default(null),
})
.transform((binding) => ({
@@ -329,6 +350,8 @@ const healthCheckConfigFields = {
health_check_timeout_ms: z.number().int().min(100).max(30000).optional(),
health_check_verify_tls: z.boolean().optional(),
health_check_provider: healthCheckProviderSchema.optional(),
health_check_providers: healthCheckProvidersSchema.optional(),
health_check_aggregate: healthCheckAggregateSchema.optional(),
}
export const healthCheckConfigSchema = z.object(healthCheckConfigFields)
+24 -4
View File
@@ -1,3 +1,15 @@
import type {
HealthCheckProvider,
HealthCheckAggregate,
HealthStatusProvider,
} from "./health-providers.js";
export type {
HealthCheckProvider,
HealthCheckAggregate,
HealthStatusProvider,
} from "./health-providers.js";
export interface Group {
id: number;
name: string;
@@ -23,6 +35,8 @@ export interface ServiceGroup {
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: HealthCheckProvider;
health_check_providers: HealthCheckProvider[];
health_check_aggregate: HealthCheckAggregate;
created_at: string;
updated_at: string;
}
@@ -123,6 +137,8 @@ export interface ServiceBinding {
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: HealthCheckProvider;
health_check_providers: HealthCheckProvider[];
health_check_aggregate: HealthCheckAggregate;
routing_strategy: LbMode;
operation_version: number;
created_at: string;
@@ -155,6 +171,8 @@ export interface ServiceBindingView {
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: HealthCheckProvider;
health_check_providers: HealthCheckProvider[];
health_check_aggregate: HealthCheckAggregate;
sync_status: string | null;
created_at: string;
updated_at: string;
@@ -181,6 +199,8 @@ export interface ServiceDomainBindingView {
health_check_timeout_ms: number;
health_check_verify_tls: boolean;
health_check_provider: HealthCheckProvider;
health_check_providers: HealthCheckProvider[];
health_check_aggregate: HealthCheckAggregate;
sync_status: string | null;
}
@@ -284,8 +304,6 @@ export type NodeHealthState =
| "unhealthy"
| "disabled";
export type HealthCheckProvider = "local" | "cloudflare";
export type HealthCheckScope = "binding" | "group";
export interface IpHealthStatus {
@@ -299,7 +317,7 @@ export interface IpHealthStatus {
last_checked_at: string | null;
last_error: string | null;
colo?: string | null;
provider?: HealthCheckProvider;
provider?: HealthStatusProvider;
}
export interface ServiceIpHealth {
@@ -308,7 +326,7 @@ export interface ServiceIpHealth {
latency_ms: number | null;
last_checked_at?: string | null;
last_error?: string | null;
provider?: HealthCheckProvider;
provider?: HealthStatusProvider;
colo?: string | null;
}
@@ -394,4 +412,6 @@ export interface HealthCheckTarget {
timeout_ms: number;
verify_tls: boolean;
provider: HealthCheckProvider;
providers?: HealthCheckProvider[];
aggregate?: HealthCheckAggregate;
}