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
+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,