From 73f53807686d1f1b1ae5381f9cbb3326c2407ac9 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 2 Nov 2026 18:39:40 +0700 Subject: [PATCH] =?UTF-8?q?fix(db):=20=D1=81=D0=BE=D0=B1=D1=80=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20listLatestLiveHealthByServiceIds=20=D0=B2=20dist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Очередь health-check видит собранную функцию и больше не падает. Co-authored-by: Cursor --- packages/db/dist/index.d.ts | 8 +++- packages/db/dist/index.js | 78 +++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/packages/db/dist/index.d.ts b/packages/db/dist/index.d.ts index 1ef337c..b3a6208 100644 --- a/packages/db/dist/index.d.ts +++ b/packages/db/dist/index.d.ts @@ -10074,6 +10074,11 @@ type ServiceIpHealthRow = { }; /** Per-IP binding-scope health, worst status if the same IP is on several bindings. */ declare function listIpHealthByServiceIds(db: Db, serviceIds: number[]): Map; +/** + * Latest probe per service+IP+provider from health_probe_log, then any-up per IP. + * Display overlay — hysteresis in ip_health_status is unchanged (DNS). + */ +declare function listLatestLiveHealthByServiceIds(db: Db, serviceIds: number[]): Map; declare function mergeHealthAggregates(parts: Array): 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, consecutiveSuccesses?: number, extras?: { @@ -10298,6 +10303,7 @@ declare const repos_listHealthChecks: typeof listHealthChecks; declare const repos_listHealthProbeLogForService: typeof listHealthProbeLogForService; declare const repos_listIpHealthByServiceIds: typeof listIpHealthByServiceIds; declare const repos_listIpHealthStatus: typeof listIpHealthStatus; +declare const repos_listLatestLiveHealthByServiceIds: typeof listLatestLiveHealthByServiceIds; declare const repos_listNodes: typeof listNodes; declare const repos_listNotificationLog: typeof listNotificationLog; declare const repos_listOriginIpsForFqdn: typeof listOriginIpsForFqdn; @@ -10345,7 +10351,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_FailoverLogAction as FailoverLogAction, type repos_FailoverLogRow as FailoverLogRow, type repos_HealthAggregate as HealthAggregate, type repos_ServiceGroupLbPatch as ServiceGroupLbPatch, type repos_ServiceIpHealthRow as ServiceIpHealthRow, type repos_ServiceIpRow as ServiceIpRow, type repos_UpdateSubdomainPatch as UpdateSubdomainPatch, repos_addDomainTags as addDomainTags, repos_aggregateGroupScopeHealthByIds as aggregateGroupScopeHealthByIds, repos_aggregateIpHealthByRefs as aggregateIpHealthByRefs, repos_aggregateIpHealthByServiceIds as aggregateIpHealthByServiceIds, repos_bindingsToRemove as bindingsToRemove, repos_bumpBindingVersion as bumpBindingVersion, repos_countCertificatesByStatus as countCertificatesByStatus, repos_createDomain as createDomain, repos_createDomainMonitor as createDomainMonitor, repos_createGroup as createGroup, repos_createHealthCheck as createHealthCheck, repos_createNode as createNode, 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_deleteHealthCheck as deleteHealthCheck, repos_deleteIpHealthStatusForIp as deleteIpHealthStatusForIp, repos_deleteIpHealthStatusForRef as deleteIpHealthStatusForRef, repos_deleteNode as deleteNode, repos_deleteService as deleteService, repos_deleteServiceGroup as deleteServiceGroup, repos_deleteSubdomain as deleteSubdomain, repos_ensureNode as ensureNode, repos_findBinding as findBinding, repos_findDnsByCfId as findDnsByCfId, repos_findDomainByZoneName as findDomainByZoneName, repos_findHealthCheckByCfId as findHealthCheckByCfId, repos_findNodeByAddress as findNodeByAddress, repos_findNodeByIp as findNodeByIp, 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_getHealthCheck as getHealthCheck, repos_getIpHealthStatusRow as getIpHealthStatusRow, repos_getNode as getNode, 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_insertFailoverLog as insertFailoverLog, repos_insertHealthProbeLog as insertHealthProbeLog, repos_insertNotificationLog as insertNotificationLog, repos_linkBindingRecord as linkBindingRecord, repos_linkGroupDnsRecord as linkGroupDnsRecord, repos_listAllBindings as listAllBindings, repos_listAllDomains as listAllDomains, repos_listAllNodes as listAllNodes, repos_listAllSubdomains as listAllSubdomains, repos_listBindingIps as listBindingIps, repos_listBindingIpsWithMeta as listBindingIpsWithMeta, repos_listBindingNodes as listBindingNodes, 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_listFailoverLogForService as listFailoverLogForService, repos_listGroupDnsRecords as listGroupDnsRecords, repos_listGroups as listGroups, repos_listHealthCheckTargets as listHealthCheckTargets, repos_listHealthChecks as listHealthChecks, repos_listHealthProbeLogForService as listHealthProbeLogForService, repos_listIpHealthByServiceIds as listIpHealthByServiceIds, repos_listIpHealthStatus as listIpHealthStatus, repos_listNodes as listNodes, repos_listNotificationLog as listNotificationLog, repos_listOriginIpsForFqdn as listOriginIpsForFqdn, repos_listRecordsForBinding as listRecordsForBinding, repos_listServiceGroups as listServiceGroups, repos_listServiceIpRows as listServiceIpRows, 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_pruneStaleIpHealthStatus as pruneStaleIpHealthStatus, 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_setBindingRoutingStrategy as setBindingRoutingStrategy, 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_setServiceIpEnabled as setServiceIpEnabled, repos_setServiceLb as setServiceLb, repos_unlinkBindingRecord as unlinkBindingRecord, repos_unlinkGroupDnsRecord as unlinkGroupDnsRecord, repos_updateBindingDomain as updateBindingDomain, 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_updateHealthCheck as updateHealthCheck, repos_updateNode as updateNode, 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_FailoverLogAction as FailoverLogAction, type repos_FailoverLogRow as FailoverLogRow, type repos_HealthAggregate as HealthAggregate, type repos_ServiceGroupLbPatch as ServiceGroupLbPatch, type repos_ServiceIpHealthRow as ServiceIpHealthRow, type repos_ServiceIpRow as ServiceIpRow, type repos_UpdateSubdomainPatch as UpdateSubdomainPatch, repos_addDomainTags as addDomainTags, repos_aggregateGroupScopeHealthByIds as aggregateGroupScopeHealthByIds, repos_aggregateIpHealthByRefs as aggregateIpHealthByRefs, repos_aggregateIpHealthByServiceIds as aggregateIpHealthByServiceIds, repos_bindingsToRemove as bindingsToRemove, repos_bumpBindingVersion as bumpBindingVersion, repos_countCertificatesByStatus as countCertificatesByStatus, repos_createDomain as createDomain, repos_createDomainMonitor as createDomainMonitor, repos_createGroup as createGroup, repos_createHealthCheck as createHealthCheck, repos_createNode as createNode, 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_deleteHealthCheck as deleteHealthCheck, repos_deleteIpHealthStatusForIp as deleteIpHealthStatusForIp, repos_deleteIpHealthStatusForRef as deleteIpHealthStatusForRef, repos_deleteNode as deleteNode, repos_deleteService as deleteService, repos_deleteServiceGroup as deleteServiceGroup, repos_deleteSubdomain as deleteSubdomain, repos_ensureNode as ensureNode, repos_findBinding as findBinding, repos_findDnsByCfId as findDnsByCfId, repos_findDomainByZoneName as findDomainByZoneName, repos_findHealthCheckByCfId as findHealthCheckByCfId, repos_findNodeByAddress as findNodeByAddress, repos_findNodeByIp as findNodeByIp, 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_getHealthCheck as getHealthCheck, repos_getIpHealthStatusRow as getIpHealthStatusRow, repos_getNode as getNode, 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_insertFailoverLog as insertFailoverLog, repos_insertHealthProbeLog as insertHealthProbeLog, repos_insertNotificationLog as insertNotificationLog, repos_linkBindingRecord as linkBindingRecord, repos_linkGroupDnsRecord as linkGroupDnsRecord, repos_listAllBindings as listAllBindings, repos_listAllDomains as listAllDomains, repos_listAllNodes as listAllNodes, repos_listAllSubdomains as listAllSubdomains, repos_listBindingIps as listBindingIps, repos_listBindingIpsWithMeta as listBindingIpsWithMeta, repos_listBindingNodes as listBindingNodes, 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_listFailoverLogForService as listFailoverLogForService, repos_listGroupDnsRecords as listGroupDnsRecords, repos_listGroups as listGroups, repos_listHealthCheckTargets as listHealthCheckTargets, repos_listHealthChecks as listHealthChecks, repos_listHealthProbeLogForService as listHealthProbeLogForService, repos_listIpHealthByServiceIds as listIpHealthByServiceIds, repos_listIpHealthStatus as listIpHealthStatus, repos_listLatestLiveHealthByServiceIds as listLatestLiveHealthByServiceIds, repos_listNodes as listNodes, repos_listNotificationLog as listNotificationLog, repos_listOriginIpsForFqdn as listOriginIpsForFqdn, repos_listRecordsForBinding as listRecordsForBinding, repos_listServiceGroups as listServiceGroups, repos_listServiceIpRows as listServiceIpRows, 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_pruneStaleIpHealthStatus as pruneStaleIpHealthStatus, 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_setBindingRoutingStrategy as setBindingRoutingStrategy, 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_setServiceIpEnabled as setServiceIpEnabled, repos_setServiceLb as setServiceLb, repos_unlinkBindingRecord as unlinkBindingRecord, repos_unlinkGroupDnsRecord as unlinkGroupDnsRecord, repos_updateBindingDomain as updateBindingDomain, 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_updateHealthCheck as updateHealthCheck, repos_updateNode as updateNode, 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 AppSettingsDto, type AppSettingsPatch, type AppendAuditInput, ConflictError, type Db, type DnsListFilter, type HealthEngineFallbacks, type HealthEngineSettings, NotFoundError, type Sqlite, type UpdateSubdomainPatch, appSettings, appendAudit, auditLog, bindingNodes, certificates, createDb, createMemoryDb, dnsRecords, domainMonitorResults, domainMonitors, domainTags, domains, failoverLog, getAppSettings, getAppSettingsSecrets, groups, healthCheck, healthChecks, healthProbeLog, ipHealthStatus, listAudit, nodes, notificationLog, repos, resolveDatabasePath, runMigrations, schema, serviceBindingIps, serviceBindingRecords, serviceBindings, serviceGroupDnsRecords, serviceGroups, serviceIps, services, subdomains, syncJobs, touchVpsTrackerSync, updateAppSettings }; diff --git a/packages/db/dist/index.js b/packages/db/dist/index.js index 596b482..8a06eb5 100644 --- a/packages/db/dist/index.js +++ b/packages/db/dist/index.js @@ -757,6 +757,7 @@ __export(repos_exports, { listHealthProbeLogForService: () => listHealthProbeLogForService, listIpHealthByServiceIds: () => listIpHealthByServiceIds, listIpHealthStatus: () => listIpHealthStatus, + listLatestLiveHealthByServiceIds: () => listLatestLiveHealthByServiceIds, listNodes: () => listNodes, listNotificationLog: () => listNotificationLog, listOriginIpsForFqdn: () => listOriginIpsForFqdn, @@ -2127,6 +2128,83 @@ function listIpHealthByServiceIds(db, serviceIds) { } return result; } +function parseIpHealthState(status) { + if (status === "up" || status === "down" || status === "degraded" || status === "unknown") { + return status; + } + return void 0; +} +function bestAliveIpState(statuses) { + if (statuses.some((status) => status === "up")) return "up"; + if (statuses.some((status) => status === "degraded")) return "degraded"; + if (statuses.some((status) => status === "down")) return "down"; + return "unknown"; +} +function listLatestLiveHealthByServiceIds(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, + l.ip AS ip, + l.provider AS provider, + l.status AS status, + l.latency_ms AS latency_ms, + l.error AS last_error, + l.colo AS colo, + l.checked_at AS checked_at + FROM health_probe_log l + INNER JOIN service_bindings sb + ON l.scope = 'binding' AND l.ref_id = sb.id + INNER JOIN ( + SELECT sb2.service_id AS service_id, + l2.ip AS ip, + l2.provider AS provider, + MAX(l2.id) AS max_id + FROM health_probe_log l2 + INNER JOIN service_bindings sb2 + ON l2.scope = 'binding' AND l2.ref_id = sb2.id + WHERE sb2.service_id IN (${idList}) + GROUP BY sb2.service_id, l2.ip, l2.provider + ) latest + ON latest.max_id = l.id + `); + const byServiceIp = /* @__PURE__ */ new Map(); + for (const row of rows) { + const status = parseIpHealthState(row.status); + if (!status) continue; + const key = `${row.service_id}\0${row.ip}`; + const probe = { + ip: row.ip, + status, + latency_ms: row.latency_ms, + last_checked_at: row.checked_at, + last_error: row.last_error, + provider: normalizeStatusProvider(row.provider), + colo: row.colo + }; + const bucket = byServiceIp.get(key); + if (bucket) bucket.probes.push(probe); + else { + byServiceIp.set(key, { + serviceId: row.service_id, + ip: row.ip, + probes: [probe] + }); + } + } + for (const { serviceId, ip, probes } of byServiceIp.values()) { + const status = bestAliveIpState(probes.map((probe) => probe.status)); + const preferred = probes.find((probe) => probe.status === status) ?? probes[0]; + const list = result.get(serviceId) ?? []; + list.push({ ...preferred, ip, status }); + result.set(serviceId, list); + } + return result; +} function mergeHealthAggregates(parts) { const rank = { unknown: 0,