feat: Implement load balancing and health check features for service groups, including DNS-based load balancing modes and health check configurations, enhancing service reliability and performance monitoring
Build, Test, and Push CFDM Docker Image / test (push) Successful in 4m0s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 2m13s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Failing after 6s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped

This commit is contained in:
Denozordec
2026-06-25 18:24:00 +07:00
parent e5dc483d43
commit 6a1498bf80
35 changed files with 5312 additions and 281 deletions
+482 -13
View File
@@ -22,17 +22,14 @@ interface ServiceGroup$1 {
icon: string | null;
domain: string | null;
enabled: boolean;
created_at: string;
updated_at: string;
}
interface Service$1 {
id: number;
name: string;
slug: string;
service_group_id: number | null;
subdomain: string;
enabled: boolean;
sort_order: number;
lb_mode: LbMode;
health_check_enabled: boolean;
health_check_type: HealthCheckType;
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
created_at: string;
updated_at: string;
}
@@ -53,6 +50,14 @@ interface ServiceBinding {
hostname: string;
cname_target: string | null;
dns_record_id: number | null;
lb_mode: LbMode;
health_check_enabled: boolean;
health_check_type: HealthCheckType;
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
created_at: string;
updated_at: string;
}
@@ -69,6 +74,16 @@ interface ServiceBindingView {
service_slug: string;
target_ip: string | null;
target_ips: string[];
target_ip_weights: Record<string, number>;
target_ip_priorities: Record<string, number>;
lb_mode: LbMode;
health_check_enabled: boolean;
health_check_type: HealthCheckType;
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
created_at: string;
updated_at: string;
@@ -81,7 +96,17 @@ interface ServiceDomainBindingView {
fqdn: string;
record_type: "A" | "CNAME";
target_ips: string[];
target_ip_weights: Record<string, number>;
target_ip_priorities: Record<string, number>;
target_cname: string | null;
lb_mode: LbMode;
health_check_enabled: boolean;
health_check_type: HealthCheckType;
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
}
interface SyncJob {
@@ -126,13 +151,41 @@ interface JwtClaims {
sub: string;
exp: number;
}
type LbMode = "round_robin" | "failover" | "weighted";
type HealthCheckType = "tcp" | "http";
type IpHealthState = "up" | "down" | "degraded" | "unknown";
type HealthCheckScope = "binding" | "group";
interface IpHealthStatus {
scope: HealthCheckScope;
ref_id: number;
ip: string;
status: IpHealthState;
latency_ms: number | null;
consecutive_failures: number;
last_checked_at: string | null;
last_error: string | null;
}
interface HealthCheckTarget {
scope: HealthCheckScope;
ref_id: number;
ip: string;
hostname: string;
type: HealthCheckType;
port: number | null;
path: string | null;
expected_status: number | null;
timeout_ms: number;
}
declare class ValidationError extends Error {
constructor(message: string);
}
declare function validateDnsRecord(recordType: string, name: string, content: string, ttl: number, proxied: boolean): void;
declare function certStatusFromExpiry(daysLeft: number): string;
declare function shouldMonitorService(service: Pick<Service$1, "enabled" | "service_group_id">, group?: Pick<ServiceGroup$1, "enabled"> | null): boolean;
declare function shouldMonitorService(service: {
enabled?: boolean;
service_group_id?: number | null;
}, group?: Pick<ServiceGroup$1, "enabled"> | null): boolean;
declare function isValidIpv4(ip: string): boolean;
declare function dnsNameToSubdomainLabel(recordName: string, zoneName: string): string | null;
@@ -160,6 +213,43 @@ declare const certMonitoringSchema: z.ZodEnum<{
skipped: "skipped";
}>;
type CertMonitoring = z.infer<typeof certMonitoringSchema>;
declare const lbModeSchema: z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>;
declare const healthCheckTypeSchema: z.ZodEnum<{
tcp: "tcp";
http: "http";
}>;
declare const ipHealthStateSchema: z.ZodEnum<{
unknown: "unknown";
up: "up";
down: "down";
degraded: "degraded";
}>;
declare const healthCheckScopeSchema: z.ZodEnum<{
binding: "binding";
group: "group";
}>;
declare const ipHealthStatusSchema: z.ZodObject<{
scope: z.ZodEnum<{
binding: "binding";
group: "group";
}>;
ref_id: z.ZodNumber;
ip: z.ZodString;
status: z.ZodEnum<{
unknown: "unknown";
up: "up";
down: "down";
degraded: "degraded";
}>;
latency_ms: z.ZodNullable<z.ZodNumber>;
consecutive_failures: z.ZodNumber;
last_checked_at: z.ZodNullable<z.ZodString>;
last_error: z.ZodNullable<z.ZodString>;
}, z.core.$strip>;
declare const groupSchema: z.ZodObject<{
id: z.ZodNumber;
name: z.ZodString;
@@ -195,6 +285,21 @@ declare const serviceGroupSchema: z.ZodObject<{
icon: z.ZodNullable<z.ZodString>;
domain: z.ZodNullable<z.ZodString>;
enabled: z.ZodBoolean;
lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
health_check_enabled: z.ZodDefault<z.ZodBoolean>;
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
created_at: z.ZodString;
updated_at: z.ZodString;
}, z.core.$strip>;
@@ -206,6 +311,8 @@ declare const serviceSchema: z.ZodObject<{
subdomain: z.ZodOptional<z.ZodString>;
enabled: z.ZodOptional<z.ZodBoolean>;
computed_fqdn: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_weight: z.ZodDefault<z.ZodNumber>;
lb_priority: z.ZodDefault<z.ZodNumber>;
created_at: z.ZodString;
updated_at: z.ZodString;
}, z.core.$strip>;
@@ -221,10 +328,29 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
}>>;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
health_check_enabled: z.ZodDefault<z.ZodBoolean>;
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
sync_status: z.ZodNullable<z.ZodString>;
}, z.core.$strip>, z.ZodTransform<{
target_ips: string[];
target_ip_weights: Record<string, number>;
target_ip_priorities: Record<string, number>;
target_cname: string | null;
record_type: "A" | "CNAME";
binding_id: number;
@@ -232,6 +358,14 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
zone_name: string;
hostname: string;
fqdn: string;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
target_ip?: string | null | undefined;
}, {
@@ -241,9 +375,19 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
hostname: string;
fqdn: string;
record_type: "A" | "CNAME";
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
target_ips?: string[] | undefined;
target_ip?: string | null | undefined;
target_ip_weights?: Record<string, number> | undefined;
target_ip_priorities?: Record<string, number> | undefined;
target_cname?: string | null | undefined;
}>>;
declare const serviceViewSchema: z.ZodObject<{
@@ -252,6 +396,8 @@ declare const serviceViewSchema: z.ZodObject<{
slug: z.ZodString;
service_group_id: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
computed_fqdn: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_weight: z.ZodDefault<z.ZodNumber>;
lb_priority: z.ZodDefault<z.ZodNumber>;
created_at: z.ZodString;
updated_at: z.ZodString;
subdomain: z.ZodDefault<z.ZodString>;
@@ -269,10 +415,29 @@ declare const serviceViewSchema: z.ZodObject<{
}>>;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
health_check_enabled: z.ZodDefault<z.ZodBoolean>;
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
sync_status: z.ZodNullable<z.ZodString>;
}, z.core.$strip>, z.ZodTransform<{
target_ips: string[];
target_ip_weights: Record<string, number>;
target_ip_priorities: Record<string, number>;
target_cname: string | null;
record_type: "A" | "CNAME";
binding_id: number;
@@ -280,6 +445,14 @@ declare const serviceViewSchema: z.ZodObject<{
zone_name: string;
hostname: string;
fqdn: string;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
target_ip?: string | null | undefined;
}, {
@@ -289,9 +462,19 @@ declare const serviceViewSchema: z.ZodObject<{
hostname: string;
fqdn: string;
record_type: "A" | "CNAME";
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
target_ips?: string[] | undefined;
target_ip?: string | null | undefined;
target_ip_weights?: Record<string, number> | undefined;
target_ip_priorities?: Record<string, number> | undefined;
target_cname?: string | null | undefined;
}>>>>;
}, z.core.$strip>;
@@ -308,6 +491,21 @@ declare const serviceGroupViewSchema: z.ZodObject<{
icon: z.ZodNullable<z.ZodString>;
domain: z.ZodNullable<z.ZodString>;
enabled: z.ZodBoolean;
lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
health_check_enabled: z.ZodDefault<z.ZodBoolean>;
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
created_at: z.ZodString;
updated_at: z.ZodString;
services: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -316,6 +514,8 @@ declare const serviceGroupViewSchema: z.ZodObject<{
slug: z.ZodString;
service_group_id: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
computed_fqdn: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_weight: z.ZodDefault<z.ZodNumber>;
lb_priority: z.ZodDefault<z.ZodNumber>;
created_at: z.ZodString;
updated_at: z.ZodString;
subdomain: z.ZodDefault<z.ZodString>;
@@ -333,10 +533,29 @@ declare const serviceGroupViewSchema: z.ZodObject<{
}>>;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
health_check_enabled: z.ZodDefault<z.ZodBoolean>;
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
sync_status: z.ZodNullable<z.ZodString>;
}, z.core.$strip>, z.ZodTransform<{
target_ips: string[];
target_ip_weights: Record<string, number>;
target_ip_priorities: Record<string, number>;
target_cname: string | null;
record_type: "A" | "CNAME";
binding_id: number;
@@ -344,6 +563,14 @@ declare const serviceGroupViewSchema: z.ZodObject<{
zone_name: string;
hostname: string;
fqdn: string;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
target_ip?: string | null | undefined;
}, {
@@ -353,9 +580,19 @@ declare const serviceGroupViewSchema: z.ZodObject<{
hostname: string;
fqdn: string;
record_type: "A" | "CNAME";
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
target_ips?: string[] | undefined;
target_ip?: string | null | undefined;
target_ip_weights?: Record<string, number> | undefined;
target_ip_priorities?: Record<string, number> | undefined;
target_cname?: string | null | undefined;
}>>>>;
}, z.core.$strip>>>;
@@ -374,6 +611,21 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
icon: z.ZodNullable<z.ZodString>;
domain: z.ZodNullable<z.ZodString>;
enabled: z.ZodBoolean;
lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
health_check_enabled: z.ZodDefault<z.ZodBoolean>;
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
created_at: z.ZodString;
updated_at: z.ZodString;
services: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -382,6 +634,8 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
slug: z.ZodString;
service_group_id: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
computed_fqdn: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_weight: z.ZodDefault<z.ZodNumber>;
lb_priority: z.ZodDefault<z.ZodNumber>;
created_at: z.ZodString;
updated_at: z.ZodString;
subdomain: z.ZodDefault<z.ZodString>;
@@ -399,10 +653,29 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
}>>;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
health_check_enabled: z.ZodDefault<z.ZodBoolean>;
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
sync_status: z.ZodNullable<z.ZodString>;
}, z.core.$strip>, z.ZodTransform<{
target_ips: string[];
target_ip_weights: Record<string, number>;
target_ip_priorities: Record<string, number>;
target_cname: string | null;
record_type: "A" | "CNAME";
binding_id: number;
@@ -410,6 +683,14 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
zone_name: string;
hostname: string;
fqdn: string;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
target_ip?: string | null | undefined;
}, {
@@ -419,9 +700,19 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
hostname: string;
fqdn: string;
record_type: "A" | "CNAME";
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
target_ips?: string[] | undefined;
target_ip?: string | null | undefined;
target_ip_weights?: Record<string, number> | undefined;
target_ip_priorities?: Record<string, number> | undefined;
target_cname?: string | null | undefined;
}>>>>;
}, z.core.$strip>>>;
@@ -432,6 +723,8 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
slug: z.ZodString;
service_group_id: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
computed_fqdn: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_weight: z.ZodDefault<z.ZodNumber>;
lb_priority: z.ZodDefault<z.ZodNumber>;
created_at: z.ZodString;
updated_at: z.ZodString;
subdomain: z.ZodDefault<z.ZodString>;
@@ -449,10 +742,29 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
}>>;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
health_check_enabled: z.ZodDefault<z.ZodBoolean>;
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
sync_status: z.ZodNullable<z.ZodString>;
}, z.core.$strip>, z.ZodTransform<{
target_ips: string[];
target_ip_weights: Record<string, number>;
target_ip_priorities: Record<string, number>;
target_cname: string | null;
record_type: "A" | "CNAME";
binding_id: number;
@@ -460,6 +772,14 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
zone_name: string;
hostname: string;
fqdn: string;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
target_ip?: string | null | undefined;
}, {
@@ -469,9 +789,19 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
hostname: string;
fqdn: string;
record_type: "A" | "CNAME";
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
target_ips?: string[] | undefined;
target_ip?: string | null | undefined;
target_ip_weights?: Record<string, number> | undefined;
target_ip_priorities?: Record<string, number> | undefined;
target_cname?: string | null | undefined;
}>>>>;
}, z.core.$strip>>>;
@@ -521,11 +851,30 @@ declare const serviceBindingSchema: z.ZodPipe<z.ZodObject<{
service_slug: z.ZodString;
target_ip: z.ZodNullable<z.ZodString>;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
health_check_enabled: z.ZodDefault<z.ZodBoolean>;
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
sync_status: z.ZodNullable<z.ZodString>;
created_at: z.ZodString;
updated_at: z.ZodString;
}, z.core.$strip>, z.ZodTransform<{
target_ips: string[];
target_ip_weights: Record<string, number>;
target_ip_priorities: Record<string, number>;
id: number;
domain_id: number;
service_id: number;
@@ -537,6 +886,14 @@ declare const serviceBindingSchema: z.ZodPipe<z.ZodObject<{
service_name: string;
service_slug: string;
target_ip: string | null;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
created_at: string;
updated_at: string;
@@ -552,10 +909,20 @@ declare const serviceBindingSchema: z.ZodPipe<z.ZodObject<{
service_name: string;
service_slug: string;
target_ip: string | null;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
created_at: string;
updated_at: string;
target_ips?: string[] | undefined;
target_ip_weights?: Record<string, number> | undefined;
target_ip_priorities?: Record<string, number> | undefined;
}>>;
declare const dnsRecordSchema: z.ZodObject<{
id: z.ZodNumber;
@@ -601,6 +968,19 @@ declare const createGroupSchema: z.ZodObject<{
name: z.ZodString;
slug: z.ZodString;
}, z.core.$strip>;
declare const healthCheckConfigSchema: z.ZodObject<{
health_check_enabled: z.ZodOptional<z.ZodBoolean>;
health_check_type: z.ZodOptional<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodOptional<z.ZodNumber>;
health_check_timeout_ms: z.ZodOptional<z.ZodNumber>;
}, z.core.$strip>;
type HealthCheckConfig = z.infer<typeof healthCheckConfigSchema>;
declare const createServiceSchema: z.ZodObject<{
name: z.ZodString;
slug: z.ZodString;
@@ -610,10 +990,29 @@ declare const createServiceWithConfigSchema: z.ZodObject<{
slug: z.ZodString;
service_group_id: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
ips: z.ZodDefault<z.ZodArray<z.ZodString>>;
lb_weight: z.ZodOptional<z.ZodNumber>;
lb_priority: z.ZodOptional<z.ZodNumber>;
domains: z.ZodDefault<z.ZodArray<z.ZodObject<{
health_check_enabled: z.ZodOptional<z.ZodBoolean>;
health_check_type: z.ZodOptional<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodOptional<z.ZodNumber>;
health_check_timeout_ms: z.ZodOptional<z.ZodNumber>;
fqdn: z.ZodString;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_cname: z.ZodOptional<z.ZodString>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
lb_mode: z.ZodOptional<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
}, z.core.$strip>>>;
}, z.core.$strip>;
declare const createServiceBindingSchema: z.ZodObject<{
@@ -694,14 +1093,43 @@ declare const updateServiceConfigSchema: z.ZodObject<{
slug: z.ZodOptional<z.ZodString>;
service_group_id: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
lb_weight: z.ZodOptional<z.ZodNumber>;
lb_priority: z.ZodOptional<z.ZodNumber>;
domains: z.ZodOptional<z.ZodArray<z.ZodObject<{
health_check_enabled: z.ZodOptional<z.ZodBoolean>;
health_check_type: z.ZodOptional<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodOptional<z.ZodNumber>;
health_check_timeout_ms: z.ZodOptional<z.ZodNumber>;
fqdn: z.ZodString;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_cname: z.ZodOptional<z.ZodString>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
lb_mode: z.ZodOptional<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
}, z.core.$strip>>>;
}, z.core.$strip>;
type UpdateServiceConfigInput = z.infer<typeof updateServiceConfigSchema>;
declare const createServiceGroupSchema: z.ZodObject<{
health_check_enabled: z.ZodOptional<z.ZodBoolean>;
health_check_type: z.ZodOptional<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodOptional<z.ZodNumber>;
health_check_timeout_ms: z.ZodOptional<z.ZodNumber>;
name: z.ZodString;
type: z.ZodDefault<z.ZodEnum<{
vpn: "vpn";
@@ -712,7 +1140,40 @@ declare const createServiceGroupSchema: z.ZodObject<{
}>>;
icon: z.ZodOptional<z.ZodNullable<z.ZodString>>;
domain: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_mode: z.ZodOptional<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
}, z.core.$strip>;
declare const updateServiceGroupSchema: z.ZodObject<{
health_check_enabled: z.ZodOptional<z.ZodBoolean>;
health_check_type: z.ZodOptional<z.ZodEnum<{
tcp: "tcp";
http: "http";
}>>;
health_check_port: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodOptional<z.ZodNumber>;
health_check_timeout_ms: z.ZodOptional<z.ZodNumber>;
name: z.ZodOptional<z.ZodString>;
type: z.ZodOptional<z.ZodEnum<{
vpn: "vpn";
network: "network";
internet: "internet";
bgp: "bgp";
custom: "custom";
}>>;
icon: z.ZodOptional<z.ZodNullable<z.ZodString>>;
domain: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_mode: z.ZodOptional<z.ZodEnum<{
round_robin: "round_robin";
failover: "failover";
weighted: "weighted";
}>>;
}, z.core.$strip>;
type UpdateServiceGroupInput = z.infer<typeof updateServiceGroupSchema>;
declare const toggleEnabledSchema: z.ZodObject<{
enabled: z.ZodBoolean;
}, z.core.$strip>;
@@ -720,6 +1181,14 @@ declare const reorderServicesSchema: z.ZodObject<{
group_id: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodNull]>>>;
service_ids: z.ZodArray<z.ZodNumber>;
}, z.core.$strip>;
declare const healthStatusQuerySchema: z.ZodObject<{
scope: z.ZodEnum<{
binding: "binding";
group: "group";
}>;
ref_id: z.ZodCoercedNumber<unknown>;
}, z.core.$strip>;
type HealthStatusQuery = z.infer<typeof healthStatusQuerySchema>;
type CreateServiceGroupInput = z.infer<typeof createServiceGroupSchema>;
type ToggleEnabledInput = z.infer<typeof toggleEnabledSchema>;
type ReorderServicesInput = z.infer<typeof reorderServicesSchema>;
@@ -728,4 +1197,4 @@ type CreateDomainInput = z.infer<typeof createDomainSchema>;
type LoginInput = z.infer<typeof loginSchema>;
type CreateDnsRecordInput = z.infer<typeof createDnsRecordSchema>;
export { 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 CfZone, type CreateDnsRecordInput, type CreateDnsRecordPayload, type CreateDomainInput, type CreateGroupInput, type CreateServiceBindingInput, type CreateServiceGroupInput, type CreateServiceInput, type CreateServiceWithConfigInput, type CreateSubdomainInput, type DnsRecord, type Domain, type DomainListItem, type Group, type GroupWithStats, type JwtClaims, type LoginInput, type LoginRequest, type LoginResponse, type ParsedFqdn, 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 ServiceView, type Subdomain, type SubdomainRecord, type SyncJob, type ToggleEnabledInput, type UpdateDomainInput, type UpdateServiceConfigInput, type UpdateSubdomainInput, ValidationError, bindingToFqdn, certMonitoringSchema, certStatusFromExpiry, certificateSchema, createDnsRecordSchema, createDomainSchema, createGroupSchema, createServiceBindingSchema, createServiceGroupSchema, createServiceSchema, createServiceWithConfigSchema, createSubdomainSchema, dnsNameToSubdomainLabel, dnsRecordNamesMatch, dnsRecordSchema, domainListItemSchema, domainSchema, fqdnToDisplay, groupSchema, groupWithStatsSchema, isValidIpv4, loginSchema, normalizeDnsRecordName, parseFqdn, reorderServicesSchema, serviceBindingSchema, serviceDomainBindingSchema, serviceGroupSchema, serviceGroupTypeSchema, serviceGroupViewSchema, serviceGroupsResponseSchema, serviceSchema, serviceViewSchema, shouldMonitorService, subdomainLabelToFqdn, subdomainSchema, toggleEnabledSchema, updateDomainGroupSchema, updateDomainSchema, updateServiceConfigSchema, updateSubdomainSchema, validateDnsRecord };
export { 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 CfZone, type CreateDnsRecordInput, type CreateDnsRecordPayload, type CreateDomainInput, type CreateGroupInput, type CreateServiceBindingInput, type CreateServiceGroupInput, type CreateServiceInput, type CreateServiceWithConfigInput, type CreateSubdomainInput, type DnsRecord, type Domain, type DomainListItem, type Group, type GroupWithStats, type HealthCheckConfig, type HealthCheckScope, type HealthCheckTarget, type HealthCheckType, type HealthStatusQuery, type IpHealthState, type IpHealthStatus, type JwtClaims, type LbMode, type LoginInput, type LoginRequest, type LoginResponse, type ParsedFqdn, 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 ServiceView, type Subdomain, type SubdomainRecord, type SyncJob, type ToggleEnabledInput, type UpdateDomainInput, type UpdateServiceConfigInput, type UpdateServiceGroupInput, type UpdateSubdomainInput, ValidationError, bindingToFqdn, certMonitoringSchema, certStatusFromExpiry, certificateSchema, createDnsRecordSchema, createDomainSchema, createGroupSchema, createServiceBindingSchema, createServiceGroupSchema, createServiceSchema, createServiceWithConfigSchema, createSubdomainSchema, dnsNameToSubdomainLabel, dnsRecordNamesMatch, dnsRecordSchema, domainListItemSchema, domainSchema, fqdnToDisplay, groupSchema, groupWithStatsSchema, healthCheckConfigSchema, healthCheckScopeSchema, healthCheckTypeSchema, healthStatusQuerySchema, ipHealthStateSchema, ipHealthStatusSchema, isValidIpv4, lbModeSchema, loginSchema, normalizeDnsRecordName, parseFqdn, reorderServicesSchema, serviceBindingSchema, serviceDomainBindingSchema, serviceGroupSchema, serviceGroupTypeSchema, serviceGroupViewSchema, serviceGroupsResponseSchema, serviceSchema, serviceViewSchema, shouldMonitorService, subdomainLabelToFqdn, subdomainSchema, toggleEnabledSchema, updateDomainGroupSchema, updateDomainSchema, updateServiceConfigSchema, updateServiceGroupSchema, updateSubdomainSchema, validateDnsRecord };
+91 -3
View File
@@ -160,6 +160,20 @@ function bindingToFqdn(binding) {
// 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"]);
var ipHealthStateSchema = z.enum(["up", "down", "degraded", "unknown"]);
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(),
last_checked_at: z.string().nullable(),
last_error: z.string().nullable()
});
var groupSchema = z.object({
id: z.number(),
name: z.string(),
@@ -184,6 +198,14 @@ var serviceGroupSchema = z.object({
icon: z.string().nullable(),
domain: z.string().nullable(),
enabled: z.boolean(),
lb_mode: lbModeSchema.catch("round_robin"),
health_check_enabled: z.boolean().default(false),
health_check_type: healthCheckTypeSchema.catch("tcp"),
health_check_port: z.number().nullable(),
health_check_path: z.string().nullable(),
health_check_expected_status: z.number().nullable(),
health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3e3),
created_at: z.string(),
updated_at: z.string()
});
@@ -195,6 +217,8 @@ var serviceSchema = z.object({
subdomain: z.string().optional(),
enabled: z.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()
});
@@ -207,11 +231,23 @@ var serviceDomainBindingSchema = z.object({
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.number()).optional(),
target_ip_priorities: z.record(z.string(), z.number()).optional(),
target_cname: z.string().nullable().optional(),
lb_mode: lbModeSchema.catch("round_robin"),
health_check_enabled: z.boolean().default(false),
health_check_type: healthCheckTypeSchema.catch("tcp"),
health_check_port: z.number().nullable(),
health_check_path: z.string().nullable(),
health_check_expected_status: z.number().nullable(),
health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3e3),
sync_status: z.string().nullable()
}).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"
}));
@@ -256,12 +292,24 @@ var serviceBindingSchema = z.object({
service_slug: z.string(),
target_ip: z.string().nullable(),
target_ips: z.array(z.string()).optional(),
target_ip_weights: z.record(z.string(), z.number()).optional(),
target_ip_priorities: z.record(z.string(), z.number()).optional(),
lb_mode: lbModeSchema.catch("round_robin"),
health_check_enabled: z.boolean().default(false),
health_check_type: healthCheckTypeSchema.catch("tcp"),
health_check_port: z.number().nullable(),
health_check_path: z.string().nullable(),
health_check_expected_status: z.number().nullable(),
health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3e3),
sync_status: z.string().nullable(),
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_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(),
@@ -299,10 +347,24 @@ 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 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()
};
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_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());
@@ -328,6 +390,8 @@ var createServiceSchema = z.object({
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({
@@ -383,13 +447,25 @@ var updateServiceConfigSchema = z.object({
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()
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()
@@ -398,6 +474,10 @@ 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()
});
export {
CERT_ERROR,
CERT_EXPIRED,
@@ -434,7 +514,14 @@ export {
fqdnToDisplay,
groupSchema,
groupWithStatsSchema,
healthCheckConfigSchema,
healthCheckScopeSchema,
healthCheckTypeSchema,
healthStatusQuerySchema,
ipHealthStateSchema,
ipHealthStatusSchema,
isValidIpv4,
lbModeSchema,
loginSchema,
normalizeDnsRecordName,
parseFqdn,
@@ -454,6 +541,7 @@ export {
updateDomainGroupSchema,
updateDomainSchema,
updateServiceConfigSchema,
updateServiceGroupSchema,
updateSubdomainSchema,
validateDnsRecord
};
+6
View File
@@ -15,4 +15,10 @@ export type {
ServiceBindingView,
ServiceDomainBindingView,
Subdomain,
LbMode,
HealthCheckType,
IpHealthState,
HealthCheckScope,
IpHealthStatus,
HealthCheckTarget,
} from "./types.js";
+100
View File
@@ -4,6 +4,31 @@ export const certMonitoringSchema = z.enum(['auto', 'required', 'skipped'])
export type CertMonitoring = z.infer<typeof certMonitoringSchema>
export const lbModeSchema = z.enum(['round_robin', 'failover', 'weighted'])
export type LbMode = z.infer<typeof lbModeSchema>
export const healthCheckTypeSchema = z.enum(['tcp', 'http'])
export type HealthCheckType = z.infer<typeof healthCheckTypeSchema>
export const ipHealthStateSchema = z.enum(['up', 'down', 'degraded', 'unknown'])
export type IpHealthState = z.infer<typeof ipHealthStateSchema>
export const healthCheckScopeSchema = z.enum(['binding', 'group'])
export type HealthCheckScope = z.infer<typeof healthCheckScopeSchema>
export const ipHealthStatusSchema = z.object({
scope: healthCheckScopeSchema,
ref_id: z.number(),
ip: z.string(),
status: ipHealthStateSchema,
latency_ms: z.number().nullable(),
consecutive_failures: z.number(),
last_checked_at: z.string().nullable(),
last_error: z.string().nullable(),
})
export type IpHealthStatus = z.infer<typeof ipHealthStatusSchema>
export const groupSchema = z.object({
id: z.number(),
name: z.string(),
@@ -31,6 +56,14 @@ export const serviceGroupSchema = z.object({
icon: z.string().nullable(),
domain: z.string().nullable(),
enabled: z.boolean(),
lb_mode: lbModeSchema.catch('round_robin'),
health_check_enabled: z.boolean().default(false),
health_check_type: healthCheckTypeSchema.catch('tcp'),
health_check_port: z.number().nullable(),
health_check_path: z.string().nullable(),
health_check_expected_status: z.number().nullable(),
health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3000),
created_at: z.string(),
updated_at: z.string(),
})
@@ -43,6 +76,8 @@ export const serviceSchema = z.object({
subdomain: z.string().optional(),
enabled: z.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(),
})
@@ -57,7 +92,17 @@ export const serviceDomainBindingSchema = z
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.number()).optional(),
target_ip_priorities: z.record(z.string(), z.number()).optional(),
target_cname: z.string().nullable().optional(),
lb_mode: lbModeSchema.catch('round_robin'),
health_check_enabled: z.boolean().default(false),
health_check_type: healthCheckTypeSchema.catch('tcp'),
health_check_port: z.number().nullable(),
health_check_path: z.string().nullable(),
health_check_expected_status: z.number().nullable(),
health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3000),
sync_status: z.string().nullable(),
})
.transform((binding) => ({
@@ -68,6 +113,8 @@ export const serviceDomainBindingSchema = z
: 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'
@@ -121,6 +168,16 @@ export const serviceBindingSchema = z
service_slug: z.string(),
target_ip: z.string().nullable(),
target_ips: z.array(z.string()).optional(),
target_ip_weights: z.record(z.string(), z.number()).optional(),
target_ip_priorities: z.record(z.string(), z.number()).optional(),
lb_mode: lbModeSchema.catch('round_robin'),
health_check_enabled: z.boolean().default(false),
health_check_type: healthCheckTypeSchema.catch('tcp'),
health_check_port: z.number().nullable(),
health_check_path: z.string().nullable(),
health_check_expected_status: z.number().nullable(),
health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3000),
sync_status: z.string().nullable(),
created_at: z.string(),
updated_at: z.string(),
@@ -133,6 +190,8 @@ export const serviceBindingSchema = z
: binding.target_ip
? [binding.target_ip]
: [],
target_ip_weights: binding.target_ip_weights ?? {},
target_ip_priorities: binding.target_ip_priorities ?? {},
}))
export const dnsRecordSchema = z.object({
@@ -190,11 +249,28 @@ const ipv4Schema = z
'Некорректный IPv4',
)
const 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(30000).optional(),
}
export const healthCheckConfigSchema = z.object(healthCheckConfigFields)
export type HealthCheckConfig = z.infer<typeof healthCheckConfigSchema>
const serviceDomainInputSchema = z
.object({
fqdn: z.string().min(1, 'Укажите FQDN'),
target_ips: z.array(ipv4Schema).optional(),
target_cname: z.string().min(1, 'Укажите CNAME-цель').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
@@ -223,6 +299,8 @@ export const createServiceSchema = z.object({
export const 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([]),
})
@@ -298,6 +376,8 @@ export const updateServiceConfigSchema = z.object({
slug: z.string().min(1, 'Укажите 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(),
@@ -310,8 +390,21 @@ export const createServiceGroupSchema = z.object({
type: serviceGroupTypeSchema.default('custom'),
icon: z.string().nullable().optional(),
domain: z.string().nullable().optional(),
lb_mode: lbModeSchema.optional(),
...healthCheckConfigFields,
})
export const updateServiceGroupSchema = z.object({
name: z.string().min(1, 'Укажите название').optional(),
type: serviceGroupTypeSchema.optional(),
icon: z.string().nullable().optional(),
domain: z.string().nullable().optional(),
lb_mode: lbModeSchema.optional(),
...healthCheckConfigFields,
})
export type UpdateServiceGroupInput = z.infer<typeof updateServiceGroupSchema>
export const toggleEnabledSchema = z.object({
enabled: z.boolean(),
})
@@ -321,6 +414,13 @@ export const reorderServicesSchema = z.object({
service_ids: z.array(z.number().int().positive()).min(1),
})
export const healthStatusQuerySchema = z.object({
scope: healthCheckScopeSchema,
ref_id: z.coerce.number().int().positive(),
})
export type HealthStatusQuery = z.infer<typeof healthStatusQuerySchema>
export type CreateServiceGroupInput = z.infer<typeof createServiceGroupSchema>
export type ToggleEnabledInput = z.infer<typeof toggleEnabledSchema>
export type ReorderServicesInput = z.infer<typeof reorderServicesSchema>
+71
View File
@@ -13,6 +13,14 @@ export interface ServiceGroup {
icon: string | null;
domain: string | null;
enabled: boolean;
lb_mode: LbMode;
health_check_enabled: boolean;
health_check_type: HealthCheckType;
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
created_at: string;
updated_at: string;
}
@@ -34,6 +42,8 @@ export interface Service {
subdomain: string;
enabled: boolean;
sort_order: number;
lb_weight: number;
lb_priority: number;
created_at: string;
updated_at: string;
}
@@ -98,6 +108,14 @@ export interface ServiceBinding {
hostname: string;
cname_target: string | null;
dns_record_id: number | null;
lb_mode: LbMode;
health_check_enabled: boolean;
health_check_type: HealthCheckType;
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
created_at: string;
updated_at: string;
}
@@ -115,6 +133,16 @@ export interface ServiceBindingView {
service_slug: string;
target_ip: string | null;
target_ips: string[];
target_ip_weights: Record<string, number>;
target_ip_priorities: Record<string, number>;
lb_mode: LbMode;
health_check_enabled: boolean;
health_check_type: HealthCheckType;
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
created_at: string;
updated_at: string;
@@ -128,7 +156,17 @@ export interface ServiceDomainBindingView {
fqdn: string;
record_type: "A" | "CNAME";
target_ips: string[];
target_ip_weights: Record<string, number>;
target_ip_priorities: Record<string, number>;
target_cname: string | null;
lb_mode: LbMode;
health_check_enabled: boolean;
health_check_type: HealthCheckType;
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
health_check_interval_sec: number;
health_check_timeout_ms: number;
sync_status: string | null;
}
@@ -140,6 +178,8 @@ export interface ServiceView {
subdomain: string;
enabled: boolean;
computed_fqdn: string | null;
lb_weight: number;
lb_priority: number;
created_at: string;
updated_at: string;
ips: string[];
@@ -203,3 +243,34 @@ export interface JwtClaims {
sub: string;
exp: number;
}
export type LbMode = "round_robin" | "failover" | "weighted";
export type HealthCheckType = "tcp" | "http";
export type IpHealthState = "up" | "down" | "degraded" | "unknown";
export type HealthCheckScope = "binding" | "group";
export interface IpHealthStatus {
scope: HealthCheckScope;
ref_id: number;
ip: string;
status: IpHealthState;
latency_ms: number | null;
consecutive_failures: number;
last_checked_at: string | null;
last_error: string | null;
}
export interface HealthCheckTarget {
scope: HealthCheckScope;
ref_id: number;
ip: string;
hostname: string;
type: HealthCheckType;
port: number | null;
path: string | null;
expected_status: number | null;
timeout_ms: number;
}
+2 -2
View File
@@ -3,7 +3,7 @@ import {
CERT_OK,
CERT_WARNING,
} from "./constants.js";
import type { Service, ServiceGroup } from "./types.js";
import type { ServiceGroup } from "./types.js";
const 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_])?)*)$/;
@@ -72,7 +72,7 @@ export function certStatusFromExpiry(daysLeft: number): string {
}
export function shouldMonitorService(
service: Pick<Service, "enabled" | "service_group_id">,
service: { enabled?: boolean; service_group_id?: number | null },
group?: Pick<ServiceGroup, "enabled"> | null,
): boolean {
if (!service.enabled) return false;