summaryrefslogtreecommitdiff
path: root/DomainDig/DomainMonitoringService.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-25 00:25:18 -0500
committerChristian Cleberg <[email protected]>2026-04-25 00:25:18 -0500
commit1715e59287a7b48b29ed8cbf2cd45fcc92ec3126 (patch)
treecdca41731ecf7eff1aa83b20dc2a7c5fd33e649f /DomainDig/DomainMonitoringService.swift
parentfd34553ab0e63b4d3bc22532b61f4fef32846f34 (diff)
downloaddomain-dig-1715e59287a7b48b29ed8cbf2cd45fcc92ec3126.tar.gz
domain-dig-1715e59287a7b48b29ed8cbf2cd45fcc92ec3126.tar.bz2
domain-dig-1715e59287a7b48b29ed8cbf2cd45fcc92ec3126.zip
DomainDig v4.0.0 — Integrations
* 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
Diffstat (limited to 'DomainDig/DomainMonitoringService.swift')
-rw-r--r--DomainDig/DomainMonitoringService.swift91
1 files changed, 91 insertions, 0 deletions
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 {