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
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:
Vendored
+1177
-5
File diff suppressed because it is too large
Load Diff
Vendored
+251
-26
@@ -30,6 +30,8 @@ var services = sqliteTable("services", {
|
||||
subdomain: text("subdomain"),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(false),
|
||||
sort_order: integer("sort_order").notNull().default(0),
|
||||
lb_weight: integer("lb_weight").notNull().default(1),
|
||||
lb_priority: integer("lb_priority").notNull().default(1),
|
||||
created_at: text("created_at").notNull().default(sql`datetime('now')`),
|
||||
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
|
||||
});
|
||||
@@ -40,6 +42,14 @@ var serviceGroups = sqliteTable("service_groups", {
|
||||
icon: text("icon"),
|
||||
domain: text("domain"),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
lb_mode: text("lb_mode").notNull().default("round_robin"),
|
||||
health_check_enabled: integer("health_check_enabled", { mode: "boolean" }).notNull().default(false),
|
||||
health_check_type: text("health_check_type").notNull().default("tcp"),
|
||||
health_check_port: integer("health_check_port"),
|
||||
health_check_path: text("health_check_path"),
|
||||
health_check_expected_status: integer("health_check_expected_status"),
|
||||
health_check_interval_sec: integer("health_check_interval_sec").notNull().default(30),
|
||||
health_check_timeout_ms: integer("health_check_timeout_ms").notNull().default(3e3),
|
||||
created_at: text("created_at").notNull().default(sql`datetime('now')`),
|
||||
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
|
||||
});
|
||||
@@ -91,6 +101,14 @@ var serviceBindings = sqliteTable("service_bindings", {
|
||||
dns_record_id: integer("dns_record_id").references(() => dnsRecords.id, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
lb_mode: text("lb_mode").notNull().default("round_robin"),
|
||||
health_check_enabled: integer("health_check_enabled", { mode: "boolean" }).notNull().default(false),
|
||||
health_check_type: text("health_check_type").notNull().default("tcp"),
|
||||
health_check_port: integer("health_check_port"),
|
||||
health_check_path: text("health_check_path"),
|
||||
health_check_expected_status: integer("health_check_expected_status"),
|
||||
health_check_interval_sec: integer("health_check_interval_sec").notNull().default(30),
|
||||
health_check_timeout_ms: integer("health_check_timeout_ms").notNull().default(3e3),
|
||||
created_at: text("created_at").notNull().default(sql`datetime('now')`),
|
||||
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
|
||||
});
|
||||
@@ -112,7 +130,9 @@ var serviceBindingIps = sqliteTable(
|
||||
"service_binding_ips",
|
||||
{
|
||||
binding_id: integer("binding_id").notNull().references(() => serviceBindings.id, { onDelete: "cascade" }),
|
||||
ip: text("ip").notNull()
|
||||
ip: text("ip").notNull(),
|
||||
weight: integer("weight").notNull().default(1),
|
||||
priority: integer("priority").notNull().default(1)
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.binding_id, t.ip] })]
|
||||
);
|
||||
@@ -148,6 +168,22 @@ var syncJobs = sqliteTable("sync_jobs", {
|
||||
created_at: text("created_at").notNull().default(sql`datetime('now')`),
|
||||
finished_at: text("finished_at")
|
||||
});
|
||||
var ipHealthStatus = sqliteTable(
|
||||
"ip_health_status",
|
||||
{
|
||||
scope: text("scope").notNull(),
|
||||
ref_id: integer("ref_id").notNull(),
|
||||
ip: text("ip").notNull(),
|
||||
status: text("status").notNull().default("unknown"),
|
||||
latency_ms: integer("latency_ms"),
|
||||
consecutive_failures: integer("consecutive_failures").notNull().default(0),
|
||||
last_checked_at: text("last_checked_at"),
|
||||
last_error: text("last_error"),
|
||||
created_at: text("created_at").notNull().default(sql`datetime('now')`),
|
||||
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.scope, t.ref_id, t.ip] })]
|
||||
);
|
||||
var schema = {
|
||||
groups,
|
||||
services,
|
||||
@@ -161,7 +197,8 @@ var schema = {
|
||||
serviceBindingIps,
|
||||
serviceGroupDnsRecords,
|
||||
certificates,
|
||||
syncJobs
|
||||
syncJobs,
|
||||
ipHealthStatus
|
||||
};
|
||||
|
||||
// src/client.ts
|
||||
@@ -243,6 +280,8 @@ __export(repos_exports, {
|
||||
deleteDnsRecord: () => deleteDnsRecord,
|
||||
deleteDomain: () => deleteDomain,
|
||||
deleteGroup: () => deleteGroup,
|
||||
deleteIpHealthStatusForIp: () => deleteIpHealthStatusForIp,
|
||||
deleteIpHealthStatusForRef: () => deleteIpHealthStatusForRef,
|
||||
deleteService: () => deleteService,
|
||||
deleteServiceGroup: () => deleteServiceGroup,
|
||||
deleteSubdomain: () => deleteSubdomain,
|
||||
@@ -258,6 +297,7 @@ __export(repos_exports, {
|
||||
getDomain: () => getDomain,
|
||||
getGroup: () => getGroup,
|
||||
getGroupWithStats: () => getGroupWithStats,
|
||||
getIpHealthStatusRow: () => getIpHealthStatusRow,
|
||||
getService: () => getService,
|
||||
getServiceGroup: () => getServiceGroup,
|
||||
getSubdomain: () => getSubdomain,
|
||||
@@ -270,6 +310,7 @@ __export(repos_exports, {
|
||||
listAllDomains: () => listAllDomains,
|
||||
listAllSubdomains: () => listAllSubdomains,
|
||||
listBindingIps: () => listBindingIps,
|
||||
listBindingIpsWithMeta: () => listBindingIpsWithMeta,
|
||||
listBindingsByDomain: () => listBindingsByDomain,
|
||||
listBindingsByService: () => listBindingsByService,
|
||||
listCertificates: () => listCertificates,
|
||||
@@ -279,6 +320,8 @@ __export(repos_exports, {
|
||||
listDomainsEnriched: () => listDomainsEnriched,
|
||||
listGroupDnsRecords: () => listGroupDnsRecords,
|
||||
listGroups: () => listGroups,
|
||||
listHealthCheckTargets: () => listHealthCheckTargets,
|
||||
listIpHealthStatus: () => listIpHealthStatus,
|
||||
listRecordsForBinding: () => listRecordsForBinding,
|
||||
listServiceGroups: () => listServiceGroups,
|
||||
listServiceIps: () => listServiceIps,
|
||||
@@ -289,6 +332,7 @@ __export(repos_exports, {
|
||||
markDnsPendingDelete: () => markDnsPendingDelete,
|
||||
reorderServices: () => reorderServices,
|
||||
replaceBindingIps: () => replaceBindingIps,
|
||||
replaceBindingIpsWithMeta: () => replaceBindingIpsWithMeta,
|
||||
replaceServiceIps: () => replaceServiceIps,
|
||||
setBindingCnameTarget: () => setBindingCnameTarget,
|
||||
setBindingDnsRecordId: () => setBindingDnsRecordId,
|
||||
@@ -297,9 +341,11 @@ __export(repos_exports, {
|
||||
setServiceEnabled: () => setServiceEnabled,
|
||||
setServiceGroup: () => setServiceGroup,
|
||||
setServiceGroupEnabled: () => setServiceGroupEnabled,
|
||||
setServiceLb: () => setServiceLb,
|
||||
unlinkBindingRecord: () => unlinkBindingRecord,
|
||||
unlinkGroupDnsRecord: () => unlinkGroupDnsRecord,
|
||||
updateBindingFields: () => updateBindingFields,
|
||||
updateBindingLbConfig: () => updateBindingLbConfig,
|
||||
updateDnsFields: () => updateDnsFields,
|
||||
updateDomain: () => updateDomain,
|
||||
updateGroup: () => updateGroup,
|
||||
@@ -307,6 +353,7 @@ __export(repos_exports, {
|
||||
updateServiceGroup: () => updateServiceGroup,
|
||||
updateSubdomain: () => updateSubdomain,
|
||||
upsertCertificateCheck: () => upsertCertificateCheck,
|
||||
upsertIpHealthStatus: () => upsertIpHealthStatus,
|
||||
upsertSubdomain: () => upsertSubdomain
|
||||
});
|
||||
import { dnsRecordNamesMatch } from "@cfdm/shared";
|
||||
@@ -580,6 +627,13 @@ function setServiceEnabled(db, id, enabled) {
|
||||
db.update(services).set({ enabled, updated_at: sql2`datetime('now')` }).where(eq(services.id, id)).run();
|
||||
return getService(db, id);
|
||||
}
|
||||
function setServiceLb(db, id, weight, priority) {
|
||||
db.update(services).set({
|
||||
lb_weight: weight,
|
||||
lb_priority: priority,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq(services.id, id)).run();
|
||||
}
|
||||
function setServiceGroup(db, id, groupId) {
|
||||
const sortOrder = maxSortOrderInGroup(db, groupId) + 1;
|
||||
db.update(services).set({
|
||||
@@ -619,6 +673,14 @@ function mapServiceGroup(row) {
|
||||
icon: row.icon,
|
||||
domain: row.domain,
|
||||
enabled: row.enabled,
|
||||
lb_mode: row.lb_mode,
|
||||
health_check_enabled: row.health_check_enabled,
|
||||
health_check_type: row.health_check_type,
|
||||
health_check_port: row.health_check_port,
|
||||
health_check_path: row.health_check_path,
|
||||
health_check_expected_status: row.health_check_expected_status,
|
||||
health_check_interval_sec: row.health_check_interval_sec,
|
||||
health_check_timeout_ms: row.health_check_timeout_ms,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
};
|
||||
@@ -631,18 +693,49 @@ function getServiceGroup(db, id) {
|
||||
if (!row) throw new NotFoundError(`service group ${id}`);
|
||||
return mapServiceGroup(row);
|
||||
}
|
||||
function createServiceGroup(db, name, groupType, icon, domain) {
|
||||
const id = db.insert(serviceGroups).values({ name, type: groupType, icon, domain }).returning({ id: serviceGroups.id }).get().id;
|
||||
function createServiceGroup(db, name, groupType, icon, domain, lbPatch) {
|
||||
const id = db.insert(serviceGroups).values({
|
||||
name,
|
||||
type: groupType,
|
||||
icon,
|
||||
domain,
|
||||
lb_mode: lbPatch?.lb_mode ?? "round_robin",
|
||||
health_check_enabled: lbPatch?.health_check_enabled ?? false,
|
||||
health_check_type: lbPatch?.health_check_type ?? "tcp",
|
||||
health_check_port: lbPatch?.health_check_port ?? null,
|
||||
health_check_path: lbPatch?.health_check_path ?? null,
|
||||
health_check_expected_status: lbPatch?.health_check_expected_status ?? null,
|
||||
health_check_interval_sec: lbPatch?.health_check_interval_sec ?? 30,
|
||||
health_check_timeout_ms: lbPatch?.health_check_timeout_ms ?? 3e3
|
||||
}).returning({ id: serviceGroups.id }).get().id;
|
||||
return getServiceGroup(db, id);
|
||||
}
|
||||
function updateServiceGroup(db, id, name, groupType, icon, domain) {
|
||||
const result = db.update(serviceGroups).set({
|
||||
function updateServiceGroup(db, id, name, groupType, icon, domain, lbPatch) {
|
||||
const update = {
|
||||
name,
|
||||
type: groupType,
|
||||
icon,
|
||||
domain,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq(serviceGroups.id, id)).run();
|
||||
};
|
||||
if (lbPatch) {
|
||||
if (lbPatch.lb_mode !== void 0) update.lb_mode = lbPatch.lb_mode;
|
||||
if (lbPatch.health_check_enabled !== void 0)
|
||||
update.health_check_enabled = lbPatch.health_check_enabled;
|
||||
if (lbPatch.health_check_type !== void 0)
|
||||
update.health_check_type = lbPatch.health_check_type;
|
||||
if (lbPatch.health_check_port !== void 0)
|
||||
update.health_check_port = lbPatch.health_check_port;
|
||||
if (lbPatch.health_check_path !== void 0)
|
||||
update.health_check_path = lbPatch.health_check_path;
|
||||
if (lbPatch.health_check_expected_status !== void 0)
|
||||
update.health_check_expected_status = lbPatch.health_check_expected_status;
|
||||
if (lbPatch.health_check_interval_sec !== void 0)
|
||||
update.health_check_interval_sec = lbPatch.health_check_interval_sec;
|
||||
if (lbPatch.health_check_timeout_ms !== void 0)
|
||||
update.health_check_timeout_ms = lbPatch.health_check_timeout_ms;
|
||||
}
|
||||
const result = db.update(serviceGroups).set(update).where(eq(serviceGroups.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`service group ${id}`);
|
||||
return getServiceGroup(db, id);
|
||||
}
|
||||
@@ -667,12 +760,52 @@ function replaceServiceIps(db, serviceId, ips) {
|
||||
function listBindingIps(db, bindingId) {
|
||||
return db.select({ ip: serviceBindingIps.ip }).from(serviceBindingIps).where(eq(serviceBindingIps.binding_id, bindingId)).all().map((r) => r.ip);
|
||||
}
|
||||
function listBindingIpsWithMeta(db, bindingId) {
|
||||
return db.select({
|
||||
ip: serviceBindingIps.ip,
|
||||
weight: serviceBindingIps.weight,
|
||||
priority: serviceBindingIps.priority
|
||||
}).from(serviceBindingIps).where(eq(serviceBindingIps.binding_id, bindingId)).all();
|
||||
}
|
||||
function replaceBindingIps(db, bindingId, ips) {
|
||||
replaceBindingIpsWithMeta(
|
||||
db,
|
||||
bindingId,
|
||||
ips.map((ip) => ({ ip, weight: 1, priority: 1 }))
|
||||
);
|
||||
}
|
||||
function replaceBindingIpsWithMeta(db, bindingId, entries) {
|
||||
db.delete(serviceBindingIps).where(eq(serviceBindingIps.binding_id, bindingId)).run();
|
||||
for (const ip of ips) {
|
||||
db.insert(serviceBindingIps).values({ binding_id: bindingId, ip }).run();
|
||||
for (const entry of entries) {
|
||||
db.insert(serviceBindingIps).values({
|
||||
binding_id: bindingId,
|
||||
ip: entry.ip,
|
||||
weight: entry.weight,
|
||||
priority: entry.priority
|
||||
}).run();
|
||||
}
|
||||
}
|
||||
function updateBindingLbConfig(db, bindingId, patch) {
|
||||
const update = {
|
||||
updated_at: sql2`datetime('now')`
|
||||
};
|
||||
if (patch.lb_mode !== void 0) update.lb_mode = patch.lb_mode;
|
||||
if (patch.health_check_enabled !== void 0)
|
||||
update.health_check_enabled = patch.health_check_enabled;
|
||||
if (patch.health_check_type !== void 0)
|
||||
update.health_check_type = patch.health_check_type;
|
||||
if (patch.health_check_port !== void 0)
|
||||
update.health_check_port = patch.health_check_port;
|
||||
if (patch.health_check_path !== void 0)
|
||||
update.health_check_path = patch.health_check_path;
|
||||
if (patch.health_check_expected_status !== void 0)
|
||||
update.health_check_expected_status = patch.health_check_expected_status;
|
||||
if (patch.health_check_interval_sec !== void 0)
|
||||
update.health_check_interval_sec = patch.health_check_interval_sec;
|
||||
if (patch.health_check_timeout_ms !== void 0)
|
||||
update.health_check_timeout_ms = patch.health_check_timeout_ms;
|
||||
db.update(serviceBindings).set(update).where(eq(serviceBindings.id, bindingId)).run();
|
||||
}
|
||||
function setBindingCnameTarget(db, bindingId, target) {
|
||||
db.update(serviceBindings).set({
|
||||
cname_target: target,
|
||||
@@ -726,13 +859,22 @@ function unlinkGroupDnsRecord(db, groupId, dnsRecordId) {
|
||||
function dnsRecordMatchesHostname(recordName, hostname, zoneName) {
|
||||
return dnsRecordNamesMatch(recordName, hostname, zoneName);
|
||||
}
|
||||
var SERVICE_BINDING_SELECT_COLUMNS = `sb.id, sb.domain_id, sb.service_id, sb.hostname, sb.dns_record_id,
|
||||
sb.lb_mode, sb.health_check_enabled, sb.health_check_type, sb.health_check_port,
|
||||
sb.health_check_path, sb.health_check_expected_status, sb.health_check_interval_sec,
|
||||
sb.health_check_timeout_ms, sb.cname_target,
|
||||
d.zone_name, d.group_id, g.name AS group_name,
|
||||
s.name AS service_name, s.slug AS service_slug,
|
||||
dr.content AS target_ip, dr.sync_status,
|
||||
sb.created_at, sb.updated_at`;
|
||||
function enrichServiceBindingView(db, row) {
|
||||
const configured = listBindingIps(db, row.id);
|
||||
const configured = listBindingIpsWithMeta(db, row.id);
|
||||
const configuredIps = configured.map((c) => c.ip);
|
||||
const linkedRecords = listRecordsForBinding(db, row.id);
|
||||
const linkedIps = linkedRecords.filter((record) => record.record_type.toUpperCase() === "A").map((record) => record.content);
|
||||
const target_ips = [
|
||||
.../* @__PURE__ */ new Set([
|
||||
...configured,
|
||||
...configuredIps,
|
||||
...linkedIps,
|
||||
...row.target_ip ? [row.target_ip] : []
|
||||
])
|
||||
@@ -749,21 +891,29 @@ function enrichServiceBindingView(db, row) {
|
||||
}
|
||||
target_ips.sort();
|
||||
}
|
||||
const target_ip_weights = {};
|
||||
const target_ip_priorities = {};
|
||||
for (const entry of configured) {
|
||||
target_ip_weights[entry.ip] = entry.weight;
|
||||
target_ip_priorities[entry.ip] = entry.priority;
|
||||
}
|
||||
for (const ip of target_ips) {
|
||||
if (target_ip_weights[ip] === void 0) target_ip_weights[ip] = 1;
|
||||
if (target_ip_priorities[ip] === void 0) target_ip_priorities[ip] = 1;
|
||||
}
|
||||
const sync_status = row.sync_status ?? linkedRecords.find((record) => record.sync_status)?.sync_status ?? null;
|
||||
return {
|
||||
...row,
|
||||
target_ips,
|
||||
target_ip: target_ips[0] ?? null,
|
||||
target_ip_weights,
|
||||
target_ip_priorities,
|
||||
sync_status
|
||||
};
|
||||
}
|
||||
function listAllBindings(db) {
|
||||
return db.all(sql2`
|
||||
SELECT sb.id, sb.domain_id, sb.service_id, sb.hostname, sb.dns_record_id,
|
||||
d.zone_name, d.group_id, g.name AS group_name,
|
||||
s.name AS service_name, s.slug AS service_slug,
|
||||
dr.content AS target_ip, dr.sync_status,
|
||||
sb.created_at, sb.updated_at
|
||||
SELECT ${sql2.raw(SERVICE_BINDING_SELECT_COLUMNS)}
|
||||
FROM service_bindings sb
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
LEFT JOIN groups g ON g.id = d.group_id
|
||||
@@ -774,11 +924,7 @@ function listAllBindings(db) {
|
||||
}
|
||||
function listBindingsByDomain(db, domainId) {
|
||||
return db.all(sql2`
|
||||
SELECT sb.id, sb.domain_id, sb.service_id, sb.hostname, sb.dns_record_id,
|
||||
d.zone_name, d.group_id, g.name AS group_name,
|
||||
s.name AS service_name, s.slug AS service_slug,
|
||||
dr.content AS target_ip, dr.sync_status,
|
||||
sb.created_at, sb.updated_at
|
||||
SELECT ${sql2.raw(SERVICE_BINDING_SELECT_COLUMNS)}
|
||||
FROM service_bindings sb
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
LEFT JOIN groups g ON g.id = d.group_id
|
||||
@@ -802,11 +948,7 @@ function getBinding(db, id) {
|
||||
}
|
||||
function getBindingView(db, id) {
|
||||
const rows = db.all(sql2`
|
||||
SELECT sb.id, sb.domain_id, sb.service_id, sb.hostname, sb.dns_record_id,
|
||||
d.zone_name, d.group_id, g.name AS group_name,
|
||||
s.name AS service_name, s.slug AS service_slug,
|
||||
dr.content AS target_ip, dr.sync_status,
|
||||
sb.created_at, sb.updated_at
|
||||
SELECT ${sql2.raw(SERVICE_BINDING_SELECT_COLUMNS)}
|
||||
FROM service_bindings sb
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
LEFT JOIN groups g ON g.id = d.group_id
|
||||
@@ -932,6 +1074,88 @@ function finishSyncJob(db, id, status, message) {
|
||||
finished_at: sql2`datetime('now')`
|
||||
}).where(eq(syncJobs.id, id)).run();
|
||||
}
|
||||
function listIpHealthStatus(db, scope, refId) {
|
||||
return db.all(sql2`
|
||||
SELECT scope, ref_id, ip, status, latency_ms, consecutive_failures,
|
||||
last_checked_at, last_error
|
||||
FROM ip_health_status
|
||||
WHERE scope = ${scope} AND ref_id = ${refId}
|
||||
`);
|
||||
}
|
||||
function getIpHealthStatusRow(db, scope, refId, ip) {
|
||||
const rows = db.all(sql2`
|
||||
SELECT scope, ref_id, ip, status, latency_ms, consecutive_failures,
|
||||
last_checked_at, last_error
|
||||
FROM ip_health_status
|
||||
WHERE scope = ${scope} AND ref_id = ${refId} AND ip = ${ip}
|
||||
LIMIT 1
|
||||
`);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
function upsertIpHealthStatus(db, scope, refId, ip, status, latencyMs, consecutiveFailures, lastError) {
|
||||
db.run(sql2`
|
||||
INSERT INTO ip_health_status
|
||||
(scope, ref_id, ip, status, latency_ms, consecutive_failures,
|
||||
last_checked_at, last_error, created_at, updated_at)
|
||||
VALUES (${scope}, ${refId}, ${ip}, ${status}, ${latencyMs}, ${consecutiveFailures},
|
||||
datetime('now'), ${lastError}, datetime('now'), datetime('now'))
|
||||
ON CONFLICT(scope, ref_id, ip) DO UPDATE SET
|
||||
status = excluded.status,
|
||||
latency_ms = excluded.latency_ms,
|
||||
consecutive_failures = excluded.consecutive_failures,
|
||||
last_checked_at = excluded.last_checked_at,
|
||||
last_error = excluded.last_error,
|
||||
updated_at = datetime('now')
|
||||
`);
|
||||
}
|
||||
function deleteIpHealthStatusForRef(db, scope, refId) {
|
||||
db.delete(ipHealthStatus).where(
|
||||
and(
|
||||
eq(ipHealthStatus.scope, scope),
|
||||
eq(ipHealthStatus.ref_id, refId)
|
||||
)
|
||||
).run();
|
||||
}
|
||||
function deleteIpHealthStatusForIp(db, scope, refId, ip) {
|
||||
db.delete(ipHealthStatus).where(
|
||||
and(
|
||||
eq(ipHealthStatus.scope, scope),
|
||||
eq(ipHealthStatus.ref_id, refId),
|
||||
eq(ipHealthStatus.ip, ip)
|
||||
)
|
||||
).run();
|
||||
}
|
||||
function listHealthCheckTargets(db) {
|
||||
const bindingTargets = db.all(sql2`
|
||||
SELECT 'binding' AS scope, sb.id AS ref_id, sbi.ip,
|
||||
sb.hostname AS hostname,
|
||||
sb.health_check_type AS type,
|
||||
sb.health_check_port AS port,
|
||||
sb.health_check_path AS path,
|
||||
sb.health_check_expected_status AS expected_status,
|
||||
sb.health_check_timeout_ms AS timeout_ms
|
||||
FROM service_binding_ips sbi
|
||||
JOIN service_bindings sb ON sb.id = sbi.binding_id
|
||||
WHERE sb.health_check_enabled = 1
|
||||
`);
|
||||
const groupTargets = db.all(sql2`
|
||||
SELECT 'group' AS scope, sg.id AS ref_id, sip.ip,
|
||||
sg.domain AS hostname,
|
||||
sg.health_check_type AS type,
|
||||
sg.health_check_port AS port,
|
||||
sg.health_check_path AS path,
|
||||
sg.health_check_expected_status AS expected_status,
|
||||
sg.health_check_timeout_ms AS timeout_ms
|
||||
FROM services s
|
||||
JOIN service_ips sip ON sip.service_id = s.id
|
||||
JOIN service_groups sg ON sg.id = s.service_group_id
|
||||
WHERE sg.health_check_enabled = 1
|
||||
AND sg.domain IS NOT NULL
|
||||
AND s.enabled = 1
|
||||
AND (sg.enabled = 1)
|
||||
`);
|
||||
return [...bindingTargets, ...groupTargets];
|
||||
}
|
||||
export {
|
||||
ConflictError,
|
||||
NotFoundError,
|
||||
@@ -942,6 +1166,7 @@ export {
|
||||
domains,
|
||||
groups,
|
||||
healthCheck,
|
||||
ipHealthStatus,
|
||||
repos_exports as repos,
|
||||
resolveDatabasePath,
|
||||
runMigrations,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
ALTER TABLE services ADD COLUMN lb_weight INTEGER NOT NULL DEFAULT 1;
|
||||
ALTER TABLE services ADD COLUMN lb_priority INTEGER NOT NULL DEFAULT 1;
|
||||
|
||||
ALTER TABLE service_groups ADD COLUMN lb_mode TEXT NOT NULL DEFAULT 'round_robin';
|
||||
ALTER TABLE service_groups ADD COLUMN health_check_enabled INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE service_groups ADD COLUMN health_check_type TEXT NOT NULL DEFAULT 'tcp';
|
||||
ALTER TABLE service_groups ADD COLUMN health_check_port INTEGER;
|
||||
ALTER TABLE service_groups ADD COLUMN health_check_path TEXT;
|
||||
ALTER TABLE service_groups ADD COLUMN health_check_expected_status INTEGER;
|
||||
ALTER TABLE service_groups ADD COLUMN health_check_interval_sec INTEGER NOT NULL DEFAULT 30;
|
||||
ALTER TABLE service_groups ADD COLUMN health_check_timeout_ms INTEGER NOT NULL DEFAULT 3000;
|
||||
|
||||
ALTER TABLE service_bindings ADD COLUMN lb_mode TEXT NOT NULL DEFAULT 'round_robin';
|
||||
ALTER TABLE service_bindings ADD COLUMN health_check_enabled INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE service_bindings ADD COLUMN health_check_type TEXT NOT NULL DEFAULT 'tcp';
|
||||
ALTER TABLE service_bindings ADD COLUMN health_check_port INTEGER;
|
||||
ALTER TABLE service_bindings ADD COLUMN health_check_path TEXT;
|
||||
ALTER TABLE service_bindings ADD COLUMN health_check_expected_status INTEGER;
|
||||
ALTER TABLE service_bindings ADD COLUMN health_check_interval_sec INTEGER NOT NULL DEFAULT 30;
|
||||
ALTER TABLE service_bindings ADD COLUMN health_check_timeout_ms INTEGER NOT NULL DEFAULT 3000;
|
||||
|
||||
ALTER TABLE service_binding_ips ADD COLUMN weight INTEGER NOT NULL DEFAULT 1;
|
||||
ALTER TABLE service_binding_ips ADD COLUMN priority INTEGER NOT NULL DEFAULT 1;
|
||||
|
||||
CREATE TABLE ip_health_status (
|
||||
scope TEXT NOT NULL,
|
||||
ref_id INTEGER NOT NULL,
|
||||
ip TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'unknown',
|
||||
latency_ms INTEGER,
|
||||
consecutive_failures INTEGER NOT NULL DEFAULT 0,
|
||||
last_checked_at TEXT,
|
||||
last_error TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
PRIMARY KEY (scope, ref_id, ip)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_ip_health_status_scope_ref ON ip_health_status(scope, ref_id);
|
||||
+324
-31
@@ -5,6 +5,11 @@ import type {
|
||||
DomainListItem,
|
||||
Group,
|
||||
GroupWithStats,
|
||||
HealthCheckScope,
|
||||
HealthCheckTarget,
|
||||
HealthCheckType,
|
||||
IpHealthStatus,
|
||||
LbMode,
|
||||
Service,
|
||||
ServiceBinding,
|
||||
ServiceBindingView,
|
||||
@@ -21,6 +26,7 @@ import {
|
||||
dnsRecords,
|
||||
domains,
|
||||
groups,
|
||||
ipHealthStatus,
|
||||
serviceBindingIps,
|
||||
serviceBindingRecords,
|
||||
serviceBindings,
|
||||
@@ -578,6 +584,22 @@ export function setServiceEnabled(
|
||||
return getService(db, id);
|
||||
}
|
||||
|
||||
export function setServiceLb(
|
||||
db: Db,
|
||||
id: number,
|
||||
weight: number,
|
||||
priority: number,
|
||||
): void {
|
||||
db.update(services)
|
||||
.set({
|
||||
lb_weight: weight,
|
||||
lb_priority: priority,
|
||||
updated_at: sql`datetime('now')`,
|
||||
})
|
||||
.where(eq(services.id, id))
|
||||
.run();
|
||||
}
|
||||
|
||||
export function setServiceGroup(
|
||||
db: Db,
|
||||
id: number,
|
||||
@@ -648,6 +670,14 @@ function mapServiceGroup(row: typeof serviceGroups.$inferSelect): ServiceGroup {
|
||||
icon: row.icon,
|
||||
domain: row.domain,
|
||||
enabled: row.enabled,
|
||||
lb_mode: row.lb_mode as LbMode,
|
||||
health_check_enabled: row.health_check_enabled,
|
||||
health_check_type: row.health_check_type as HealthCheckType,
|
||||
health_check_port: row.health_check_port,
|
||||
health_check_path: row.health_check_path,
|
||||
health_check_expected_status: row.health_check_expected_status,
|
||||
health_check_interval_sec: row.health_check_interval_sec,
|
||||
health_check_timeout_ms: row.health_check_timeout_ms,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
};
|
||||
@@ -672,16 +702,41 @@ export function getServiceGroup(db: Db, id: number): ServiceGroup {
|
||||
return mapServiceGroup(row);
|
||||
}
|
||||
|
||||
export interface ServiceGroupLbPatch {
|
||||
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;
|
||||
}
|
||||
|
||||
export function createServiceGroup(
|
||||
db: Db,
|
||||
name: string,
|
||||
groupType: string,
|
||||
icon: string | null,
|
||||
domain: string | null,
|
||||
lbPatch?: ServiceGroupLbPatch,
|
||||
): ServiceGroup {
|
||||
const id = db
|
||||
.insert(serviceGroups)
|
||||
.values({ name, type: groupType, icon, domain })
|
||||
.values({
|
||||
name,
|
||||
type: groupType,
|
||||
icon,
|
||||
domain,
|
||||
lb_mode: lbPatch?.lb_mode ?? "round_robin",
|
||||
health_check_enabled: lbPatch?.health_check_enabled ?? false,
|
||||
health_check_type: lbPatch?.health_check_type ?? "tcp",
|
||||
health_check_port: lbPatch?.health_check_port ?? null,
|
||||
health_check_path: lbPatch?.health_check_path ?? null,
|
||||
health_check_expected_status: lbPatch?.health_check_expected_status ?? null,
|
||||
health_check_interval_sec: lbPatch?.health_check_interval_sec ?? 30,
|
||||
health_check_timeout_ms: lbPatch?.health_check_timeout_ms ?? 3000,
|
||||
})
|
||||
.returning({ id: serviceGroups.id })
|
||||
.get()!.id;
|
||||
return getServiceGroup(db, id);
|
||||
@@ -694,16 +749,35 @@ export function updateServiceGroup(
|
||||
groupType: string,
|
||||
icon: string | null,
|
||||
domain: string | null,
|
||||
lbPatch?: ServiceGroupLbPatch,
|
||||
): ServiceGroup {
|
||||
const update: Record<string, unknown> = {
|
||||
name,
|
||||
type: groupType,
|
||||
icon,
|
||||
domain,
|
||||
updated_at: sql`datetime('now')`,
|
||||
};
|
||||
if (lbPatch) {
|
||||
if (lbPatch.lb_mode !== undefined) update.lb_mode = lbPatch.lb_mode;
|
||||
if (lbPatch.health_check_enabled !== undefined)
|
||||
update.health_check_enabled = lbPatch.health_check_enabled;
|
||||
if (lbPatch.health_check_type !== undefined)
|
||||
update.health_check_type = lbPatch.health_check_type;
|
||||
if (lbPatch.health_check_port !== undefined)
|
||||
update.health_check_port = lbPatch.health_check_port;
|
||||
if (lbPatch.health_check_path !== undefined)
|
||||
update.health_check_path = lbPatch.health_check_path;
|
||||
if (lbPatch.health_check_expected_status !== undefined)
|
||||
update.health_check_expected_status = lbPatch.health_check_expected_status;
|
||||
if (lbPatch.health_check_interval_sec !== undefined)
|
||||
update.health_check_interval_sec = lbPatch.health_check_interval_sec;
|
||||
if (lbPatch.health_check_timeout_ms !== undefined)
|
||||
update.health_check_timeout_ms = lbPatch.health_check_timeout_ms;
|
||||
}
|
||||
const result = db
|
||||
.update(serviceGroups)
|
||||
.set({
|
||||
name,
|
||||
type: groupType,
|
||||
icon,
|
||||
domain,
|
||||
updated_at: sql`datetime('now')`,
|
||||
})
|
||||
.set(update)
|
||||
.where(eq(serviceGroups.id, id))
|
||||
.run();
|
||||
if (result.changes === 0) throw new NotFoundError(`service group ${id}`);
|
||||
@@ -753,6 +827,12 @@ export function replaceServiceIps(
|
||||
|
||||
// --- Service Binding IPs ---
|
||||
|
||||
export interface BindingIpMeta {
|
||||
ip: string;
|
||||
weight: number;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
export function listBindingIps(db: Db, bindingId: number): string[] {
|
||||
return db
|
||||
.select({ ip: serviceBindingIps.ip })
|
||||
@@ -762,19 +842,93 @@ export function listBindingIps(db: Db, bindingId: number): string[] {
|
||||
.map((r) => r.ip);
|
||||
}
|
||||
|
||||
export function listBindingIpsWithMeta(
|
||||
db: Db,
|
||||
bindingId: number,
|
||||
): BindingIpMeta[] {
|
||||
return db
|
||||
.select({
|
||||
ip: serviceBindingIps.ip,
|
||||
weight: serviceBindingIps.weight,
|
||||
priority: serviceBindingIps.priority,
|
||||
})
|
||||
.from(serviceBindingIps)
|
||||
.where(eq(serviceBindingIps.binding_id, bindingId))
|
||||
.all();
|
||||
}
|
||||
|
||||
export function replaceBindingIps(
|
||||
db: Db,
|
||||
bindingId: number,
|
||||
ips: string[],
|
||||
): void {
|
||||
replaceBindingIpsWithMeta(
|
||||
db,
|
||||
bindingId,
|
||||
ips.map((ip) => ({ ip, weight: 1, priority: 1 })),
|
||||
);
|
||||
}
|
||||
|
||||
export function replaceBindingIpsWithMeta(
|
||||
db: Db,
|
||||
bindingId: number,
|
||||
entries: BindingIpMeta[],
|
||||
): void {
|
||||
db.delete(serviceBindingIps)
|
||||
.where(eq(serviceBindingIps.binding_id, bindingId))
|
||||
.run();
|
||||
for (const ip of ips) {
|
||||
db.insert(serviceBindingIps).values({ binding_id: bindingId, ip }).run();
|
||||
for (const entry of entries) {
|
||||
db.insert(serviceBindingIps)
|
||||
.values({
|
||||
binding_id: bindingId,
|
||||
ip: entry.ip,
|
||||
weight: entry.weight,
|
||||
priority: entry.priority,
|
||||
})
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
||||
export interface BindingLbPatch {
|
||||
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;
|
||||
}
|
||||
|
||||
export function updateBindingLbConfig(
|
||||
db: Db,
|
||||
bindingId: number,
|
||||
patch: BindingLbPatch,
|
||||
): void {
|
||||
const update: Record<string, unknown> = {
|
||||
updated_at: sql`datetime('now')`,
|
||||
};
|
||||
if (patch.lb_mode !== undefined) update.lb_mode = patch.lb_mode;
|
||||
if (patch.health_check_enabled !== undefined)
|
||||
update.health_check_enabled = patch.health_check_enabled;
|
||||
if (patch.health_check_type !== undefined)
|
||||
update.health_check_type = patch.health_check_type;
|
||||
if (patch.health_check_port !== undefined)
|
||||
update.health_check_port = patch.health_check_port;
|
||||
if (patch.health_check_path !== undefined)
|
||||
update.health_check_path = patch.health_check_path;
|
||||
if (patch.health_check_expected_status !== undefined)
|
||||
update.health_check_expected_status = patch.health_check_expected_status;
|
||||
if (patch.health_check_interval_sec !== undefined)
|
||||
update.health_check_interval_sec = patch.health_check_interval_sec;
|
||||
if (patch.health_check_timeout_ms !== undefined)
|
||||
update.health_check_timeout_ms = patch.health_check_timeout_ms;
|
||||
db.update(serviceBindings)
|
||||
.set(update)
|
||||
.where(eq(serviceBindings.id, bindingId))
|
||||
.run();
|
||||
}
|
||||
|
||||
export function setBindingCnameTarget(
|
||||
db: Db,
|
||||
bindingId: number,
|
||||
@@ -873,11 +1027,23 @@ function dnsRecordMatchesHostname(
|
||||
return dnsRecordNamesMatch(recordName, hostname, zoneName);
|
||||
}
|
||||
|
||||
const SERVICE_BINDING_SELECT_COLUMNS = `sb.id, sb.domain_id, sb.service_id, sb.hostname, sb.dns_record_id,
|
||||
sb.lb_mode, sb.health_check_enabled, sb.health_check_type, sb.health_check_port,
|
||||
sb.health_check_path, sb.health_check_expected_status, sb.health_check_interval_sec,
|
||||
sb.health_check_timeout_ms, sb.cname_target,
|
||||
d.zone_name, d.group_id, g.name AS group_name,
|
||||
s.name AS service_name, s.slug AS service_slug,
|
||||
dr.content AS target_ip, dr.sync_status,
|
||||
sb.created_at, sb.updated_at`;
|
||||
|
||||
function enrichServiceBindingView(
|
||||
db: Db,
|
||||
row: Omit<ServiceBindingView, "target_ips"> & { target_ips?: string[] },
|
||||
row: Omit<ServiceBindingView, "target_ips" | "target_ip_weights" | "target_ip_priorities"> & {
|
||||
target_ips?: string[];
|
||||
},
|
||||
): ServiceBindingView {
|
||||
const configured = listBindingIps(db, row.id);
|
||||
const configured = listBindingIpsWithMeta(db, row.id);
|
||||
const configuredIps = configured.map((c) => c.ip);
|
||||
const linkedRecords = listRecordsForBinding(db, row.id);
|
||||
const linkedIps = linkedRecords
|
||||
.filter((record) => record.record_type.toUpperCase() === "A")
|
||||
@@ -885,7 +1051,7 @@ function enrichServiceBindingView(
|
||||
|
||||
const target_ips = [
|
||||
...new Set([
|
||||
...configured,
|
||||
...configuredIps,
|
||||
...linkedIps,
|
||||
...(row.target_ip ? [row.target_ip] : []),
|
||||
]),
|
||||
@@ -904,6 +1070,17 @@ function enrichServiceBindingView(
|
||||
target_ips.sort();
|
||||
}
|
||||
|
||||
const target_ip_weights: Record<string, number> = {};
|
||||
const target_ip_priorities: Record<string, number> = {};
|
||||
for (const entry of configured) {
|
||||
target_ip_weights[entry.ip] = entry.weight;
|
||||
target_ip_priorities[entry.ip] = entry.priority;
|
||||
}
|
||||
for (const ip of target_ips) {
|
||||
if (target_ip_weights[ip] === undefined) target_ip_weights[ip] = 1;
|
||||
if (target_ip_priorities[ip] === undefined) target_ip_priorities[ip] = 1;
|
||||
}
|
||||
|
||||
const sync_status =
|
||||
row.sync_status ??
|
||||
linkedRecords.find((record) => record.sync_status)?.sync_status ??
|
||||
@@ -913,18 +1090,16 @@ function enrichServiceBindingView(
|
||||
...row,
|
||||
target_ips,
|
||||
target_ip: target_ips[0] ?? null,
|
||||
target_ip_weights,
|
||||
target_ip_priorities,
|
||||
sync_status,
|
||||
};
|
||||
}
|
||||
|
||||
export function listAllBindings(db: Db): ServiceBindingView[] {
|
||||
return db
|
||||
.all<Omit<ServiceBindingView, "target_ips">>(sql`
|
||||
SELECT sb.id, sb.domain_id, sb.service_id, sb.hostname, sb.dns_record_id,
|
||||
d.zone_name, d.group_id, g.name AS group_name,
|
||||
s.name AS service_name, s.slug AS service_slug,
|
||||
dr.content AS target_ip, dr.sync_status,
|
||||
sb.created_at, sb.updated_at
|
||||
.all<Omit<ServiceBindingView, "target_ips" | "target_ip_weights" | "target_ip_priorities">>(sql`
|
||||
SELECT ${sql.raw(SERVICE_BINDING_SELECT_COLUMNS)}
|
||||
FROM service_bindings sb
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
LEFT JOIN groups g ON g.id = d.group_id
|
||||
@@ -937,12 +1112,8 @@ export function listAllBindings(db: Db): ServiceBindingView[] {
|
||||
|
||||
export function listBindingsByDomain(db: Db, domainId: number): ServiceBindingView[] {
|
||||
return db
|
||||
.all<Omit<ServiceBindingView, "target_ips">>(sql`
|
||||
SELECT sb.id, sb.domain_id, sb.service_id, sb.hostname, sb.dns_record_id,
|
||||
d.zone_name, d.group_id, g.name AS group_name,
|
||||
s.name AS service_name, s.slug AS service_slug,
|
||||
dr.content AS target_ip, dr.sync_status,
|
||||
sb.created_at, sb.updated_at
|
||||
.all<Omit<ServiceBindingView, "target_ips" | "target_ip_weights" | "target_ip_priorities">>(sql`
|
||||
SELECT ${sql.raw(SERVICE_BINDING_SELECT_COLUMNS)}
|
||||
FROM service_bindings sb
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
LEFT JOIN groups g ON g.id = d.group_id
|
||||
@@ -973,12 +1144,8 @@ export function getBinding(db: Db, id: number): ServiceBinding {
|
||||
}
|
||||
|
||||
export function getBindingView(db: Db, id: number): ServiceBindingView {
|
||||
const rows = db.all<Omit<ServiceBindingView, "target_ips">>(sql`
|
||||
SELECT sb.id, sb.domain_id, sb.service_id, sb.hostname, sb.dns_record_id,
|
||||
d.zone_name, d.group_id, g.name AS group_name,
|
||||
s.name AS service_name, s.slug AS service_slug,
|
||||
dr.content AS target_ip, dr.sync_status,
|
||||
sb.created_at, sb.updated_at
|
||||
const rows = db.all<Omit<ServiceBindingView, "target_ips" | "target_ip_weights" | "target_ip_priorities">>(sql`
|
||||
SELECT ${sql.raw(SERVICE_BINDING_SELECT_COLUMNS)}
|
||||
FROM service_bindings sb
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
LEFT JOIN groups g ON g.id = d.group_id
|
||||
@@ -1225,3 +1392,129 @@ export function finishSyncJob(
|
||||
.where(eq(syncJobs.id, id))
|
||||
.run();
|
||||
}
|
||||
|
||||
// --- IP Health Status ---
|
||||
|
||||
export function listIpHealthStatus(
|
||||
db: Db,
|
||||
scope: HealthCheckScope,
|
||||
refId: number,
|
||||
): IpHealthStatus[] {
|
||||
return db
|
||||
.all<IpHealthStatus>(sql`
|
||||
SELECT scope, ref_id, ip, status, latency_ms, consecutive_failures,
|
||||
last_checked_at, last_error
|
||||
FROM ip_health_status
|
||||
WHERE scope = ${scope} AND ref_id = ${refId}
|
||||
`);
|
||||
}
|
||||
|
||||
export function getIpHealthStatusRow(
|
||||
db: Db,
|
||||
scope: HealthCheckScope,
|
||||
refId: number,
|
||||
ip: string,
|
||||
): IpHealthStatus | null {
|
||||
const rows = db.all<IpHealthStatus>(sql`
|
||||
SELECT scope, ref_id, ip, status, latency_ms, consecutive_failures,
|
||||
last_checked_at, last_error
|
||||
FROM ip_health_status
|
||||
WHERE scope = ${scope} AND ref_id = ${refId} AND ip = ${ip}
|
||||
LIMIT 1
|
||||
`);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
export function upsertIpHealthStatus(
|
||||
db: Db,
|
||||
scope: HealthCheckScope,
|
||||
refId: number,
|
||||
ip: string,
|
||||
status: string,
|
||||
latencyMs: number | null,
|
||||
consecutiveFailures: number,
|
||||
lastError: string | null,
|
||||
): void {
|
||||
db.run(sql`
|
||||
INSERT INTO ip_health_status
|
||||
(scope, ref_id, ip, status, latency_ms, consecutive_failures,
|
||||
last_checked_at, last_error, created_at, updated_at)
|
||||
VALUES (${scope}, ${refId}, ${ip}, ${status}, ${latencyMs}, ${consecutiveFailures},
|
||||
datetime('now'), ${lastError}, datetime('now'), datetime('now'))
|
||||
ON CONFLICT(scope, ref_id, ip) DO UPDATE SET
|
||||
status = excluded.status,
|
||||
latency_ms = excluded.latency_ms,
|
||||
consecutive_failures = excluded.consecutive_failures,
|
||||
last_checked_at = excluded.last_checked_at,
|
||||
last_error = excluded.last_error,
|
||||
updated_at = datetime('now')
|
||||
`);
|
||||
}
|
||||
|
||||
export function deleteIpHealthStatusForRef(
|
||||
db: Db,
|
||||
scope: HealthCheckScope,
|
||||
refId: number,
|
||||
): void {
|
||||
db.delete(ipHealthStatus)
|
||||
.where(
|
||||
and(
|
||||
eq(ipHealthStatus.scope, scope),
|
||||
eq(ipHealthStatus.ref_id, refId),
|
||||
),
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
export function deleteIpHealthStatusForIp(
|
||||
db: Db,
|
||||
scope: HealthCheckScope,
|
||||
refId: number,
|
||||
ip: string,
|
||||
): void {
|
||||
db.delete(ipHealthStatus)
|
||||
.where(
|
||||
and(
|
||||
eq(ipHealthStatus.scope, scope),
|
||||
eq(ipHealthStatus.ref_id, refId),
|
||||
eq(ipHealthStatus.ip, ip),
|
||||
),
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
// --- Health Check Targets ---
|
||||
|
||||
export function listHealthCheckTargets(db: Db): HealthCheckTarget[] {
|
||||
const bindingTargets = db.all<HealthCheckTarget>(sql`
|
||||
SELECT 'binding' AS scope, sb.id AS ref_id, sbi.ip,
|
||||
sb.hostname AS hostname,
|
||||
sb.health_check_type AS type,
|
||||
sb.health_check_port AS port,
|
||||
sb.health_check_path AS path,
|
||||
sb.health_check_expected_status AS expected_status,
|
||||
sb.health_check_timeout_ms AS timeout_ms
|
||||
FROM service_binding_ips sbi
|
||||
JOIN service_bindings sb ON sb.id = sbi.binding_id
|
||||
WHERE sb.health_check_enabled = 1
|
||||
`);
|
||||
|
||||
const groupTargets = db.all<HealthCheckTarget>(sql`
|
||||
SELECT 'group' AS scope, sg.id AS ref_id, sip.ip,
|
||||
sg.domain AS hostname,
|
||||
sg.health_check_type AS type,
|
||||
sg.health_check_port AS port,
|
||||
sg.health_check_path AS path,
|
||||
sg.health_check_expected_status AS expected_status,
|
||||
sg.health_check_timeout_ms AS timeout_ms
|
||||
FROM services s
|
||||
JOIN service_ips sip ON sip.service_id = s.id
|
||||
JOIN service_groups sg ON sg.id = s.service_group_id
|
||||
WHERE sg.health_check_enabled = 1
|
||||
AND sg.domain IS NOT NULL
|
||||
AND s.enabled = 1
|
||||
AND (sg.enabled = 1)
|
||||
`);
|
||||
|
||||
return [...bindingTargets, ...groupTargets];
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ export const services = sqliteTable("services", {
|
||||
subdomain: text("subdomain"),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(false),
|
||||
sort_order: integer("sort_order").notNull().default(0),
|
||||
lb_weight: integer("lb_weight").notNull().default(1),
|
||||
lb_priority: integer("lb_priority").notNull().default(1),
|
||||
created_at: text("created_at")
|
||||
.notNull()
|
||||
.default(sql`datetime('now')`),
|
||||
@@ -44,6 +46,20 @@ export const serviceGroups = sqliteTable("service_groups", {
|
||||
icon: text("icon"),
|
||||
domain: text("domain"),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
lb_mode: text("lb_mode").notNull().default("round_robin"),
|
||||
health_check_enabled: integer("health_check_enabled", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
health_check_type: text("health_check_type").notNull().default("tcp"),
|
||||
health_check_port: integer("health_check_port"),
|
||||
health_check_path: text("health_check_path"),
|
||||
health_check_expected_status: integer("health_check_expected_status"),
|
||||
health_check_interval_sec: integer("health_check_interval_sec")
|
||||
.notNull()
|
||||
.default(30),
|
||||
health_check_timeout_ms: integer("health_check_timeout_ms")
|
||||
.notNull()
|
||||
.default(3000),
|
||||
created_at: text("created_at")
|
||||
.notNull()
|
||||
.default(sql`datetime('now')`),
|
||||
@@ -123,6 +139,20 @@ export const serviceBindings = sqliteTable("service_bindings", {
|
||||
dns_record_id: integer("dns_record_id").references(() => dnsRecords.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
lb_mode: text("lb_mode").notNull().default("round_robin"),
|
||||
health_check_enabled: integer("health_check_enabled", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
health_check_type: text("health_check_type").notNull().default("tcp"),
|
||||
health_check_port: integer("health_check_port"),
|
||||
health_check_path: text("health_check_path"),
|
||||
health_check_expected_status: integer("health_check_expected_status"),
|
||||
health_check_interval_sec: integer("health_check_interval_sec")
|
||||
.notNull()
|
||||
.default(30),
|
||||
health_check_timeout_ms: integer("health_check_timeout_ms")
|
||||
.notNull()
|
||||
.default(3000),
|
||||
created_at: text("created_at")
|
||||
.notNull()
|
||||
.default(sql`datetime('now')`),
|
||||
@@ -162,6 +192,8 @@ export const serviceBindingIps = sqliteTable(
|
||||
.notNull()
|
||||
.references(() => serviceBindings.id, { onDelete: "cascade" }),
|
||||
ip: text("ip").notNull(),
|
||||
weight: integer("weight").notNull().default(1),
|
||||
priority: integer("priority").notNull().default(1),
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.binding_id, t.ip] })],
|
||||
);
|
||||
@@ -213,6 +245,29 @@ export const syncJobs = sqliteTable("sync_jobs", {
|
||||
finished_at: text("finished_at"),
|
||||
});
|
||||
|
||||
export const ipHealthStatus = sqliteTable(
|
||||
"ip_health_status",
|
||||
{
|
||||
scope: text("scope").notNull(),
|
||||
ref_id: integer("ref_id").notNull(),
|
||||
ip: text("ip").notNull(),
|
||||
status: text("status").notNull().default("unknown"),
|
||||
latency_ms: integer("latency_ms"),
|
||||
consecutive_failures: integer("consecutive_failures")
|
||||
.notNull()
|
||||
.default(0),
|
||||
last_checked_at: text("last_checked_at"),
|
||||
last_error: text("last_error"),
|
||||
created_at: text("created_at")
|
||||
.notNull()
|
||||
.default(sql`datetime('now')`),
|
||||
updated_at: text("updated_at")
|
||||
.notNull()
|
||||
.default(sql`datetime('now')`),
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.scope, t.ref_id, t.ip] })],
|
||||
);
|
||||
|
||||
export const schema = {
|
||||
groups,
|
||||
services,
|
||||
@@ -227,4 +282,5 @@ export const schema = {
|
||||
serviceGroupDnsRecords,
|
||||
certificates,
|
||||
syncJobs,
|
||||
ipHealthStatus,
|
||||
};
|
||||
|
||||
Vendored
+482
-13
@@ -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 };
|
||||
|
||||
Vendored
+91
-3
@@ -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
|
||||
};
|
||||
|
||||
@@ -15,4 +15,10 @@ export type {
|
||||
ServiceBindingView,
|
||||
ServiceDomainBindingView,
|
||||
Subdomain,
|
||||
LbMode,
|
||||
HealthCheckType,
|
||||
IpHealthState,
|
||||
HealthCheckScope,
|
||||
IpHealthStatus,
|
||||
HealthCheckTarget,
|
||||
} from "./types.js";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user