From 1715e59287a7b48b29ed8cbf2cd45fcc92ec3126 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Sat, 25 Apr 2026 00:25:18 -0500 Subject: DomainDig v4.0.0 — Integrations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Outbound webhook delivery * Native Slack webhook integration * SMTP-based email alerts * Per-integration event filtering * Reliable local delivery queue * Delivery logs and retry visibility * Integration management UI * Normalized event severity model * Canonical structured event payloads --- DomainDig/DomainMonitoringService.swift | 91 +++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) (limited to 'DomainDig/DomainMonitoringService.swift') diff --git a/DomainDig/DomainMonitoringService.swift b/DomainDig/DomainMonitoringService.swift index 5034402..31b1505 100644 --- a/DomainDig/DomainMonitoringService.swift +++ b/DomainDig/DomainMonitoringService.swift @@ -361,6 +361,12 @@ final class DomainMonitoringService { errors: errors ) saveLog(log) + let outboundEvents = monitoringEvents(from: log) + if outboundEvents.isEmpty { + IntegrationService.shared.recordNoOutboundEvents(for: log.summary) + } else { + IntegrationService.shared.enqueue(events: outboundEvents) + } return MonitoringRunOutcome( success: errors.count < results.count, @@ -375,6 +381,91 @@ final class DomainMonitoringService { MonitoringStorage.saveLogs(logs) } + private func monitoringEvents(from log: MonitoringLog) -> [MonitoringEvent] { + var events: [MonitoringEvent] = log.checkedDomains.compactMap { result in + if let errorMessage = result.errorMessage { + return MonitoringEvent( + type: .monitoringFailure, + severity: .critical, + domain: result.domain, + timestamp: result.checkedAt, + summary: errorMessage, + details: [ + "trigger": log.trigger.rawValue, + "resultSource": result.resultSource.rawValue + ] + ) + } + + if result.certificateWarningLevel == .critical { + return MonitoringEvent( + type: .certificateExpiring, + severity: .critical, + domain: result.domain, + timestamp: result.checkedAt, + summary: result.summaryMessage, + details: [ + "certificateWarningLevel": result.certificateWarningLevel.rawValue, + "trigger": log.trigger.rawValue + ] + ) + } + + guard let alertSeverity = result.alertSeverity else { + return nil + } + + return MonitoringEvent( + type: eventType(for: result.summaryMessage), + severity: EventSeverity(monitoringSeverity: alertSeverity), + domain: result.domain, + timestamp: result.checkedAt, + summary: result.summaryMessage, + details: [ + "alertSeverity": alertSeverity.title, + "certificateWarningLevel": result.certificateWarningLevel.rawValue, + "resultSource": result.resultSource.rawValue, + "trigger": log.trigger.rawValue + ] + ) + } + + if !log.errors.isEmpty { + events.append( + MonitoringEvent( + type: .monitoringFailure, + severity: .critical, + domain: "portfolio", + timestamp: log.timestamp, + summary: "Monitoring run completed with errors", + details: [ + "errors": log.errors.joined(separator: " | "), + "trigger": log.trigger.rawValue + ] + ) + ) + } + + return events + } + + private func eventType(for summary: String) -> MonitoringEventType { + let normalized = summary.lowercased() + if normalized.contains("dns") { + return .dnsChanged + } + if normalized.contains("certificate") { + return .certificateUpdated + } + if normalized.contains("redirect") { + return .redirectChanged + } + if normalized.contains("header") { + return .headersChanged + } + return .changeDetected + } + private func latestSnapshot(for trackedDomain: TrackedDomain, history: [HistoryEntry]) -> LookupSnapshot? { history.first(where: { entry in if let trackedDomainID = entry.trackedDomainID { -- cgit v1.2.3