feat(api, web): integrate health status management for services and domains
Build, Test, and Push CFDM Docker Image / test (push) Successful in 10m17s
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
Build, Test, and Push CFDM Docker Image / test (push) Successful in 10m17s
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
- Added health status and latency fields to service and domain schemas, enhancing monitoring capabilities. - Implemented health status aggregation for services in the API, allowing for improved health checks and reporting. - Updated web components to display health status using HealthCheckBadge, improving user visibility of service health. - Refactored service and domain management components to incorporate health status in various views, enhancing overall functionality. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
64585ccd47
commit
1f710bbe8b
48 files changed
+4258
-246
No files matched your search
Vendored
+15
-2
@@ -1,7 +1,7 @@
|
||||
import * as drizzle_orm_sqlite_core from 'drizzle-orm/sqlite-core';
|
||||
import Database from 'better-sqlite3';
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||
import { AppSwitcherConfig, LbMode, HealthCheckType, ServiceBinding, Domain, Group, Service, ServiceGroup, Subdomain, HealthCheckScope, DnsRecord, ServiceBindingView, Certificate, GroupWithStats, IpHealthStatus, SyncJob, DomainListItem, HealthCheckTarget } from '@cfdm/shared';
|
||||
import { AppSwitcherConfig, LbMode, HealthCheckType, IpHealthState, HealthCheckScope, ServiceBinding, Domain, Group, Service, ServiceGroup, Subdomain, DnsRecord, ServiceBindingView, Certificate, GroupWithStats, IpHealthStatus, SyncJob, DomainListItem, HealthCheckTarget } from '@cfdm/shared';
|
||||
|
||||
declare const groups: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
||||
name: "groups";
|
||||
@@ -6126,6 +6126,15 @@ declare function createSyncJob(db: Db, id: string, domainId: number | null): voi
|
||||
declare function getSyncJob(db: Db, id: string): SyncJob;
|
||||
declare function finishSyncJob(db: Db, id: string, status: string, message: string | null): void;
|
||||
declare function listIpHealthStatus(db: Db, scope: HealthCheckScope, refId: number): IpHealthStatus[];
|
||||
type HealthAggregate = {
|
||||
health_status: IpHealthState;
|
||||
health_latency_ms: number | null;
|
||||
};
|
||||
/** Worst status across ip_health_status rows for each ref_id in a scope. */
|
||||
declare function aggregateIpHealthByRefs(db: Db, scope: HealthCheckScope, refIds: number[]): Map<number, HealthAggregate>;
|
||||
/** Worst binding-scope health rolled up per service_id. */
|
||||
declare function aggregateIpHealthByServiceIds(db: Db, serviceIds: number[]): Map<number, HealthAggregate>;
|
||||
declare function mergeHealthAggregates(parts: Array<HealthAggregate | undefined | null>): HealthAggregate;
|
||||
declare function getIpHealthStatusRow(db: Db, scope: HealthCheckScope, refId: number, ip: string): IpHealthStatus | null;
|
||||
declare function upsertIpHealthStatus(db: Db, scope: HealthCheckScope, refId: number, ip: string, status: string, latencyMs: number | null, consecutiveFailures: number, lastError: string | null): void;
|
||||
declare function deleteIpHealthStatusForRef(db: Db, scope: HealthCheckScope, refId: number): void;
|
||||
@@ -6198,9 +6207,12 @@ type repos_BindingIpMeta = BindingIpMeta;
|
||||
type repos_BindingLbPatch = BindingLbPatch;
|
||||
type repos_DnsListFilter = DnsListFilter;
|
||||
type repos_DomainMonitorRow = DomainMonitorRow;
|
||||
type repos_HealthAggregate = HealthAggregate;
|
||||
type repos_ServiceGroupLbPatch = ServiceGroupLbPatch;
|
||||
type repos_UpdateSubdomainPatch = UpdateSubdomainPatch;
|
||||
declare const repos_addDomainTags: typeof addDomainTags;
|
||||
declare const repos_aggregateIpHealthByRefs: typeof aggregateIpHealthByRefs;
|
||||
declare const repos_aggregateIpHealthByServiceIds: typeof aggregateIpHealthByServiceIds;
|
||||
declare const repos_bindingsToRemove: typeof bindingsToRemove;
|
||||
declare const repos_countCertificatesByStatus: typeof countCertificatesByStatus;
|
||||
declare const repos_createDomain: typeof createDomain;
|
||||
@@ -6275,6 +6287,7 @@ declare const repos_listServicesByGroup: typeof listServicesByGroup;
|
||||
declare const repos_listSubdomainsByDomain: typeof listSubdomainsByDomain;
|
||||
declare const repos_listUngroupedServices: typeof listUngroupedServices;
|
||||
declare const repos_markDnsPendingDelete: typeof markDnsPendingDelete;
|
||||
declare const repos_mergeHealthAggregates: typeof mergeHealthAggregates;
|
||||
declare const repos_reorderServices: typeof reorderServices;
|
||||
declare const repos_replaceBindingIps: typeof replaceBindingIps;
|
||||
declare const repos_replaceBindingIpsWithMeta: typeof replaceBindingIpsWithMeta;
|
||||
@@ -6303,7 +6316,7 @@ declare const repos_upsertCertificateCheck: typeof upsertCertificateCheck;
|
||||
declare const repos_upsertIpHealthStatus: typeof upsertIpHealthStatus;
|
||||
declare const repos_upsertSubdomain: typeof upsertSubdomain;
|
||||
declare namespace repos {
|
||||
export { type repos_BindingIpMeta as BindingIpMeta, type repos_BindingLbPatch as BindingLbPatch, type repos_DnsListFilter as DnsListFilter, type repos_DomainMonitorRow as DomainMonitorRow, type repos_ServiceGroupLbPatch as ServiceGroupLbPatch, type repos_UpdateSubdomainPatch as UpdateSubdomainPatch, repos_addDomainTags as addDomainTags, repos_bindingsToRemove as bindingsToRemove, repos_countCertificatesByStatus as countCertificatesByStatus, repos_createDomain as createDomain, repos_createDomainMonitor as createDomainMonitor, repos_createGroup as createGroup, repos_createService as createService, repos_createServiceGroup as createServiceGroup, repos_createSubdomain as createSubdomain, repos_createSyncJob as createSyncJob, repos_deleteBinding as deleteBinding, repos_deleteBindingsExcept as deleteBindingsExcept, repos_deleteCertificatesNotIn as deleteCertificatesNotIn, repos_deleteDnsRecord as deleteDnsRecord, repos_deleteDomain as deleteDomain, repos_deleteDomainMonitor as deleteDomainMonitor, repos_deleteGroup as deleteGroup, repos_deleteIpHealthStatusForIp as deleteIpHealthStatusForIp, repos_deleteIpHealthStatusForRef as deleteIpHealthStatusForRef, repos_deleteService as deleteService, repos_deleteServiceGroup as deleteServiceGroup, repos_deleteSubdomain as deleteSubdomain, repos_findBinding as findBinding, repos_findDnsByCfId as findDnsByCfId, repos_findDomainByZoneName as findDomainByZoneName, repos_findSubdomainByDomainAndName as findSubdomainByDomainAndName, repos_finishSyncJob as finishSyncJob, repos_getBinding as getBinding, repos_getBindingView as getBindingView, repos_getCertificate as getCertificate, repos_getDnsRecord as getDnsRecord, repos_getDomain as getDomain, repos_getDomainMonitor as getDomainMonitor, repos_getGroup as getGroup, repos_getGroupWithStats as getGroupWithStats, repos_getIpHealthStatusRow as getIpHealthStatusRow, repos_getService as getService, repos_getServiceGroup as getServiceGroup, repos_getSubdomain as getSubdomain, repos_getSyncJob as getSyncJob, repos_insertBinding as insertBinding, repos_insertDnsRecord as insertDnsRecord, repos_insertNotificationLog as insertNotificationLog, repos_linkBindingRecord as linkBindingRecord, repos_linkGroupDnsRecord as linkGroupDnsRecord, repos_listAllBindings as listAllBindings, repos_listAllDomains as listAllDomains, repos_listAllSubdomains as listAllSubdomains, repos_listBindingIps as listBindingIps, repos_listBindingIpsWithMeta as listBindingIpsWithMeta, repos_listBindingsByDomain as listBindingsByDomain, repos_listBindingsByService as listBindingsByService, repos_listCertificates as listCertificates, repos_listDnsByDomain as listDnsByDomain, repos_listDnsRecords as listDnsRecords, repos_listDomainMonitorResults as listDomainMonitorResults, repos_listDomainMonitorResultsForDomain as listDomainMonitorResultsForDomain, repos_listDomainMonitors as listDomainMonitors, repos_listDomainTags as listDomainTags, repos_listDomains as listDomains, repos_listDomainsEnriched as listDomainsEnriched, repos_listEnabledDomainMonitors as listEnabledDomainMonitors, repos_listGroupDnsRecords as listGroupDnsRecords, repos_listGroups as listGroups, repos_listHealthCheckTargets as listHealthCheckTargets, repos_listIpHealthStatus as listIpHealthStatus, repos_listNotificationLog as listNotificationLog, repos_listRecordsForBinding as listRecordsForBinding, repos_listServiceGroups as listServiceGroups, repos_listServiceIps as listServiceIps, repos_listServices as listServices, repos_listServicesByGroup as listServicesByGroup, repos_listSubdomainsByDomain as listSubdomainsByDomain, repos_listUngroupedServices as listUngroupedServices, repos_markDnsPendingDelete as markDnsPendingDelete, repos_reorderServices as reorderServices, repos_replaceBindingIps as replaceBindingIps, repos_replaceBindingIpsWithMeta as replaceBindingIpsWithMeta, repos_replaceServiceIps as replaceServiceIps, repos_setBindingCnameTarget as setBindingCnameTarget, repos_setBindingDnsRecordId as setBindingDnsRecordId, repos_setDnsSyncStatus as setDnsSyncStatus, repos_setDomainLastSynced as setDomainLastSynced, repos_setDomainTags as setDomainTags, repos_setServiceEnabled as setServiceEnabled, repos_setServiceGroup as setServiceGroup, repos_setServiceGroupEnabled as setServiceGroupEnabled, repos_setServiceLb as setServiceLb, repos_unlinkBindingRecord as unlinkBindingRecord, repos_unlinkGroupDnsRecord as unlinkGroupDnsRecord, repos_updateBindingFields as updateBindingFields, repos_updateBindingLbConfig as updateBindingLbConfig, repos_updateDnsFields as updateDnsFields, repos_updateDomain as updateDomain, repos_updateDomainMonitorResult as updateDomainMonitorResult, repos_updateGroup as updateGroup, repos_updateService as updateService, repos_updateServiceGroup as updateServiceGroup, repos_updateSubdomain as updateSubdomain, repos_upsertCertificateCheck as upsertCertificateCheck, repos_upsertIpHealthStatus as upsertIpHealthStatus, repos_upsertSubdomain as upsertSubdomain };
|
||||
export { type repos_BindingIpMeta as BindingIpMeta, type repos_BindingLbPatch as BindingLbPatch, type repos_DnsListFilter as DnsListFilter, type repos_DomainMonitorRow as DomainMonitorRow, type repos_HealthAggregate as HealthAggregate, type repos_ServiceGroupLbPatch as ServiceGroupLbPatch, type repos_UpdateSubdomainPatch as UpdateSubdomainPatch, repos_addDomainTags as addDomainTags, repos_aggregateIpHealthByRefs as aggregateIpHealthByRefs, repos_aggregateIpHealthByServiceIds as aggregateIpHealthByServiceIds, repos_bindingsToRemove as bindingsToRemove, repos_countCertificatesByStatus as countCertificatesByStatus, repos_createDomain as createDomain, repos_createDomainMonitor as createDomainMonitor, repos_createGroup as createGroup, repos_createService as createService, repos_createServiceGroup as createServiceGroup, repos_createSubdomain as createSubdomain, repos_createSyncJob as createSyncJob, repos_deleteBinding as deleteBinding, repos_deleteBindingsExcept as deleteBindingsExcept, repos_deleteCertificatesNotIn as deleteCertificatesNotIn, repos_deleteDnsRecord as deleteDnsRecord, repos_deleteDomain as deleteDomain, repos_deleteDomainMonitor as deleteDomainMonitor, repos_deleteGroup as deleteGroup, repos_deleteIpHealthStatusForIp as deleteIpHealthStatusForIp, repos_deleteIpHealthStatusForRef as deleteIpHealthStatusForRef, repos_deleteService as deleteService, repos_deleteServiceGroup as deleteServiceGroup, repos_deleteSubdomain as deleteSubdomain, repos_findBinding as findBinding, repos_findDnsByCfId as findDnsByCfId, repos_findDomainByZoneName as findDomainByZoneName, repos_findSubdomainByDomainAndName as findSubdomainByDomainAndName, repos_finishSyncJob as finishSyncJob, repos_getBinding as getBinding, repos_getBindingView as getBindingView, repos_getCertificate as getCertificate, repos_getDnsRecord as getDnsRecord, repos_getDomain as getDomain, repos_getDomainMonitor as getDomainMonitor, repos_getGroup as getGroup, repos_getGroupWithStats as getGroupWithStats, repos_getIpHealthStatusRow as getIpHealthStatusRow, repos_getService as getService, repos_getServiceGroup as getServiceGroup, repos_getSubdomain as getSubdomain, repos_getSyncJob as getSyncJob, repos_insertBinding as insertBinding, repos_insertDnsRecord as insertDnsRecord, repos_insertNotificationLog as insertNotificationLog, repos_linkBindingRecord as linkBindingRecord, repos_linkGroupDnsRecord as linkGroupDnsRecord, repos_listAllBindings as listAllBindings, repos_listAllDomains as listAllDomains, repos_listAllSubdomains as listAllSubdomains, repos_listBindingIps as listBindingIps, repos_listBindingIpsWithMeta as listBindingIpsWithMeta, repos_listBindingsByDomain as listBindingsByDomain, repos_listBindingsByService as listBindingsByService, repos_listCertificates as listCertificates, repos_listDnsByDomain as listDnsByDomain, repos_listDnsRecords as listDnsRecords, repos_listDomainMonitorResults as listDomainMonitorResults, repos_listDomainMonitorResultsForDomain as listDomainMonitorResultsForDomain, repos_listDomainMonitors as listDomainMonitors, repos_listDomainTags as listDomainTags, repos_listDomains as listDomains, repos_listDomainsEnriched as listDomainsEnriched, repos_listEnabledDomainMonitors as listEnabledDomainMonitors, repos_listGroupDnsRecords as listGroupDnsRecords, repos_listGroups as listGroups, repos_listHealthCheckTargets as listHealthCheckTargets, repos_listIpHealthStatus as listIpHealthStatus, repos_listNotificationLog as listNotificationLog, repos_listRecordsForBinding as listRecordsForBinding, repos_listServiceGroups as listServiceGroups, repos_listServiceIps as listServiceIps, repos_listServices as listServices, repos_listServicesByGroup as listServicesByGroup, repos_listSubdomainsByDomain as listSubdomainsByDomain, repos_listUngroupedServices as listUngroupedServices, repos_markDnsPendingDelete as markDnsPendingDelete, repos_mergeHealthAggregates as mergeHealthAggregates, repos_reorderServices as reorderServices, repos_replaceBindingIps as replaceBindingIps, repos_replaceBindingIpsWithMeta as replaceBindingIpsWithMeta, repos_replaceServiceIps as replaceServiceIps, repos_setBindingCnameTarget as setBindingCnameTarget, repos_setBindingDnsRecordId as setBindingDnsRecordId, repos_setDnsSyncStatus as setDnsSyncStatus, repos_setDomainLastSynced as setDomainLastSynced, repos_setDomainTags as setDomainTags, repos_setServiceEnabled as setServiceEnabled, repos_setServiceGroup as setServiceGroup, repos_setServiceGroupEnabled as setServiceGroupEnabled, repos_setServiceLb as setServiceLb, repos_unlinkBindingRecord as unlinkBindingRecord, repos_unlinkGroupDnsRecord as unlinkGroupDnsRecord, repos_updateBindingFields as updateBindingFields, repos_updateBindingLbConfig as updateBindingLbConfig, repos_updateDnsFields as updateDnsFields, repos_updateDomain as updateDomain, repos_updateDomainMonitorResult as updateDomainMonitorResult, repos_updateGroup as updateGroup, repos_updateService as updateService, repos_updateServiceGroup as updateServiLine truncated
|
||||
}
|
||||
|
||||
export { type AppSettingsDto, type AppSettingsPatch, ConflictError, type Db, type DnsListFilter, NotFoundError, type Sqlite, type UpdateSubdomainPatch, appSettings, certificates, createDb, createMemoryDb, dnsRecords, domainMonitorResults, domainMonitors, domainTags, domains, getAppSettings, getAppSettingsSecrets, getAppSwitcher, groups, healthCheck, ipHealthStatus, notificationLog, repos, resolveDatabasePath, runMigrations, schema, serviceBindingIps, serviceBindingRecords, serviceBindings, serviceGroupDnsRecords, serviceGroups, serviceIps, services, subdomains, syncJobs, touchVpsTrackerSync, updateAppSettings };
|
||||
Vendored
+106
@@ -415,6 +415,8 @@ function getAppSwitcher(db) {
|
||||
var repos_exports = {};
|
||||
__export(repos_exports, {
|
||||
addDomainTags: () => addDomainTags,
|
||||
aggregateIpHealthByRefs: () => aggregateIpHealthByRefs,
|
||||
aggregateIpHealthByServiceIds: () => aggregateIpHealthByServiceIds,
|
||||
bindingsToRemove: () => bindingsToRemove,
|
||||
countCertificatesByStatus: () => countCertificatesByStatus,
|
||||
createDomain: () => createDomain,
|
||||
@@ -489,6 +491,7 @@ __export(repos_exports, {
|
||||
listSubdomainsByDomain: () => listSubdomainsByDomain,
|
||||
listUngroupedServices: () => listUngroupedServices,
|
||||
markDnsPendingDelete: () => markDnsPendingDelete,
|
||||
mergeHealthAggregates: () => mergeHealthAggregates,
|
||||
reorderServices: () => reorderServices,
|
||||
replaceBindingIps: () => replaceBindingIps,
|
||||
replaceBindingIpsWithMeta: () => replaceBindingIpsWithMeta,
|
||||
@@ -1294,6 +1297,109 @@ function listIpHealthStatus(db, scope, refId) {
|
||||
WHERE scope = ${scope} AND ref_id = ${refId}
|
||||
`);
|
||||
}
|
||||
var UNKNOWN_HEALTH = {
|
||||
health_status: "unknown",
|
||||
health_latency_ms: null
|
||||
};
|
||||
function parseHealthAggregateRow(row) {
|
||||
const status = row.health_status;
|
||||
if (status === "up" || status === "down" || status === "degraded" || status === "unknown") {
|
||||
return {
|
||||
health_status: status,
|
||||
health_latency_ms: row.health_latency_ms ?? null
|
||||
};
|
||||
}
|
||||
return UNKNOWN_HEALTH;
|
||||
}
|
||||
var WORST_HEALTH_SQL = sql2.raw(`CASE
|
||||
WHEN MAX(CASE
|
||||
WHEN status = 'down' THEN 3
|
||||
WHEN status = 'degraded' THEN 2
|
||||
WHEN status = 'up' THEN 1
|
||||
ELSE 0
|
||||
END) = 3 THEN 'down'
|
||||
WHEN MAX(CASE
|
||||
WHEN status = 'down' THEN 3
|
||||
WHEN status = 'degraded' THEN 2
|
||||
WHEN status = 'up' THEN 1
|
||||
ELSE 0
|
||||
END) = 2 THEN 'degraded'
|
||||
WHEN MAX(CASE
|
||||
WHEN status = 'down' THEN 3
|
||||
WHEN status = 'degraded' THEN 2
|
||||
WHEN status = 'up' THEN 1
|
||||
ELSE 0
|
||||
END) = 1 THEN 'up'
|
||||
ELSE 'unknown'
|
||||
END`);
|
||||
function aggregateIpHealthByRefs(db, scope, refIds) {
|
||||
const result = /* @__PURE__ */ new Map();
|
||||
if (refIds.length === 0) return result;
|
||||
const idList = sql2.join(
|
||||
refIds.map((id) => sql2`${id}`),
|
||||
sql2`, `
|
||||
);
|
||||
const rows = db.all(sql2`
|
||||
SELECT ref_id,
|
||||
${WORST_HEALTH_SQL} AS health_status,
|
||||
MAX(latency_ms) AS health_latency_ms
|
||||
FROM ip_health_status
|
||||
WHERE scope = ${scope} AND ref_id IN (${idList})
|
||||
GROUP BY ref_id
|
||||
`);
|
||||
for (const row of rows) {
|
||||
result.set(row.ref_id, parseHealthAggregateRow(row));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function aggregateIpHealthByServiceIds(db, serviceIds) {
|
||||
const result = /* @__PURE__ */ new Map();
|
||||
if (serviceIds.length === 0) return result;
|
||||
const idList = sql2.join(
|
||||
serviceIds.map((id) => sql2`${id}`),
|
||||
sql2`, `
|
||||
);
|
||||
const rows = db.all(sql2`
|
||||
SELECT sb.service_id AS service_id,
|
||||
${WORST_HEALTH_SQL} AS health_status,
|
||||
MAX(ihs.latency_ms) AS health_latency_ms
|
||||
FROM ip_health_status ihs
|
||||
INNER JOIN service_bindings sb
|
||||
ON ihs.scope = 'binding' AND ihs.ref_id = sb.id
|
||||
WHERE sb.service_id IN (${idList})
|
||||
GROUP BY sb.service_id
|
||||
`);
|
||||
for (const row of rows) {
|
||||
result.set(row.service_id, parseHealthAggregateRow(row));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function mergeHealthAggregates(parts) {
|
||||
const rank = {
|
||||
unknown: 0,
|
||||
up: 1,
|
||||
degraded: 2,
|
||||
down: 3
|
||||
};
|
||||
let worst = UNKNOWN_HEALTH;
|
||||
let hasAny = false;
|
||||
for (const part of parts) {
|
||||
if (!part) continue;
|
||||
hasAny = true;
|
||||
if (rank[part.health_status] > rank[worst.health_status]) {
|
||||
worst = {
|
||||
health_status: part.health_status,
|
||||
health_latency_ms: part.health_latency_ms
|
||||
};
|
||||
} else if (part.health_status === worst.health_status && part.health_latency_ms != null && (worst.health_latency_ms == null || part.health_latency_ms > worst.health_latency_ms)) {
|
||||
worst = {
|
||||
health_status: worst.health_status,
|
||||
health_latency_ms: part.health_latency_ms
|
||||
};
|
||||
}
|
||||
}
|
||||
return hasAny ? worst : UNKNOWN_HEALTH;
|
||||
}
|
||||
function getIpHealthStatusRow(db, scope, refId, ip) {
|
||||
const rows = db.all(sql2`
|
||||
SELECT scope, ref_id, ip, status, latency_ms, consecutive_failures,
|
||||
|
||||
Reference in new issue
Block a user