summaryrefslogtreecommitdiff
path: root/DomainDig/DomainViewModel.swift
diff options
context:
space:
mode:
Diffstat (limited to 'DomainDig/DomainViewModel.swift')
-rw-r--r--DomainDig/DomainViewModel.swift138
1 files changed, 83 insertions, 55 deletions
diff --git a/DomainDig/DomainViewModel.swift b/DomainDig/DomainViewModel.swift
index 656529c..03e9ad4 100644
--- a/DomainDig/DomainViewModel.swift
+++ b/DomainDig/DomainViewModel.swift
@@ -756,6 +756,7 @@ final class DomainViewModel {
func clearHistory() {
history.removeAll()
persistHistory()
+ clearMonitoringLogs()
refreshDataLifecycleSummary()
}
@@ -779,6 +780,7 @@ final class DomainViewModel {
refreshingTrackedDomainID = nil
persistTrackedDomains()
sanitizeMonitoringSelection()
+ clearMonitoringLogs()
refreshDataLifecycleSummary()
}
@@ -842,16 +844,45 @@ final class DomainViewModel {
persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled)
}
- func setMonitoringFrequency(_ frequency: MonitoringFrequency) {
+ func setMonitoringBaseInterval(_ baseInterval: MonitoringBaseInterval) {
guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else {
upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring)
return
}
- monitoringSettings.frequency = frequency
+ monitoringSettings.baseInterval = baseInterval.interval
persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled)
monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule()
}
+ func setMonitoringAdaptiveEnabled(_ isEnabled: Bool) {
+ guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else {
+ upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring)
+ return
+ }
+ monitoringSettings.adaptiveEnabled = isEnabled
+ persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled)
+ monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule()
+ }
+
+ func setMonitoringSensitivity(_ sensitivity: MonitoringSensitivity) {
+ guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else {
+ upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring)
+ return
+ }
+ monitoringSettings.sensitivity = sensitivity
+ persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled)
+ monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule()
+ }
+
+ func setMonitoringQuietHours(startHour: Int, endHour: Int, isEnabled: Bool) {
+ guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else {
+ upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring)
+ return
+ }
+ monitoringSettings.quietHours = isEnabled ? QuietHours(startHour: startHour, endHour: endHour) : nil
+ persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled)
+ }
+
func setMonitoringAlertFilter(_ filter: MonitoringAlertFilter) {
guard FeatureAccessService.hasAccess(to: .localAlerts) else {
monitoringSettings.alertsEnabled = false
@@ -928,6 +959,30 @@ final class DomainViewModel {
}
}
+ func monitoringIntervalLabel(for trackedDomain: TrackedDomain) -> String {
+ let state = DomainMonitoringScheduler.normalizedMonitoringState(for: trackedDomain, settings: monitoringSettings)
+ return Self.intervalLabel(for: state.currentInterval)
+ }
+
+ func monitoringStatusLabel(for trackedDomain: TrackedDomain) -> String {
+ let state = trackedDomain.monitoringState
+ if let lastChangeDate = state.lastChangeDate,
+ Date().timeIntervalSince(lastChangeDate) <= 6 * 60 * 60 {
+ return "Recently Changed"
+ }
+ if state.consecutiveStableChecks >= 2 {
+ return "Stable"
+ }
+ return trackedDomain.monitoringEnabled ? "Active" : "Paused"
+ }
+
+ private static func intervalLabel(for interval: TimeInterval) -> String {
+ let formatter = DateComponentsFormatter()
+ formatter.allowedUnits = interval < 3600 ? [.minute] : [.hour, .minute]
+ formatter.unitsStyle = .full
+ return formatter.string(from: interval) ?? "Unknown"
+ }
+
func rerunLookup(from entry: HistoryEntry, useSnapshotResolver: Bool) {
if useSnapshotResolver {
UserDefaults.standard.set(entry.resolverURLString, forKey: DNSResolverOption.userDefaultsKey)
@@ -968,6 +1023,22 @@ final class DomainViewModel {
clearLookupState()
}
+ func clearPresentedResults() {
+ lookupTask?.cancel()
+ customPortScanTask?.cancel()
+ batchTask?.cancel()
+ hasRun = false
+ searchedDomain = ""
+ lastLookupDurationMs = nil
+ currentDiffSections = []
+ currentChangeSummary = nil
+ ownershipDiff = []
+ currentReport = nil
+ refreshingTrackedDomainID = nil
+ clearBatchState()
+ clearLookupState()
+ }
+
func run() {
let target = trimmedDomain
guard !target.isEmpty else { return }
@@ -1222,13 +1293,6 @@ final class DomainViewModel {
}
guard ownershipHistory.isEmpty else { return }
- let creditStatus = await UsageCreditService.shared.status(for: .ownershipHistory)
- guard !creditStatus.isExhausted else {
- ownershipHistoryError = "No ownership history credits remaining"
- await refreshUsageCredits()
- return
- }
-
ownershipHistoryLoading = true
ownershipHistoryError = nil
@@ -1242,15 +1306,9 @@ final class DomainViewModel {
case let .success(events):
ownershipHistory = events
ownershipHistoryError = nil
- if outcome.source != .cached {
- _ = await UsageCreditService.shared.consume(.ownershipHistory)
- }
case let .empty(message):
ownershipHistory = []
ownershipHistoryError = message
- if outcome.source != .cached {
- _ = await UsageCreditService.shared.consume(.ownershipHistory)
- }
case let .error(message):
ownershipHistory = []
ownershipHistoryError = conciseExternalMessage(message, fallback: "Ownership history unavailable")
@@ -1258,7 +1316,6 @@ final class DomainViewModel {
ownershipHistoryLoading = false
_ = saveHistoryEntry(replaceLatest: true)
- await refreshUsageCredits()
}
func loadDNSHistory() async {
@@ -1269,13 +1326,6 @@ final class DomainViewModel {
}
guard dnsHistory.isEmpty else { return }
- let creditStatus = await UsageCreditService.shared.status(for: .dnsHistory)
- guard !creditStatus.isExhausted else {
- dnsHistoryError = "No DNS history credits remaining"
- await refreshUsageCredits()
- return
- }
-
dnsHistoryLoading = true
dnsHistoryError = nil
@@ -1289,15 +1339,9 @@ final class DomainViewModel {
case let .success(events):
dnsHistory = events
dnsHistoryError = nil
- if outcome.source != .cached {
- _ = await UsageCreditService.shared.consume(.dnsHistory)
- }
case let .empty(message):
dnsHistory = []
dnsHistoryError = message
- if outcome.source != .cached {
- _ = await UsageCreditService.shared.consume(.dnsHistory)
- }
case let .error(message):
dnsHistory = []
dnsHistoryError = conciseExternalMessage(message, fallback: "DNS history unavailable")
@@ -1305,7 +1349,6 @@ final class DomainViewModel {
dnsHistoryLoading = false
_ = saveHistoryEntry(replaceLatest: true)
- await refreshUsageCredits()
}
func loadExtendedSubdomains() async {
@@ -1316,13 +1359,6 @@ final class DomainViewModel {
}
guard extendedSubdomains.isEmpty else { return }
- let creditStatus = await UsageCreditService.shared.status(for: .extendedSubdomains)
- guard !creditStatus.isExhausted else {
- extendedSubdomainsError = "No extended subdomain credits remaining"
- await refreshUsageCredits()
- return
- }
-
extendedSubdomainsLoading = true
extendedSubdomainsError = nil
@@ -1335,15 +1371,9 @@ final class DomainViewModel {
case let .success(results):
extendedSubdomains = results
extendedSubdomainsError = nil
- if outcome.source != .cached {
- _ = await UsageCreditService.shared.consume(.extendedSubdomains)
- }
case let .empty(message):
extendedSubdomains = []
extendedSubdomainsError = message
- if outcome.source != .cached {
- _ = await UsageCreditService.shared.consume(.extendedSubdomains)
- }
case let .error(message):
extendedSubdomains = []
extendedSubdomainsError = conciseExternalMessage(message, fallback: "Extended subdomains unavailable")
@@ -1351,7 +1381,6 @@ final class DomainViewModel {
extendedSubdomainsLoading = false
_ = saveHistoryEntry(replaceLatest: true)
- await refreshUsageCredits()
}
func refreshUsageCredits() async {
@@ -1525,14 +1554,12 @@ final class DomainViewModel {
}
guard snapshot.statusMessage == nil else {
- await refreshUsageCredits()
return history.first(where: { $0.id == snapshot.historyEntryID })
}
let saveStartedAt = DomainDebugLog.signpostStart("DomainViewModel.saveHistoryEntry", domain: domain)
let entry = saveHistoryEntry(replaceLatest: false, reuseCurrentAnalysis: true)
DomainDebugLog.signpostEnd("DomainViewModel.saveHistoryEntry", start: saveStartedAt, domain: domain)
- await refreshUsageCredits()
DomainDebugLog.signpostEnd("DomainViewModel.performLookup", start: lookupStartedAt, domain: domain)
return entry
}
@@ -2246,6 +2273,12 @@ final class DomainViewModel {
CloudSyncService.shared.markMonitoringSettingsChanged(localActivationConfirmed: false)
}
+ private func clearMonitoringLogs() {
+ monitoringLogs.removeAll()
+ monitoringStatusMessage = nil
+ MonitoringStorage.saveLogs([])
+ }
+
private func updateTrackedDomainAvailability(for domain: String, status: DomainAvailabilityStatus) {
guard let index = trackedDomains.firstIndex(where: { $0.domain.caseInsensitiveCompare(domain) == .orderedSame }) else {
return
@@ -2533,14 +2566,9 @@ final class DomainViewModel {
return
}
- let entry: HistoryEntry?
- if payload.snapshot.statusMessage == nil {
- entry = saveHistoryEntry(from: payload.snapshot, replaceLatest: false, updateCurrentState: false)
- } else {
- entry = payload.snapshot.historyEntryID.flatMap { id in
- history.first(where: { $0.id == id })
- }
- }
+ let entry = payload.snapshot.historyEntryID.flatMap { id in
+ history.first(where: { $0.id == id })
+ } ?? saveHistoryEntry(from: payload.snapshot, replaceLatest: false, updateCurrentState: false)
let certificateWarningLevel = DomainDiffService.certificateWarningLevel(for: payload.snapshot)
let riskAssessment = entry?.changeSummary?.riskAssessment ?? DomainInsightEngine.analyze(snapshot: payload.snapshot).riskAssessment
let quickStatus: String
@@ -3568,7 +3596,7 @@ final class DomainViewModel {
}
}
if !DataAccessService.hasAccess(to: .extendedSubdomains) {
- lines.append(" Extended subdomain discovery (Data+)")
+ lines.append(" Extended subdomain discovery (Pro+)")
}
}