feat(api, web): enhance health check and domain management features
Build, Test, and Push CFDM Docker Image / test (push) Successful in 3m3s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 1m48s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 6s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped

- Integrated domain monitoring routes and bulk update functionality for domains in the API.
- Improved health check service to include domain monitoring and logging of health status changes.
- Updated web components to reflect health status with new HealthCheckBadge and enhanced domain filtering options.
- Refactored domain service to support bulk updates and improved domain management capabilities.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-17 02:25:12 +07:00
co-authored by Cursor
parent dd167a4fec
commit 64585ccd47
38 changed files with 4199 additions and 677 deletions
+1266 -3
View File
File diff suppressed because it is too large Load Diff
+232 -8
View File
@@ -62,6 +62,7 @@ var domains = sqliteTable("domains", {
cf_zone_id: text("cf_zone_id").notNull(),
status: text("status").notNull().default("active"),
cert_monitoring: text("cert_monitoring").notNull().default("auto"),
environment: text("environment"),
last_synced_at: text("last_synced_at"),
created_at: text("created_at").notNull().default(sql`datetime('now')`),
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
@@ -196,6 +197,48 @@ var appSettings = sqliteTable("app_settings", {
created_at: text("created_at").notNull().default(sql`datetime('now')`),
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
});
var domainTags = sqliteTable(
"domain_tags",
{
domain_id: integer("domain_id").notNull().references(() => domains.id, { onDelete: "cascade" }),
tag: text("tag").notNull()
},
(t) => [primaryKey({ columns: [t.domain_id, t.tag] })]
);
var domainMonitors = sqliteTable("domain_monitors", {
id: integer("id").primaryKey({ autoIncrement: true }),
domain_id: integer("domain_id").notNull().references(() => domains.id, { onDelete: "cascade" }),
hostname: text("hostname").notNull(),
type: text("type").notNull().default("http"),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
interval_sec: integer("interval_sec").notNull().default(60),
timeout_ms: integer("timeout_ms").notNull().default(5e3),
path: text("path"),
expected_status: integer("expected_status"),
last_status: text("last_status").notNull().default("unknown"),
last_latency_ms: integer("last_latency_ms"),
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')`)
});
var domainMonitorResults = sqliteTable("domain_monitor_results", {
id: integer("id").primaryKey({ autoIncrement: true }),
monitor_id: integer("monitor_id").notNull().references(() => domainMonitors.id, { onDelete: "cascade" }),
status: text("status").notNull(),
latency_ms: integer("latency_ms"),
error: text("error"),
checked_at: text("checked_at").notNull().default(sql`datetime('now')`)
});
var notificationLog = sqliteTable("notification_log", {
id: integer("id").primaryKey({ autoIncrement: true }),
kind: text("kind").notNull(),
ref_type: text("ref_type").notNull(),
ref_id: integer("ref_id"),
title: text("title").notNull(),
message: text("message").notNull(),
created_at: text("created_at").notNull().default(sql`datetime('now')`)
});
var schema = {
groups,
services,
@@ -211,7 +254,11 @@ var schema = {
certificates,
syncJobs,
ipHealthStatus,
appSettings
appSettings,
domainTags,
domainMonitors,
domainMonitorResults,
notificationLog
};
// src/client.ts
@@ -367,9 +414,11 @@ function getAppSwitcher(db) {
// src/repos.ts
var repos_exports = {};
__export(repos_exports, {
addDomainTags: () => addDomainTags,
bindingsToRemove: () => bindingsToRemove,
countCertificatesByStatus: () => countCertificatesByStatus,
createDomain: () => createDomain,
createDomainMonitor: () => createDomainMonitor,
createGroup: () => createGroup,
createService: () => createService,
createServiceGroup: () => createServiceGroup,
@@ -380,6 +429,7 @@ __export(repos_exports, {
deleteCertificatesNotIn: () => deleteCertificatesNotIn,
deleteDnsRecord: () => deleteDnsRecord,
deleteDomain: () => deleteDomain,
deleteDomainMonitor: () => deleteDomainMonitor,
deleteGroup: () => deleteGroup,
deleteIpHealthStatusForIp: () => deleteIpHealthStatusForIp,
deleteIpHealthStatusForRef: () => deleteIpHealthStatusForRef,
@@ -396,6 +446,7 @@ __export(repos_exports, {
getCertificate: () => getCertificate,
getDnsRecord: () => getDnsRecord,
getDomain: () => getDomain,
getDomainMonitor: () => getDomainMonitor,
getGroup: () => getGroup,
getGroupWithStats: () => getGroupWithStats,
getIpHealthStatusRow: () => getIpHealthStatusRow,
@@ -405,6 +456,7 @@ __export(repos_exports, {
getSyncJob: () => getSyncJob,
insertBinding: () => insertBinding,
insertDnsRecord: () => insertDnsRecord,
insertNotificationLog: () => insertNotificationLog,
linkBindingRecord: () => linkBindingRecord,
linkGroupDnsRecord: () => linkGroupDnsRecord,
listAllBindings: () => listAllBindings,
@@ -417,12 +469,18 @@ __export(repos_exports, {
listCertificates: () => listCertificates,
listDnsByDomain: () => listDnsByDomain,
listDnsRecords: () => listDnsRecords,
listDomainMonitorResults: () => listDomainMonitorResults,
listDomainMonitorResultsForDomain: () => listDomainMonitorResultsForDomain,
listDomainMonitors: () => listDomainMonitors,
listDomainTags: () => listDomainTags,
listDomains: () => listDomains,
listDomainsEnriched: () => listDomainsEnriched,
listEnabledDomainMonitors: () => listEnabledDomainMonitors,
listGroupDnsRecords: () => listGroupDnsRecords,
listGroups: () => listGroups,
listHealthCheckTargets: () => listHealthCheckTargets,
listIpHealthStatus: () => listIpHealthStatus,
listNotificationLog: () => listNotificationLog,
listRecordsForBinding: () => listRecordsForBinding,
listServiceGroups: () => listServiceGroups,
listServiceIps: () => listServiceIps,
@@ -439,6 +497,7 @@ __export(repos_exports, {
setBindingDnsRecordId: () => setBindingDnsRecordId,
setDnsSyncStatus: () => setDnsSyncStatus,
setDomainLastSynced: () => setDomainLastSynced,
setDomainTags: () => setDomainTags,
setServiceEnabled: () => setServiceEnabled,
setServiceGroup: () => setServiceGroup,
setServiceGroupEnabled: () => setServiceGroupEnabled,
@@ -449,6 +508,7 @@ __export(repos_exports, {
updateBindingLbConfig: () => updateBindingLbConfig,
updateDnsFields: () => updateDnsFields,
updateDomain: () => updateDomain,
updateDomainMonitorResult: () => updateDomainMonitorResult,
updateGroup: () => updateGroup,
updateService: () => updateService,
updateServiceGroup: () => updateServiceGroup,
@@ -497,14 +557,61 @@ function listDomains(db, groupId) {
}
function listDomainsEnriched(db, groupId) {
const base = groupId != null ? sql2`WHERE d.group_id = ${groupId}` : sql2``;
return db.all(sql2`
const rows = db.all(sql2`
SELECT d.*, g.name AS group_name,
(SELECT COUNT(*) FROM service_bindings sb WHERE sb.domain_id = d.id) AS service_count
(SELECT COUNT(*) FROM service_bindings sb WHERE sb.domain_id = d.id) AS service_count,
COALESCE((
SELECT CASE
WHEN MAX(CASE
WHEN ihs.status = 'down' THEN 3
WHEN ihs.status = 'degraded' THEN 2
WHEN ihs.status = 'up' THEN 1
ELSE 0
END) = 3 THEN 'down'
WHEN MAX(CASE
WHEN ihs.status = 'down' THEN 3
WHEN ihs.status = 'degraded' THEN 2
WHEN ihs.status = 'up' THEN 1
ELSE 0
END) = 2 THEN 'degraded'
WHEN MAX(CASE
WHEN ihs.status = 'down' THEN 3
WHEN ihs.status = 'degraded' THEN 2
WHEN ihs.status = 'up' THEN 1
ELSE 0
END) = 1 THEN 'up'
ELSE 'unknown'
END
FROM ip_health_status ihs
INNER JOIN service_bindings sb ON ihs.scope = 'binding' AND ihs.ref_id = sb.id
WHERE sb.domain_id = d.id
), 'unknown') AS health_status,
(
SELECT MAX(ihs.latency_ms)
FROM ip_health_status ihs
INNER JOIN service_bindings sb ON ihs.scope = 'binding' AND ihs.ref_id = sb.id
WHERE sb.domain_id = d.id
) AS health_latency_ms,
(
SELECT GROUP_CONCAT(dt.tag, ',')
FROM domain_tags dt
WHERE dt.domain_id = d.id
) AS tags_json
FROM domains d
LEFT JOIN groups g ON g.id = d.group_id
${base}
ORDER BY d.zone_name ASC
`);
return rows.map((row) => {
const { tags_json, ...rest } = row;
return {
...rest,
environment: rest.environment ?? null,
health_status: rest.health_status ?? "unknown",
health_latency_ms: rest.health_latency_ms ?? null,
tags: tags_json ? tags_json.split(",").map((t) => t.trim()).filter(Boolean) : []
};
});
}
function findDomainByZoneName(db, zoneName) {
const rows = db.all(sql2`
@@ -525,14 +632,18 @@ function createDomain(db, groupId, zoneName, cfZoneId) {
}).returning({ id: domains.id }).get().id;
return getDomain(db, id);
}
function updateDomain(db, id, groupId, status, certMonitoring) {
function updateDomain(db, id, patch) {
const existing = getDomain(db, id);
const updates = {
group_id: groupId,
status,
group_id: patch.group_id !== void 0 ? patch.group_id : existing.group_id,
status: patch.status !== void 0 ? patch.status : existing.status,
updated_at: sql2`datetime('now')`
};
if (certMonitoring !== void 0) {
updates.cert_monitoring = certMonitoring;
if (patch.cert_monitoring !== void 0) {
updates.cert_monitoring = patch.cert_monitoring;
}
if (patch.environment !== void 0) {
updates.environment = patch.environment;
}
const result = db.update(domains).set(updates).where(eq2(domains.id, id)).run();
if (result.changes === 0) throw new NotFoundError(`domain ${id}`);
@@ -1320,6 +1431,115 @@ function listHealthCheckTargets(db) {
...groupInheritedCnameBindingTargets
];
}
function listDomainTags(db, domainId) {
return db.select({ tag: domainTags.tag }).from(domainTags).where(eq2(domainTags.domain_id, domainId)).all().map((r) => r.tag);
}
function setDomainTags(db, domainId, tags) {
db.delete(domainTags).where(eq2(domainTags.domain_id, domainId)).run();
const unique = [...new Set(tags.map((t) => t.trim()).filter(Boolean))];
for (const tag of unique) {
db.insert(domainTags).values({ domain_id: domainId, tag }).run();
}
}
function addDomainTags(db, domainId, tags) {
const unique = [...new Set(tags.map((t) => t.trim()).filter(Boolean))];
for (const tag of unique) {
db.run(sql2`
INSERT INTO domain_tags (domain_id, tag)
VALUES (${domainId}, ${tag})
ON CONFLICT(domain_id, tag) DO NOTHING
`);
}
}
function listDomainMonitors(db, domainId) {
return db.select().from(domainMonitors).where(eq2(domainMonitors.domain_id, domainId)).orderBy(asc(domainMonitors.id)).all();
}
function listEnabledDomainMonitors(db) {
return db.select().from(domainMonitors).where(eq2(domainMonitors.enabled, true)).all();
}
function getDomainMonitor(db, id) {
const row = db.select().from(domainMonitors).where(eq2(domainMonitors.id, id)).get();
if (!row) throw new NotFoundError(`domain_monitor ${id}`);
return row;
}
function createDomainMonitor(db, domainId, input) {
const id = db.insert(domainMonitors).values({
domain_id: domainId,
hostname: input.hostname.trim(),
type: input.type,
enabled: input.enabled ?? true,
interval_sec: input.interval_sec ?? 60,
timeout_ms: input.timeout_ms ?? 5e3,
path: input.path ?? null,
expected_status: input.expected_status ?? null
}).returning({ id: domainMonitors.id }).get().id;
return getDomainMonitor(db, id);
}
function deleteDomainMonitor(db, id) {
const result = db.delete(domainMonitors).where(eq2(domainMonitors.id, id)).run();
if (result.changes === 0) throw new NotFoundError(`domain_monitor ${id}`);
}
function updateDomainMonitorResult(db, monitorId, status, latencyMs, error) {
db.update(domainMonitors).set({
last_status: status,
last_latency_ms: latencyMs,
last_checked_at: sql2`datetime('now')`,
last_error: error,
updated_at: sql2`datetime('now')`
}).where(eq2(domainMonitors.id, monitorId)).run();
db.insert(domainMonitorResults).values({
monitor_id: monitorId,
status,
latency_ms: latencyMs,
error
}).run();
db.run(sql2`
DELETE FROM domain_monitor_results
WHERE monitor_id = ${monitorId}
AND id NOT IN (
SELECT id FROM domain_monitor_results
WHERE monitor_id = ${monitorId}
ORDER BY checked_at DESC, id DESC
LIMIT 100
)
`);
}
function listDomainMonitorResults(db, monitorId, limit = 50) {
return db.all(sql2`
SELECT id, monitor_id, status, latency_ms, error, checked_at
FROM domain_monitor_results
WHERE monitor_id = ${monitorId}
ORDER BY checked_at DESC, id DESC
LIMIT ${limit}
`);
}
function listDomainMonitorResultsForDomain(db, domainId, limit = 50) {
return db.all(sql2`
SELECT r.id, r.monitor_id, m.hostname, m.type, r.status, r.latency_ms, r.error, r.checked_at
FROM domain_monitor_results r
JOIN domain_monitors m ON m.id = r.monitor_id
WHERE m.domain_id = ${domainId}
ORDER BY r.checked_at DESC, r.id DESC
LIMIT ${limit}
`);
}
function insertNotificationLog(db, kind, refType, refId, title, message) {
db.insert(notificationLog).values({
kind,
ref_type: refType,
ref_id: refId,
title,
message
}).run();
}
function listNotificationLog(db, limit = 50) {
return db.all(sql2`
SELECT id, kind, ref_type, ref_id, title, message, created_at
FROM notification_log
ORDER BY created_at DESC, id DESC
LIMIT ${limit}
`);
}
export {
ConflictError,
NotFoundError,
@@ -1328,6 +1548,9 @@ export {
createDb,
createMemoryDb,
dnsRecords,
domainMonitorResults,
domainMonitors,
domainTags,
domains,
getAppSettings,
getAppSettingsSecrets,
@@ -1335,6 +1558,7 @@ export {
groups,
healthCheck,
ipHealthStatus,
notificationLog,
repos_exports as repos,
resolveDatabasePath,
runMigrations,
@@ -0,0 +1,54 @@
-- Domain environment + tags + monitors + notification log
ALTER TABLE domains ADD COLUMN environment TEXT;
CREATE TABLE IF NOT EXISTS domain_tags (
domain_id INTEGER NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
tag TEXT NOT NULL,
PRIMARY KEY (domain_id, tag)
);
CREATE TABLE IF NOT EXISTS domain_monitors (
id INTEGER PRIMARY KEY AUTOINCREMENT,
domain_id INTEGER NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
hostname TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'http',
enabled INTEGER NOT NULL DEFAULT 1,
interval_sec INTEGER NOT NULL DEFAULT 60,
timeout_ms INTEGER NOT NULL DEFAULT 5000,
path TEXT,
expected_status INTEGER,
last_status TEXT NOT NULL DEFAULT 'unknown',
last_latency_ms INTEGER,
last_checked_at TEXT,
last_error TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_domain_monitors_domain
ON domain_monitors(domain_id);
CREATE TABLE IF NOT EXISTS domain_monitor_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
monitor_id INTEGER NOT NULL REFERENCES domain_monitors(id) ON DELETE CASCADE,
status TEXT NOT NULL,
latency_ms INTEGER,
error TEXT,
checked_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_domain_monitor_results_monitor
ON domain_monitor_results(monitor_id, checked_at DESC);
CREATE TABLE IF NOT EXISTS notification_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
kind TEXT NOT NULL,
ref_type TEXT NOT NULL,
ref_id INTEGER,
title TEXT NOT NULL,
message TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_notification_log_created
ON notification_log(created_at DESC);
+315 -15
View File
@@ -24,9 +24,13 @@ import { NotFoundError } from "./errors.js";
import {
certificates,
dnsRecords,
domainMonitorResults,
domainMonitors,
domainTags,
domains,
groups,
ipHealthStatus,
notificationLog,
serviceBindingIps,
serviceBindingRecords,
serviceBindings,
@@ -119,14 +123,65 @@ export function listDomainsEnriched(db: Db, groupId?: number): DomainListItem[]
const base = groupId != null
? sql`WHERE d.group_id = ${groupId}`
: sql``;
return db.all<DomainListItem>(sql`
const rows = db.all<
Omit<DomainListItem, "tags"> & { tags_json: string | null }
>(sql`
SELECT d.*, g.name AS group_name,
(SELECT COUNT(*) FROM service_bindings sb WHERE sb.domain_id = d.id) AS service_count
(SELECT COUNT(*) FROM service_bindings sb WHERE sb.domain_id = d.id) AS service_count,
COALESCE((
SELECT CASE
WHEN MAX(CASE
WHEN ihs.status = 'down' THEN 3
WHEN ihs.status = 'degraded' THEN 2
WHEN ihs.status = 'up' THEN 1
ELSE 0
END) = 3 THEN 'down'
WHEN MAX(CASE
WHEN ihs.status = 'down' THEN 3
WHEN ihs.status = 'degraded' THEN 2
WHEN ihs.status = 'up' THEN 1
ELSE 0
END) = 2 THEN 'degraded'
WHEN MAX(CASE
WHEN ihs.status = 'down' THEN 3
WHEN ihs.status = 'degraded' THEN 2
WHEN ihs.status = 'up' THEN 1
ELSE 0
END) = 1 THEN 'up'
ELSE 'unknown'
END
FROM ip_health_status ihs
INNER JOIN service_bindings sb ON ihs.scope = 'binding' AND ihs.ref_id = sb.id
WHERE sb.domain_id = d.id
), 'unknown') AS health_status,
(
SELECT MAX(ihs.latency_ms)
FROM ip_health_status ihs
INNER JOIN service_bindings sb ON ihs.scope = 'binding' AND ihs.ref_id = sb.id
WHERE sb.domain_id = d.id
) AS health_latency_ms,
(
SELECT GROUP_CONCAT(dt.tag, ',')
FROM domain_tags dt
WHERE dt.domain_id = d.id
) AS tags_json
FROM domains d
LEFT JOIN groups g ON g.id = d.group_id
${base}
ORDER BY d.zone_name ASC
`);
return rows.map((row) => {
const { tags_json, ...rest } = row;
return {
...rest,
environment: (rest.environment as DomainListItem["environment"]) ?? null,
health_status: rest.health_status ?? "unknown",
health_latency_ms: rest.health_latency_ms ?? null,
tags: tags_json
? tags_json.split(",").map((t) => t.trim()).filter(Boolean)
: [],
};
});
}
export function findDomainByZoneName(db: Db, zoneName: string): Domain | null {
@@ -163,22 +218,24 @@ export function createDomain(
export function updateDomain(
db: Db,
id: number,
groupId: number | null,
status: string,
certMonitoring?: string,
): Domain {
const updates: {
group_id: number | null;
status: string;
patch: {
group_id?: number | null;
status?: string;
cert_monitoring?: string;
updated_at: ReturnType<typeof sql>;
} = {
group_id: groupId,
status,
environment?: string | null;
},
): Domain {
const existing = getDomain(db, id);
const updates: Record<string, unknown> = {
group_id: patch.group_id !== undefined ? patch.group_id : existing.group_id,
status: patch.status !== undefined ? patch.status : existing.status,
updated_at: sql`datetime('now')`,
};
if (certMonitoring !== undefined) {
updates.cert_monitoring = certMonitoring;
if (patch.cert_monitoring !== undefined) {
updates.cert_monitoring = patch.cert_monitoring;
}
if (patch.environment !== undefined) {
updates.environment = patch.environment;
}
const result = db
.update(domains)
@@ -1595,3 +1652,246 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] {
...groupInheritedCnameBindingTargets,
];
}
// --- Domain tags ---
export function listDomainTags(db: Db, domainId: number): string[] {
return db
.select({ tag: domainTags.tag })
.from(domainTags)
.where(eq(domainTags.domain_id, domainId))
.all()
.map((r) => r.tag);
}
export function setDomainTags(db: Db, domainId: number, tags: string[]): void {
db.delete(domainTags).where(eq(domainTags.domain_id, domainId)).run();
const unique = [...new Set(tags.map((t) => t.trim()).filter(Boolean))];
for (const tag of unique) {
db.insert(domainTags).values({ domain_id: domainId, tag }).run();
}
}
export function addDomainTags(db: Db, domainId: number, tags: string[]): void {
const unique = [...new Set(tags.map((t) => t.trim()).filter(Boolean))];
for (const tag of unique) {
db.run(sql`
INSERT INTO domain_tags (domain_id, tag)
VALUES (${domainId}, ${tag})
ON CONFLICT(domain_id, tag) DO NOTHING
`);
}
}
// --- Domain monitors ---
export interface DomainMonitorRow {
id: number;
domain_id: number;
hostname: string;
type: string;
enabled: boolean;
interval_sec: number;
timeout_ms: number;
path: string | null;
expected_status: number | null;
last_status: string;
last_latency_ms: number | null;
last_checked_at: string | null;
last_error: string | null;
created_at: string;
updated_at: string;
}
export function listDomainMonitors(
db: Db,
domainId: number,
): DomainMonitorRow[] {
return db
.select()
.from(domainMonitors)
.where(eq(domainMonitors.domain_id, domainId))
.orderBy(asc(domainMonitors.id))
.all() as DomainMonitorRow[];
}
export function listEnabledDomainMonitors(db: Db): DomainMonitorRow[] {
return db
.select()
.from(domainMonitors)
.where(eq(domainMonitors.enabled, true))
.all() as DomainMonitorRow[];
}
export function getDomainMonitor(db: Db, id: number): DomainMonitorRow {
const row = db
.select()
.from(domainMonitors)
.where(eq(domainMonitors.id, id))
.get();
if (!row) throw new NotFoundError(`domain_monitor ${id}`);
return row as DomainMonitorRow;
}
export function createDomainMonitor(
db: Db,
domainId: number,
input: {
hostname: string;
type: string;
enabled?: boolean;
interval_sec?: number;
timeout_ms?: number;
path?: string | null;
expected_status?: number | null;
},
): DomainMonitorRow {
const id = db
.insert(domainMonitors)
.values({
domain_id: domainId,
hostname: input.hostname.trim(),
type: input.type,
enabled: input.enabled ?? true,
interval_sec: input.interval_sec ?? 60,
timeout_ms: input.timeout_ms ?? 5000,
path: input.path ?? null,
expected_status: input.expected_status ?? null,
})
.returning({ id: domainMonitors.id })
.get()!.id;
return getDomainMonitor(db, id);
}
export function deleteDomainMonitor(db: Db, id: number): void {
const result = db
.delete(domainMonitors)
.where(eq(domainMonitors.id, id))
.run();
if (result.changes === 0) throw new NotFoundError(`domain_monitor ${id}`);
}
export function updateDomainMonitorResult(
db: Db,
monitorId: number,
status: string,
latencyMs: number | null,
error: string | null,
): void {
db.update(domainMonitors)
.set({
last_status: status,
last_latency_ms: latencyMs,
last_checked_at: sql`datetime('now')`,
last_error: error,
updated_at: sql`datetime('now')`,
})
.where(eq(domainMonitors.id, monitorId))
.run();
db.insert(domainMonitorResults)
.values({
monitor_id: monitorId,
status,
latency_ms: latencyMs,
error,
})
.run();
// keep last 100 results per monitor
db.run(sql`
DELETE FROM domain_monitor_results
WHERE monitor_id = ${monitorId}
AND id NOT IN (
SELECT id FROM domain_monitor_results
WHERE monitor_id = ${monitorId}
ORDER BY checked_at DESC, id DESC
LIMIT 100
)
`);
}
export function listDomainMonitorResults(
db: Db,
monitorId: number,
limit = 50,
): {
id: number;
monitor_id: number;
status: string;
latency_ms: number | null;
error: string | null;
checked_at: string;
}[] {
return db.all(sql`
SELECT id, monitor_id, status, latency_ms, error, checked_at
FROM domain_monitor_results
WHERE monitor_id = ${monitorId}
ORDER BY checked_at DESC, id DESC
LIMIT ${limit}
`);
}
export function listDomainMonitorResultsForDomain(
db: Db,
domainId: number,
limit = 50,
): {
id: number;
monitor_id: number;
hostname: string;
type: string;
status: string;
latency_ms: number | null;
error: string | null;
checked_at: string;
}[] {
return db.all(sql`
SELECT r.id, r.monitor_id, m.hostname, m.type, r.status, r.latency_ms, r.error, r.checked_at
FROM domain_monitor_results r
JOIN domain_monitors m ON m.id = r.monitor_id
WHERE m.domain_id = ${domainId}
ORDER BY r.checked_at DESC, r.id DESC
LIMIT ${limit}
`);
}
// --- Notification log ---
export function insertNotificationLog(
db: Db,
kind: string,
refType: string,
refId: number | null,
title: string,
message: string,
): void {
db.insert(notificationLog)
.values({
kind,
ref_type: refType,
ref_id: refId,
title,
message,
})
.run();
}
export function listNotificationLog(
db: Db,
limit = 50,
): {
id: number;
kind: string;
ref_type: string;
ref_id: number | null;
title: string;
message: string;
created_at: string;
}[] {
return db.all(sql`
SELECT id, kind, ref_type, ref_id, title, message, created_at
FROM notification_log
ORDER BY created_at DESC, id DESC
LIMIT ${limit}
`);
}
+65
View File
@@ -77,6 +77,7 @@ export const domains = sqliteTable("domains", {
cf_zone_id: text("cf_zone_id").notNull(),
status: text("status").notNull().default("active"),
cert_monitoring: text("cert_monitoring").notNull().default("auto"),
environment: text("environment"),
last_synced_at: text("last_synced_at"),
created_at: text("created_at")
.notNull()
@@ -287,6 +288,66 @@ export const appSettings = sqliteTable("app_settings", {
.default(sql`datetime('now')`),
});
export const domainTags = sqliteTable(
"domain_tags",
{
domain_id: integer("domain_id")
.notNull()
.references(() => domains.id, { onDelete: "cascade" }),
tag: text("tag").notNull(),
},
(t) => [primaryKey({ columns: [t.domain_id, t.tag] })],
);
export const domainMonitors = sqliteTable("domain_monitors", {
id: integer("id").primaryKey({ autoIncrement: true }),
domain_id: integer("domain_id")
.notNull()
.references(() => domains.id, { onDelete: "cascade" }),
hostname: text("hostname").notNull(),
type: text("type").notNull().default("http"),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
interval_sec: integer("interval_sec").notNull().default(60),
timeout_ms: integer("timeout_ms").notNull().default(5000),
path: text("path"),
expected_status: integer("expected_status"),
last_status: text("last_status").notNull().default("unknown"),
last_latency_ms: integer("last_latency_ms"),
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')`),
});
export const domainMonitorResults = sqliteTable("domain_monitor_results", {
id: integer("id").primaryKey({ autoIncrement: true }),
monitor_id: integer("monitor_id")
.notNull()
.references(() => domainMonitors.id, { onDelete: "cascade" }),
status: text("status").notNull(),
latency_ms: integer("latency_ms"),
error: text("error"),
checked_at: text("checked_at")
.notNull()
.default(sql`datetime('now')`),
});
export const notificationLog = sqliteTable("notification_log", {
id: integer("id").primaryKey({ autoIncrement: true }),
kind: text("kind").notNull(),
ref_type: text("ref_type").notNull(),
ref_id: integer("ref_id"),
title: text("title").notNull(),
message: text("message").notNull(),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
});
export const schema = {
groups,
services,
@@ -303,4 +364,8 @@ export const schema = {
syncJobs,
ipHealthStatus,
appSettings,
domainTags,
domainMonitors,
domainMonitorResults,
notificationLog,
};
+156 -14
View File
@@ -152,7 +152,7 @@ interface JwtClaims {
exp: number;
}
type LbMode = "round_robin" | "failover" | "weighted";
type HealthCheckType = "tcp" | "http";
type HealthCheckType = "tcp" | "http" | "ping" | "dns";
type IpHealthState = "up" | "down" | "degraded" | "unknown";
type HealthCheckScope = "binding" | "group";
interface IpHealthStatus {
@@ -221,7 +221,21 @@ declare const lbModeSchema: z.ZodEnum<{
declare const healthCheckTypeSchema: z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>;
declare const domainEnvironmentSchema: z.ZodEnum<{
prod: "prod";
staging: "staging";
dev: "dev";
}>;
type DomainEnvironment = z.infer<typeof domainEnvironmentSchema>;
declare const domainMonitorTypeSchema: z.ZodEnum<{
http: "http";
ping: "ping";
dns: "dns";
}>;
type DomainMonitorType = z.infer<typeof domainMonitorTypeSchema>;
declare const ipHealthStateSchema: z.ZodEnum<{
unknown: "unknown";
up: "up";
@@ -294,6 +308,8 @@ declare const serviceGroupSchema: z.ZodObject<{
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
@@ -340,6 +356,8 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
@@ -360,7 +378,7 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
fqdn: string;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_type: "tcp" | "http" | "ping" | "dns";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
@@ -377,7 +395,7 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
record_type: "A" | "CNAME";
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_type: "tcp" | "http" | "ping" | "dns";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
@@ -427,6 +445,8 @@ declare const serviceViewSchema: z.ZodObject<{
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
@@ -447,7 +467,7 @@ declare const serviceViewSchema: z.ZodObject<{
fqdn: string;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_type: "tcp" | "http" | "ping" | "dns";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
@@ -464,7 +484,7 @@ declare const serviceViewSchema: z.ZodObject<{
record_type: "A" | "CNAME";
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_type: "tcp" | "http" | "ping" | "dns";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
@@ -500,6 +520,8 @@ declare const serviceGroupViewSchema: z.ZodObject<{
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
@@ -545,6 +567,8 @@ declare const serviceGroupViewSchema: z.ZodObject<{
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
@@ -565,7 +589,7 @@ declare const serviceGroupViewSchema: z.ZodObject<{
fqdn: string;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_type: "tcp" | "http" | "ping" | "dns";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
@@ -582,7 +606,7 @@ declare const serviceGroupViewSchema: z.ZodObject<{
record_type: "A" | "CNAME";
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_type: "tcp" | "http" | "ping" | "dns";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
@@ -620,6 +644,8 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
@@ -665,6 +691,8 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
@@ -685,7 +713,7 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
fqdn: string;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_type: "tcp" | "http" | "ping" | "dns";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
@@ -702,7 +730,7 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
record_type: "A" | "CNAME";
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_type: "tcp" | "http" | "ping" | "dns";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
@@ -754,6 +782,8 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
@@ -774,7 +804,7 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
fqdn: string;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_type: "tcp" | "http" | "ping" | "dns";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
@@ -791,7 +821,7 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
record_type: "A" | "CNAME";
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_type: "tcp" | "http" | "ping" | "dns";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
@@ -817,6 +847,11 @@ declare const domainSchema: z.ZodObject<{
required: "required";
skipped: "skipped";
}>>;
environment: z.ZodDefault<z.ZodOptional<z.ZodNullable<z.ZodEnum<{
prod: "prod";
staging: "staging";
dev: "dev";
}>>>>;
last_synced_at: z.ZodNullable<z.ZodString>;
created_at: z.ZodString;
updated_at: z.ZodString;
@@ -832,11 +867,24 @@ declare const domainListItemSchema: z.ZodObject<{
required: "required";
skipped: "skipped";
}>>;
environment: z.ZodDefault<z.ZodOptional<z.ZodNullable<z.ZodEnum<{
prod: "prod";
staging: "staging";
dev: "dev";
}>>>>;
last_synced_at: z.ZodNullable<z.ZodString>;
created_at: z.ZodString;
updated_at: z.ZodString;
group_name: z.ZodNullable<z.ZodString>;
service_count: z.ZodNumber;
health_status: z.ZodDefault<z.ZodEnum<{
unknown: "unknown";
up: "up";
down: "down";
degraded: "degraded";
}>>;
health_latency_ms: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
}, z.core.$strip>;
declare const serviceBindingSchema: z.ZodPipe<z.ZodObject<{
id: z.ZodNumber;
@@ -862,6 +910,8 @@ declare const serviceBindingSchema: z.ZodPipe<z.ZodObject<{
health_check_type: z.ZodCatch<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodNullable<z.ZodNumber>;
health_check_path: z.ZodNullable<z.ZodString>;
@@ -888,7 +938,7 @@ declare const serviceBindingSchema: z.ZodPipe<z.ZodObject<{
target_ip: string | null;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_type: "tcp" | "http" | "ping" | "dns";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
@@ -911,7 +961,7 @@ declare const serviceBindingSchema: z.ZodPipe<z.ZodObject<{
target_ip: string | null;
lb_mode: "round_robin" | "failover" | "weighted";
health_check_enabled: boolean;
health_check_type: "tcp" | "http";
health_check_type: "tcp" | "http" | "ping" | "dns";
health_check_port: number | null;
health_check_path: string | null;
health_check_expected_status: number | null;
@@ -973,6 +1023,8 @@ declare const healthCheckConfigSchema: z.ZodObject<{
health_check_type: z.ZodOptional<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -997,6 +1049,8 @@ declare const createServiceWithConfigSchema: z.ZodObject<{
health_check_type: z.ZodOptional<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -1081,7 +1135,89 @@ declare const updateDomainSchema: z.ZodObject<{
required: "required";
skipped: "skipped";
}>>;
environment: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
prod: "prod";
staging: "staging";
dev: "dev";
}>>>;
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
}, z.core.$strip>;
declare const bulkUpdateDomainsSchema: z.ZodObject<{
ids: z.ZodArray<z.ZodNumber>;
group_id: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
environment: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
prod: "prod";
staging: "staging";
dev: "dev";
}>>>;
tags_add: z.ZodOptional<z.ZodArray<z.ZodString>>;
}, z.core.$strip>;
type BulkUpdateDomainsInput = z.infer<typeof bulkUpdateDomainsSchema>;
declare const domainMonitorSchema: z.ZodObject<{
id: z.ZodNumber;
domain_id: z.ZodNumber;
hostname: z.ZodString;
type: z.ZodEnum<{
http: "http";
ping: "ping";
dns: "dns";
}>;
enabled: z.ZodCoercedBoolean<unknown>;
interval_sec: z.ZodNumber;
timeout_ms: z.ZodNumber;
path: z.ZodNullable<z.ZodString>;
expected_status: z.ZodNullable<z.ZodNumber>;
last_status: z.ZodDefault<z.ZodEnum<{
unknown: "unknown";
up: "up";
down: "down";
degraded: "degraded";
}>>;
last_latency_ms: z.ZodNullable<z.ZodNumber>;
last_checked_at: z.ZodNullable<z.ZodString>;
last_error: z.ZodNullable<z.ZodString>;
created_at: z.ZodString;
updated_at: z.ZodString;
}, z.core.$strip>;
type DomainMonitor = z.infer<typeof domainMonitorSchema>;
declare const createDomainMonitorSchema: z.ZodObject<{
hostname: z.ZodString;
type: z.ZodEnum<{
http: "http";
ping: "ping";
dns: "dns";
}>;
enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
interval_sec: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
timeout_ms: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
expected_status: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
}, z.core.$strip>;
type CreateDomainMonitorInput = z.infer<typeof createDomainMonitorSchema>;
declare const domainMonitorResultSchema: z.ZodObject<{
id: z.ZodNumber;
monitor_id: z.ZodNumber;
status: z.ZodEnum<{
unknown: "unknown";
up: "up";
down: "down";
degraded: "degraded";
}>;
latency_ms: z.ZodNullable<z.ZodNumber>;
error: z.ZodNullable<z.ZodString>;
checked_at: z.ZodString;
}, z.core.$strip>;
type DomainMonitorResult = z.infer<typeof domainMonitorResultSchema>;
declare const notificationLogSchema: z.ZodObject<{
id: z.ZodNumber;
kind: z.ZodString;
ref_type: z.ZodString;
ref_id: z.ZodNullable<z.ZodNumber>;
title: z.ZodString;
message: z.ZodString;
created_at: z.ZodString;
}, z.core.$strip>;
type NotificationLog = z.infer<typeof notificationLogSchema>;
type CreateSubdomainInput = z.infer<typeof createSubdomainSchema>;
type UpdateSubdomainInput = z.infer<typeof updateSubdomainSchema>;
type UpdateDomainInput = z.infer<typeof updateDomainSchema>;
@@ -1100,6 +1236,8 @@ declare const updateServiceConfigSchema: z.ZodObject<{
health_check_type: z.ZodOptional<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -1124,6 +1262,8 @@ declare const createServiceGroupSchema: z.ZodObject<{
health_check_type: z.ZodOptional<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -1151,6 +1291,8 @@ declare const updateServiceGroupSchema: z.ZodObject<{
health_check_type: z.ZodOptional<z.ZodEnum<{
tcp: "tcp";
http: "http";
ping: "ping";
dns: "dns";
}>>;
health_check_port: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -1300,4 +1442,4 @@ declare const vpsTrackerEventSchema: z.ZodObject<{
}, z.core.$strip>;
type VpsTrackerEvent = z.infer<typeof vpsTrackerEventSchema>;
export { type AppSettingsPatch, type AppSwitcherConfig, type AppSwitcherEntry, 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 CfdmBindingSyncItem, 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, type VpsTrackerEvent, appSettingsPatchSchema, appSwitcherConfigSchema, appSwitcherEntrySchema, appSwitcherIconSchema, bindingToFqdn, certMonitoringSchema, certStatusFromExpiry, certificateSchema, cfdmBindingSyncItemSchema, cfdmSyncBindingsBodySchema, 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, vpsTrackerEventSchema };
export { type AppSettingsPatch, type AppSwitcherConfig, type AppSwitcherEntry, type BulkUpdateDomainsInput, CERT_ERROR, CERT_EXPIRED, CERT_MONITORING_VALUES, CERT_MONITOR_AUTO, CERT_MONITOR_REQUIRED, CERT_MONITOR_SKIPPED, CERT_OK, CERT_UNKNOWN, CERT_WARNING, type CertMonitoring, type Certificate, type CfDnsRecord, type CfZone, type CfdmBindingSyncItem, type CreateDnsRecordInput, type CreateDnsRecordPayload, type CreateDomainInput, type CreateDomainMonitorInput, type CreateGroupInput, type CreateServiceBindingInput, type CreateServiceGroupInput, type CreateServiceInput, type CreateServiceWithConfigInput, type CreateSubdomainInput, type DnsRecord, type Domain, type DomainEnvironment, type DomainListItem, type DomainMonitor, type DomainMonitorResult, type DomainMonitorType, type Group, type GroupWithStats, type HealthCheckConfig, type HealthCheckScope, type HealthCheckTarget, type HealthCheckType, type HealthStatusQuery, type IpHealthState, type IpHealthStatus, type JwtClaims, type LbMode, type LoginInput, type LoginRequest, type LoginResponse, type NotificationLog, 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, type VpsTrackerEvent, appSettingsPatchSchema, appSwitcherConfigSchema, appSwitcherEntrySchema, appSwitcherIconSchema, bindingToFqdn, bulkUpdateDomainsSchema, certMonitoringSchema, certStatusFromExpiry, certificateSchema, cfdmBindingSyncItemSchema, cfdmSyncBindingsBodySchema, createDnsRecordSchema, createDomainMonitorSchema, createDomainSchema, createGroupSchema, createServiceBindingSchema, createServiceGroupSchema, createServiceSchema, createServiceWithConfigSchema, createSubdomainSchema, dnsNameToSubdomainLabel, dnsRecordNamesMatch, dnsRecordSchema, domainEnvironmentSchema, domainListItemSchema, domainMonitorResultSchema, domainMonitorSchema, domainMonitorTypeSchema, domainSchema, fqdnToDisplay, groupSchema, groupWithStatsSchema, healthCheckConfigSchema, healthCheckScopeSchema, healthCheckTypeSchema, healthStatusQuerySchema, ipHealthStateSchema, ipHealthStatusSchema, isValidIpv4, lbModeSchema, loginSchema, normalizeDnsRecordName, notificationLogSchema, parseFqdn, reorderServicesSchema, serviceBindingSchema, serviceDomainBindingSchema, serviceGroupSchema, serviceGroupTypeSchema, serviceGroupViewSchema, serviceGroupsResponseSchema, serviceSchema, serviceViewSchema, shouldMonitorService, subdomainLabelToFqdn, subdomainSchema, toggleEnabledSchema, updateDomainGroupSchema, updateDomainSchema, updateServiceConfigSchema, updateServiceGroupSchema, updateSubdomainSchema, validateDnsRecord, vpsTrackerEventSchema };
+67 -3
View File
@@ -161,7 +161,9 @@ function bindingToFqdn(binding) {
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 healthCheckTypeSchema = z.enum(["tcp", "http", "ping", "dns"]);
var domainEnvironmentSchema = z.enum(["prod", "staging", "dev"]);
var domainMonitorTypeSchema = z.enum(["http", "ping", "dns"]);
var ipHealthStateSchema = z.enum(["up", "down", "degraded", "unknown"]);
var healthCheckScopeSchema = z.enum(["binding", "group"]);
var ipHealthStatusSchema = z.object({
@@ -271,13 +273,17 @@ var domainSchema = z.object({
cf_zone_id: z.string(),
status: z.string(),
cert_monitoring: certMonitoringSchema.default("auto"),
environment: domainEnvironmentSchema.nullable().optional().default(null),
last_synced_at: z.string().nullable(),
created_at: z.string(),
updated_at: z.string()
});
var domainListItemSchema = domainSchema.extend({
group_name: z.string().nullable(),
service_count: z.number()
service_count: z.number(),
health_status: ipHealthStateSchema.default("unknown"),
health_latency_ms: z.number().nullable().default(null),
tags: z.array(z.string()).default([])
});
var serviceBindingSchema = z.object({
id: z.number(),
@@ -440,7 +446,58 @@ var updateSubdomainSchema = z.object({
var updateDomainSchema = z.object({
group_id: z.number().nullable().optional(),
status: z.string().optional(),
cert_monitoring: certMonitoringSchema.optional()
cert_monitoring: certMonitoringSchema.optional(),
environment: domainEnvironmentSchema.nullable().optional(),
tags: z.array(z.string().min(1).max(64)).max(20).optional()
});
var bulkUpdateDomainsSchema = z.object({
ids: z.array(z.number().int().positive()).min(1),
group_id: z.number().nullable().optional(),
environment: domainEnvironmentSchema.nullable().optional(),
tags_add: z.array(z.string().min(1).max(64)).max(20).optional()
});
var domainMonitorSchema = z.object({
id: z.number(),
domain_id: z.number(),
hostname: z.string(),
type: domainMonitorTypeSchema,
enabled: z.coerce.boolean(),
interval_sec: z.number(),
timeout_ms: z.number(),
path: z.string().nullable(),
expected_status: z.number().nullable(),
last_status: ipHealthStateSchema.default("unknown"),
last_latency_ms: z.number().nullable(),
last_checked_at: z.string().nullable(),
last_error: z.string().nullable(),
created_at: z.string(),
updated_at: z.string()
});
var createDomainMonitorSchema = z.object({
hostname: z.string().min(1),
type: domainMonitorTypeSchema,
enabled: z.boolean().optional().default(true),
interval_sec: z.number().int().min(10).max(3600).optional().default(60),
timeout_ms: z.number().int().min(500).max(3e4).optional().default(5e3),
path: z.string().nullable().optional(),
expected_status: z.number().int().min(100).max(599).nullable().optional()
});
var domainMonitorResultSchema = z.object({
id: z.number(),
monitor_id: z.number(),
status: ipHealthStateSchema,
latency_ms: z.number().nullable(),
error: z.string().nullable(),
checked_at: z.string()
});
var notificationLogSchema = z.object({
id: z.number(),
kind: z.string(),
ref_type: z.string(),
ref_id: z.number().nullable(),
title: z.string(),
message: z.string(),
created_at: z.string()
});
var updateServiceConfigSchema = z.object({
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435").optional(),
@@ -555,12 +612,14 @@ export {
appSwitcherEntrySchema,
appSwitcherIconSchema,
bindingToFqdn,
bulkUpdateDomainsSchema,
certMonitoringSchema,
certStatusFromExpiry,
certificateSchema,
cfdmBindingSyncItemSchema,
cfdmSyncBindingsBodySchema,
createDnsRecordSchema,
createDomainMonitorSchema,
createDomainSchema,
createGroupSchema,
createServiceBindingSchema,
@@ -571,7 +630,11 @@ export {
dnsNameToSubdomainLabel,
dnsRecordNamesMatch,
dnsRecordSchema,
domainEnvironmentSchema,
domainListItemSchema,
domainMonitorResultSchema,
domainMonitorSchema,
domainMonitorTypeSchema,
domainSchema,
fqdnToDisplay,
groupSchema,
@@ -586,6 +649,7 @@ export {
lbModeSchema,
loginSchema,
normalizeDnsRecordName,
notificationLogSchema,
parseFqdn,
reorderServicesSchema,
serviceBindingSchema,
+77 -1
View File
@@ -7,9 +7,15 @@ 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 const healthCheckTypeSchema = z.enum(['tcp', 'http', 'ping', 'dns'])
export type HealthCheckType = z.infer<typeof healthCheckTypeSchema>
export const domainEnvironmentSchema = z.enum(['prod', 'staging', 'dev'])
export type DomainEnvironment = z.infer<typeof domainEnvironmentSchema>
export const domainMonitorTypeSchema = z.enum(['http', 'ping', 'dns'])
export type DomainMonitorType = z.infer<typeof domainMonitorTypeSchema>
export const ipHealthStateSchema = z.enum(['up', 'down', 'degraded', 'unknown'])
export type IpHealthState = z.infer<typeof ipHealthStateSchema>
@@ -144,6 +150,7 @@ export const domainSchema = z.object({
cf_zone_id: z.string(),
status: z.string(),
cert_monitoring: certMonitoringSchema.default('auto'),
environment: domainEnvironmentSchema.nullable().optional().default(null),
last_synced_at: z.string().nullable(),
created_at: z.string(),
updated_at: z.string(),
@@ -152,6 +159,9 @@ export const domainSchema = z.object({
export const domainListItemSchema = domainSchema.extend({
group_name: z.string().nullable(),
service_count: z.number(),
health_status: ipHealthStateSchema.default('unknown'),
health_latency_ms: z.number().nullable().default(null),
tags: z.array(z.string()).default([]),
})
export const serviceBindingSchema = z
@@ -361,8 +371,74 @@ export const updateDomainSchema = z.object({
group_id: z.number().nullable().optional(),
status: z.string().optional(),
cert_monitoring: certMonitoringSchema.optional(),
environment: domainEnvironmentSchema.nullable().optional(),
tags: z.array(z.string().min(1).max(64)).max(20).optional(),
})
export const bulkUpdateDomainsSchema = z.object({
ids: z.array(z.number().int().positive()).min(1),
group_id: z.number().nullable().optional(),
environment: domainEnvironmentSchema.nullable().optional(),
tags_add: z.array(z.string().min(1).max(64)).max(20).optional(),
})
export type BulkUpdateDomainsInput = z.infer<typeof bulkUpdateDomainsSchema>
export const domainMonitorSchema = z.object({
id: z.number(),
domain_id: z.number(),
hostname: z.string(),
type: domainMonitorTypeSchema,
enabled: z.coerce.boolean(),
interval_sec: z.number(),
timeout_ms: z.number(),
path: z.string().nullable(),
expected_status: z.number().nullable(),
last_status: ipHealthStateSchema.default('unknown'),
last_latency_ms: z.number().nullable(),
last_checked_at: z.string().nullable(),
last_error: z.string().nullable(),
created_at: z.string(),
updated_at: z.string(),
})
export type DomainMonitor = z.infer<typeof domainMonitorSchema>
export const createDomainMonitorSchema = z.object({
hostname: z.string().min(1),
type: domainMonitorTypeSchema,
enabled: z.boolean().optional().default(true),
interval_sec: z.number().int().min(10).max(3600).optional().default(60),
timeout_ms: z.number().int().min(500).max(30000).optional().default(5000),
path: z.string().nullable().optional(),
expected_status: z.number().int().min(100).max(599).nullable().optional(),
})
export type CreateDomainMonitorInput = z.infer<typeof createDomainMonitorSchema>
export const domainMonitorResultSchema = z.object({
id: z.number(),
monitor_id: z.number(),
status: ipHealthStateSchema,
latency_ms: z.number().nullable(),
error: z.string().nullable(),
checked_at: z.string(),
})
export type DomainMonitorResult = z.infer<typeof domainMonitorResultSchema>
export const notificationLogSchema = z.object({
id: z.number(),
kind: z.string(),
ref_type: z.string(),
ref_id: z.number().nullable(),
title: z.string(),
message: z.string(),
created_at: z.string(),
})
export type NotificationLog = z.infer<typeof notificationLogSchema>
export type CreateSubdomainInput = z.infer<typeof createSubdomainSchema>
export type UpdateSubdomainInput = z.infer<typeof updateSubdomainSchema>
export type UpdateDomainInput = z.infer<typeof updateDomainSchema>
+9 -1
View File
@@ -55,6 +55,7 @@ export interface Domain {
cf_zone_id: string;
status: string;
cert_monitoring: string;
environment: DomainEnvironment | null;
last_synced_at: string | null;
created_at: string;
updated_at: string;
@@ -193,6 +194,9 @@ export interface GroupWithStats extends Group {
export interface DomainListItem extends Domain {
group_name: string | null;
service_count: number;
health_status: IpHealthState;
health_latency_ms: number | null;
tags: string[];
}
export interface SyncJob {
@@ -246,7 +250,11 @@ export interface JwtClaims {
export type LbMode = "round_robin" | "failover" | "weighted";
export type HealthCheckType = "tcp" | "http";
export type HealthCheckType = "tcp" | "http" | "ping" | "dns";
export type DomainEnvironment = "prod" | "staging" | "dev";
export type DomainMonitorType = "http" | "ping" | "dns";
export type IpHealthState = "up" | "down" | "degraded" | "unknown";