feat(health-checks): implement health check IP toggling and configuration updates
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 8s
quality / changes (push) Successful in 5s
quality / docker-check (push) Skipped
quality / web (push) Successful in 51s
quality / api (push) Successful in 43s
CD / quality (push) Successful in 1m43s
CD / publish (push) Successful in 1m38s

- Added functionality to toggle individual IPs for services, allowing for dynamic management of IP health status.
- Enhanced the health check configuration in the UI, enabling users to set parameters directly from the settings page.
- Updated service views to include IP health tracking, improving visibility into the status of each IP associated with a service.
- Refactored relevant components to support the new IP toggling feature, ensuring a seamless user experience.

This commit significantly enhances the health management capabilities of services, providing users with more control over IP configurations and health monitoring.
This commit is contained in:
Denozordec
2026-08-19 15:33:47 +07:00
parent 4224db8eb3
commit d63c86065c
35 changed files with 1538 additions and 123 deletions
+235 -5
View File
File diff suppressed because one or more lines are too long
+67 -10
View File
@@ -187,6 +187,7 @@ var serviceIps = sqliteTable("service_ips", {
id: integer("id").primaryKey({ autoIncrement: true }),
service_id: integer("service_id").notNull().references(() => services.id, { onDelete: "cascade" }),
ip: text("ip").notNull(),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
created_at: text("created_at").notNull().default(sql`datetime('now')`)
});
var serviceBindingRecords = sqliteTable(
@@ -269,6 +270,11 @@ var appSettings = sqliteTable("app_settings", {
show_quick_actions: integer("show_quick_actions", {
mode: "boolean"
}).notNull().default(true),
health_check_cron: text("health_check_cron"),
health_degraded_failures: integer("health_degraded_failures"),
health_down_failures: integer("health_down_failures"),
health_latency_warn_ms: integer("health_latency_warn_ms"),
health_success_recoveries: integer("health_success_recoveries"),
created_at: text("created_at").notNull().default(sql`datetime('now')`),
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
});
@@ -493,7 +499,17 @@ function listAudit(db, opts = {}) {
// src/settings-repo.ts
import { eq as eq2 } from "drizzle-orm";
var SETTINGS_ID = "settings-main";
function toDto(row) {
function coalesceInt(value, fallback) {
return value == null || Number.isNaN(value) || value < 1 ? fallback : value;
}
function toDto(row, fallbacks) {
const env = fallbacks ?? {
healthCheckCron: "0 */2 * * * *",
healthDegradedFailures: 1,
healthDownFailures: 2,
healthLatencyWarnMs: 1e3,
healthSuccessRecoveries: 2
};
return {
id: row.id,
vpsTrackerUrl: row.vps_tracker_url?.trim() ?? "",
@@ -502,18 +518,36 @@ function toDto(row) {
),
vpsTrackerSyncEnabled: Boolean(row.vps_tracker_sync_enabled),
vpsTrackerLastSyncAt: row.vps_tracker_last_sync_at,
showQuickActions: row.show_quick_actions == null ? true : Boolean(row.show_quick_actions)
showQuickActions: row.show_quick_actions == null ? true : Boolean(row.show_quick_actions),
healthCheckCron: row.health_check_cron?.trim() || env.healthCheckCron,
healthDegradedFailures: coalesceInt(
row.health_degraded_failures,
env.healthDegradedFailures
),
healthDownFailures: coalesceInt(
row.health_down_failures,
env.healthDownFailures
),
healthLatencyWarnMs: coalesceInt(
row.health_latency_warn_ms,
env.healthLatencyWarnMs
),
healthSuccessRecoveries: coalesceInt(
row.health_success_recoveries,
env.healthSuccessRecoveries
)
};
}
function getAppSettings(db) {
function getAppSettings(db, fallbacks) {
const row = db.select().from(appSettings).where(eq2(appSettings.id, SETTINGS_ID)).get();
if (!row) {
db.insert(appSettings).values({ id: SETTINGS_ID }).run();
return toDto(
db.select().from(appSettings).where(eq2(appSettings.id, SETTINGS_ID)).get()
db.select().from(appSettings).where(eq2(appSettings.id, SETTINGS_ID)).get(),
fallbacks
);
}
return toDto(row);
return toDto(row, fallbacks);
}
function getAppSettingsSecrets(db) {
const row = db.select().from(appSettings).where(eq2(appSettings.id, SETTINGS_ID)).get();
@@ -523,7 +557,7 @@ function getAppSettingsSecrets(db) {
vpsTrackerSyncEnabled: Boolean(row?.vps_tracker_sync_enabled)
};
}
function updateAppSettings(db, patch) {
function updateAppSettings(db, patch, fallbacks) {
const existing = db.select().from(appSettings).where(eq2(appSettings.id, SETTINGS_ID)).get();
if (!existing) {
db.insert(appSettings).values({ id: SETTINGS_ID }).run();
@@ -535,9 +569,14 @@ function updateAppSettings(db, patch) {
vps_tracker_integration_token: patch.vpsTrackerIntegrationToken !== void 0 && patch.vpsTrackerIntegrationToken.trim() !== "" ? patch.vpsTrackerIntegrationToken : current.vps_tracker_integration_token,
vps_tracker_sync_enabled: patch.vpsTrackerSyncEnabled !== void 0 ? patch.vpsTrackerSyncEnabled : current.vps_tracker_sync_enabled,
show_quick_actions: patch.showQuickActions !== void 0 ? patch.showQuickActions : current.show_quick_actions,
health_check_cron: patch.healthCheckCron !== void 0 ? patch.healthCheckCron.trim() : current.health_check_cron,
health_degraded_failures: patch.healthDegradedFailures !== void 0 ? patch.healthDegradedFailures : current.health_degraded_failures,
health_down_failures: patch.healthDownFailures !== void 0 ? patch.healthDownFailures : current.health_down_failures,
health_latency_warn_ms: patch.healthLatencyWarnMs !== void 0 ? patch.healthLatencyWarnMs : current.health_latency_warn_ms,
health_success_recoveries: patch.healthSuccessRecoveries !== void 0 ? patch.healthSuccessRecoveries : current.health_success_recoveries,
updated_at: (/* @__PURE__ */ new Date()).toISOString()
}).where(eq2(appSettings.id, SETTINGS_ID)).run();
return getAppSettings(db);
return getAppSettings(db, fallbacks);
}
function touchVpsTrackerSync(db) {
db.update(appSettings).set({
@@ -638,6 +677,7 @@ __export(repos_exports, {
listOriginIpsForFqdn: () => listOriginIpsForFqdn,
listRecordsForBinding: () => listRecordsForBinding,
listServiceGroups: () => listServiceGroups,
listServiceIpRows: () => listServiceIpRows,
listServiceIps: () => listServiceIps,
listServices: () => listServices,
listServicesByGroup: () => listServicesByGroup,
@@ -659,6 +699,7 @@ __export(repos_exports, {
setServiceEnabled: () => setServiceEnabled,
setServiceGroup: () => setServiceGroup,
setServiceGroupEnabled: () => setServiceGroupEnabled,
setServiceIpEnabled: () => setServiceIpEnabled,
setServiceLb: () => setServiceLb,
unlinkBindingRecord: () => unlinkBindingRecord,
unlinkGroupDnsRecord: () => unlinkGroupDnsRecord,
@@ -1155,16 +1196,32 @@ function deleteServiceGroup(db, id) {
function insertServiceIpIfMissing(db, serviceId, ip) {
const existing = db.select({ ip: serviceIps.ip }).from(serviceIps).where(and2(eq3(serviceIps.service_id, serviceId), eq3(serviceIps.ip, ip))).get();
if (!existing) {
db.insert(serviceIps).values({ service_id: serviceId, ip }).run();
db.insert(serviceIps).values({ service_id: serviceId, ip, enabled: true }).run();
}
}
function listServiceIpRows(db, serviceId) {
return db.select({ ip: serviceIps.ip, enabled: serviceIps.enabled }).from(serviceIps).where(eq3(serviceIps.service_id, serviceId)).all().map((row) => ({ ip: row.ip, enabled: Boolean(row.enabled) }));
}
function listServiceIps(db, serviceId) {
return db.select({ ip: serviceIps.ip }).from(serviceIps).where(eq3(serviceIps.service_id, serviceId)).all().map((r) => r.ip);
return listServiceIpRows(db, serviceId).map((row) => row.ip);
}
function setServiceIpEnabled(db, serviceId, ip, enabled) {
const result = db.update(serviceIps).set({ enabled }).where(and2(eq3(serviceIps.service_id, serviceId), eq3(serviceIps.ip, ip))).run();
if (result.changes === 0) {
throw new NotFoundError(`service ip ${ip}`);
}
}
function replaceServiceIps(db, serviceId, ips) {
const previous = new Map(
listServiceIpRows(db, serviceId).map((row) => [row.ip, row.enabled])
);
db.delete(serviceIps).where(eq3(serviceIps.service_id, serviceId)).run();
for (const ip of ips) {
db.insert(serviceIps).values({ service_id: serviceId, ip }).run();
db.insert(serviceIps).values({
service_id: serviceId,
ip,
enabled: previous.get(ip) ?? true
}).run();
ensureNode(db, serviceId, ip);
}
const keep = new Set(ips);
@@ -0,0 +1 @@
ALTER TABLE service_ips ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1;
@@ -0,0 +1,7 @@
-- Local health-check engine settings (cron + state-machine thresholds).
-- NULL = inherit from process env (HEALTH_CHECK_CRON / HEALTH_*).
ALTER TABLE app_settings ADD COLUMN health_check_cron TEXT;
ALTER TABLE app_settings ADD COLUMN health_degraded_failures INTEGER;
ALTER TABLE app_settings ADD COLUMN health_down_failures INTEGER;
ALTER TABLE app_settings ADD COLUMN health_latency_warn_ms INTEGER;
ALTER TABLE app_settings ADD COLUMN health_success_recoveries INTEGER;
+39 -5
View File
@@ -923,17 +923,42 @@ function insertServiceIpIfMissing(db: Db, serviceId: number, ip: string): void {
.where(and(eq(serviceIps.service_id, serviceId), eq(serviceIps.ip, ip)))
.get();
if (!existing) {
db.insert(serviceIps).values({ service_id: serviceId, ip }).run();
db.insert(serviceIps).values({ service_id: serviceId, ip, enabled: true }).run();
}
}
export function listServiceIps(db: Db, serviceId: number): string[] {
export type ServiceIpRow = {
ip: string;
enabled: boolean;
};
export function listServiceIpRows(db: Db, serviceId: number): ServiceIpRow[] {
return db
.select({ ip: serviceIps.ip })
.select({ ip: serviceIps.ip, enabled: serviceIps.enabled })
.from(serviceIps)
.where(eq(serviceIps.service_id, serviceId))
.all()
.map((r) => r.ip);
.map((row) => ({ ip: row.ip, enabled: Boolean(row.enabled) }));
}
export function listServiceIps(db: Db, serviceId: number): string[] {
return listServiceIpRows(db, serviceId).map((row) => row.ip);
}
export function setServiceIpEnabled(
db: Db,
serviceId: number,
ip: string,
enabled: boolean,
): void {
const result = db
.update(serviceIps)
.set({ enabled })
.where(and(eq(serviceIps.service_id, serviceId), eq(serviceIps.ip, ip)))
.run();
if (result.changes === 0) {
throw new NotFoundError(`service ip ${ip}`);
}
}
export function replaceServiceIps(
@@ -941,9 +966,18 @@ export function replaceServiceIps(
serviceId: number,
ips: string[],
): void {
const previous = new Map(
listServiceIpRows(db, serviceId).map((row) => [row.ip, row.enabled]),
);
db.delete(serviceIps).where(eq(serviceIps.service_id, serviceId)).run();
for (const ip of ips) {
db.insert(serviceIps).values({ service_id: serviceId, ip }).run();
db.insert(serviceIps)
.values({
service_id: serviceId,
ip,
enabled: previous.get(ip) ?? true,
})
.run();
ensureNode(db, serviceId, ip);
}
const keep = new Set(ips);
+6
View File
@@ -259,6 +259,7 @@ export const serviceIps = sqliteTable("service_ips", {
.notNull()
.references(() => services.id, { onDelete: "cascade" }),
ip: text("ip").notNull(),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
@@ -379,6 +380,11 @@ export const appSettings = sqliteTable("app_settings", {
})
.notNull()
.default(true),
health_check_cron: text("health_check_cron"),
health_degraded_failures: integer("health_degraded_failures"),
health_down_failures: integer("health_down_failures"),
health_latency_warn_ms: integer("health_latency_warn_ms"),
health_success_recoveries: integer("health_success_recoveries"),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
+80 -6
View File
@@ -4,6 +4,14 @@ import { appSettings } from "./schema.js";
const SETTINGS_ID = "settings-main";
export type HealthEngineSettings = {
healthCheckCron: string;
healthDegradedFailures: number;
healthDownFailures: number;
healthLatencyWarnMs: number;
healthSuccessRecoveries: number;
};
export type AppSettingsDto = {
id: string;
vpsTrackerUrl: string;
@@ -11,16 +19,37 @@ export type AppSettingsDto = {
vpsTrackerSyncEnabled: boolean;
vpsTrackerLastSyncAt: string | null;
showQuickActions: boolean;
};
} & HealthEngineSettings;
export type AppSettingsPatch = {
vpsTrackerUrl?: string;
vpsTrackerIntegrationToken?: string;
vpsTrackerSyncEnabled?: boolean;
showQuickActions?: boolean;
healthCheckCron?: string;
healthDegradedFailures?: number;
healthDownFailures?: number;
healthLatencyWarnMs?: number;
healthSuccessRecoveries?: number;
};
function toDto(row: typeof appSettings.$inferSelect): AppSettingsDto {
export type HealthEngineFallbacks = HealthEngineSettings;
function coalesceInt(value: number | null | undefined, fallback: number): number {
return value == null || Number.isNaN(value) || value < 1 ? fallback : value;
}
function toDto(
row: typeof appSettings.$inferSelect,
fallbacks?: HealthEngineFallbacks,
): AppSettingsDto {
const env = fallbacks ?? {
healthCheckCron: "0 */2 * * * *",
healthDegradedFailures: 1,
healthDownFailures: 2,
healthLatencyWarnMs: 1000,
healthSuccessRecoveries: 2,
};
return {
id: row.id,
vpsTrackerUrl: row.vps_tracker_url?.trim() ?? "",
@@ -31,10 +60,30 @@ function toDto(row: typeof appSettings.$inferSelect): AppSettingsDto {
vpsTrackerLastSyncAt: row.vps_tracker_last_sync_at,
showQuickActions:
row.show_quick_actions == null ? true : Boolean(row.show_quick_actions),
healthCheckCron: row.health_check_cron?.trim() || env.healthCheckCron,
healthDegradedFailures: coalesceInt(
row.health_degraded_failures,
env.healthDegradedFailures,
),
healthDownFailures: coalesceInt(
row.health_down_failures,
env.healthDownFailures,
),
healthLatencyWarnMs: coalesceInt(
row.health_latency_warn_ms,
env.healthLatencyWarnMs,
),
healthSuccessRecoveries: coalesceInt(
row.health_success_recoveries,
env.healthSuccessRecoveries,
),
};
}
export function getAppSettings(db: Db): AppSettingsDto {
export function getAppSettings(
db: Db,
fallbacks?: HealthEngineFallbacks,
): AppSettingsDto {
const row = db
.select()
.from(appSettings)
@@ -44,9 +93,10 @@ export function getAppSettings(db: Db): AppSettingsDto {
db.insert(appSettings).values({ id: SETTINGS_ID }).run();
return toDto(
db.select().from(appSettings).where(eq(appSettings.id, SETTINGS_ID)).get()!,
fallbacks,
);
}
return toDto(row);
return toDto(row, fallbacks);
}
export function getAppSettingsSecrets(db: Db): {
@@ -67,7 +117,11 @@ export function getAppSettingsSecrets(db: Db): {
};
}
export function updateAppSettings(db: Db, patch: AppSettingsPatch): AppSettingsDto {
export function updateAppSettings(
db: Db,
patch: AppSettingsPatch,
fallbacks?: HealthEngineFallbacks,
): AppSettingsDto {
const existing = db
.select()
.from(appSettings)
@@ -102,12 +156,32 @@ export function updateAppSettings(db: Db, patch: AppSettingsPatch): AppSettingsD
patch.showQuickActions !== undefined
? patch.showQuickActions
: current.show_quick_actions,
health_check_cron:
patch.healthCheckCron !== undefined
? patch.healthCheckCron.trim()
: current.health_check_cron,
health_degraded_failures:
patch.healthDegradedFailures !== undefined
? patch.healthDegradedFailures
: current.health_degraded_failures,
health_down_failures:
patch.healthDownFailures !== undefined
? patch.healthDownFailures
: current.health_down_failures,
health_latency_warn_ms:
patch.healthLatencyWarnMs !== undefined
? patch.healthLatencyWarnMs
: current.health_latency_warn_ms,
health_success_recoveries:
patch.healthSuccessRecoveries !== undefined
? patch.healthSuccessRecoveries
: current.health_success_recoveries,
updated_at: new Date().toISOString(),
})
.where(eq(appSettings.id, SETTINGS_ID))
.run();
return getAppSettings(db);
return getAppSettings(db, fallbacks);
}
export function touchVpsTrackerSync(db: Db): void {
+16 -1
View File
@@ -133,6 +133,7 @@ interface ServiceView$1 {
health_status: IpHealthState;
health_latency_ms: number | null;
ip_health: ServiceIpHealth$1[];
ip_enabled: Record<string, boolean>;
}
interface SyncJob {
id: string;
@@ -646,6 +647,7 @@ declare const serviceViewSchema: z.ZodObject<{
}>;
latency_ms: z.ZodNullable<z.ZodNumber>;
}, z.core.$strip>>>;
ip_enabled: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
}, z.core.$strip>;
declare const serviceGroupViewSchema: z.ZodObject<{
id: z.ZodNumber;
@@ -789,6 +791,7 @@ declare const serviceGroupViewSchema: z.ZodObject<{
}>;
latency_ms: z.ZodNullable<z.ZodNumber>;
}, z.core.$strip>>>;
ip_enabled: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
}, z.core.$strip>>>;
health_status: z.ZodDefault<z.ZodEnum<{
unknown: "unknown";
@@ -941,6 +944,7 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
}>;
latency_ms: z.ZodNullable<z.ZodNumber>;
}, z.core.$strip>>>;
ip_enabled: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
}, z.core.$strip>>>;
health_status: z.ZodDefault<z.ZodEnum<{
unknown: "unknown";
@@ -1059,6 +1063,7 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
}>;
latency_ms: z.ZodNullable<z.ZodNumber>;
}, z.core.$strip>>>;
ip_enabled: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
}, z.core.$strip>>>;
}, z.core.$strip>;
declare const domainSchema: z.ZodObject<{
@@ -1552,6 +1557,11 @@ type UpdateServiceGroupInput = z.infer<typeof updateServiceGroupSchema>;
declare const toggleEnabledSchema: z.ZodObject<{
enabled: z.ZodBoolean;
}, z.core.$strip>;
declare const toggleServiceIpSchema: z.ZodObject<{
ip: z.ZodString;
enabled: z.ZodBoolean;
}, z.core.$strip>;
type ToggleServiceIpInput = z.infer<typeof toggleServiceIpSchema>;
declare const reorderServicesSchema: z.ZodObject<{
group_id: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodNull]>>>;
service_ids: z.ZodArray<z.ZodNumber>;
@@ -1768,6 +1778,11 @@ declare const appSettingsPatchSchema: z.ZodObject<{
vpsTrackerIntegrationToken: z.ZodOptional<z.ZodString>;
vpsTrackerSyncEnabled: z.ZodOptional<z.ZodBoolean>;
showQuickActions: z.ZodOptional<z.ZodBoolean>;
healthCheckCron: z.ZodOptional<z.ZodString>;
healthDegradedFailures: z.ZodOptional<z.ZodNumber>;
healthDownFailures: z.ZodOptional<z.ZodNumber>;
healthLatencyWarnMs: z.ZodOptional<z.ZodNumber>;
healthSuccessRecoveries: z.ZodOptional<z.ZodNumber>;
}, z.core.$strip>;
type AppSettingsPatch = z.infer<typeof appSettingsPatchSchema>;
declare const vpsTrackerEventSchema: z.ZodObject<{
@@ -1892,4 +1907,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, type HealthCheckConfig, type HealthCheckProvider, type HealthCheckScope, type HealthCheckTarget, type HealthCheckType, type HealthStatusQuery, 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 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, 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, 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, type HealthCheckConfig, type HealthCheckProvider, type HealthCheckScope, type HealthCheckTarget, type HealthCheckType, type HealthStatusQuery, 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, 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 };
+21 -2
View File
@@ -283,7 +283,8 @@ var serviceViewSchema = serviceSchema.extend({
domains: z.array(serviceDomainBindingSchema).default([]),
health_status: ipHealthStateSchema.default("unknown"),
health_latency_ms: z.number().nullable().default(null),
ip_health: z.array(serviceIpHealthSchema).default([])
ip_health: z.array(serviceIpHealthSchema).default([]),
ip_enabled: z.record(z.string(), z.boolean()).default({})
});
var serviceGroupViewSchema = serviceGroupSchema.extend({
services: z.array(serviceViewSchema).default([]),
@@ -558,6 +559,10 @@ var updateServiceGroupSchema = z.object({
var toggleEnabledSchema = z.object({
enabled: z.boolean()
});
var toggleServiceIpSchema = z.object({
ip: ipv4Schema,
enabled: z.boolean()
});
var reorderServicesSchema = z.object({
group_id: z.union([z.number(), z.null()]).optional().default(null),
service_ids: z.array(z.number().int().positive()).min(1)
@@ -693,7 +698,20 @@ var appSettingsPatchSchema = z3.object({
vpsTrackerUrl: z3.string().url().or(z3.literal("")).optional(),
vpsTrackerIntegrationToken: z3.string().optional(),
vpsTrackerSyncEnabled: z3.boolean().optional(),
showQuickActions: z3.boolean().optional()
showQuickActions: z3.boolean().optional(),
healthCheckCron: z3.string().trim().min(1).max(64).optional(),
healthDegradedFailures: z3.number().int().min(1).max(20).optional(),
healthDownFailures: z3.number().int().min(1).max(50).optional(),
healthLatencyWarnMs: z3.number().int().min(50).max(6e4).optional(),
healthSuccessRecoveries: z3.number().int().min(1).max(20).optional()
}).superRefine((data, ctx) => {
if (data.healthDegradedFailures != null && data.healthDownFailures != null && data.healthDownFailures < data.healthDegradedFailures) {
ctx.addIssue({
code: "custom",
message: "\u043E\u0448\u0438\u0431\u043E\u043A \u0434\u043E down \u043D\u0435 \u043C\u0435\u043D\u044C\u0448\u0435, \u0447\u0435\u043C \u0434\u043E degraded",
path: ["healthDownFailures"]
});
}
});
var vpsTrackerEventSchema = z3.object({
event: z3.enum(["vps_down", "vps_up"]),
@@ -857,6 +875,7 @@ export {
subdomainLabelToFqdn,
subdomainSchema,
toggleEnabledSchema,
toggleServiceIpSchema,
updateDomainGroupSchema,
updateDomainSchema,
updateServiceConfigSchema,
@@ -30,6 +30,23 @@ export const appSettingsPatchSchema = z.object({
vpsTrackerIntegrationToken: z.string().optional(),
vpsTrackerSyncEnabled: z.boolean().optional(),
showQuickActions: z.boolean().optional(),
healthCheckCron: z.string().trim().min(1).max(64).optional(),
healthDegradedFailures: z.number().int().min(1).max(20).optional(),
healthDownFailures: z.number().int().min(1).max(50).optional(),
healthLatencyWarnMs: z.number().int().min(50).max(60_000).optional(),
healthSuccessRecoveries: z.number().int().min(1).max(20).optional(),
}).superRefine((data, ctx) => {
if (
data.healthDegradedFailures != null &&
data.healthDownFailures != null &&
data.healthDownFailures < data.healthDegradedFailures
) {
ctx.addIssue({
code: "custom",
message: "ошибок до down не меньше, чем до degraded",
path: ["healthDownFailures"],
});
}
});
export type AppSettingsPatch = z.infer<typeof appSettingsPatchSchema>;
+8
View File
@@ -159,6 +159,7 @@ export const serviceViewSchema = serviceSchema.extend({
health_status: ipHealthStateSchema.default('unknown'),
health_latency_ms: z.number().nullable().default(null),
ip_health: z.array(serviceIpHealthSchema).default([]),
ip_enabled: z.record(z.string(), z.boolean()).default({}),
})
export const serviceGroupViewSchema = serviceGroupSchema.extend({
@@ -521,6 +522,13 @@ export const toggleEnabledSchema = z.object({
enabled: z.boolean(),
})
export const toggleServiceIpSchema = z.object({
ip: ipv4Schema,
enabled: z.boolean(),
})
export type ToggleServiceIpInput = z.infer<typeof toggleServiceIpSchema>
export const reorderServicesSchema = z.object({
group_id: z.union([z.number(), z.null()]).optional().default(null),
service_ids: z.array(z.number().int().positive()).min(1),
+1
View File
@@ -197,6 +197,7 @@ export interface ServiceView {
health_status: IpHealthState;
health_latency_ms: number | null;
ip_health: ServiceIpHealth[];
ip_enabled: Record<string, boolean>;
}
export interface GroupWithStats extends Group {