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
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:
Vendored
+132
-5
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user