krz/domain-dig

an ios app for DNS & SSL analysis

clone: git clone https://gitbay.org/krz/domain-dig.git

v5.0.3: DomainDig/ScheduledReportsView.swift · raw

  1import SwiftUI
  2
  3struct ScheduledReportsView: View {
  4    @Environment(\.appDensity) private var appDensity
  5    @State private var settings = ScheduledReportStorage.loadSettings()
  6    @State private var logs = ScheduledReportStorage.loadLogs()
  7    @State private var isGenerating = false
  8    @State private var statusMessage: String?
  9
 10    var body: some View {
 11        List {
 12            Section("Overview") {
 13                if !FeatureAccessService.hasAccess(to: .automatedMonitoring) {
 14                    Text("Scheduled reports require Pro.")
 15                        .font(appDensity.font(.caption))
 16                        .foregroundStyle(Color(.appTextSecondary))
 17                }
 18
 19                Toggle("Scheduled Reports", isOn: Binding(
 20                    get: { settings.isEnabled },
 21                    set: { newValue in
 22                        settings.isEnabled = newValue
 23                        saveSettings()
 24                    }
 25                ))
 26                .disabled(!FeatureAccessService.hasAccess(to: .automatedMonitoring))
 27
 28                Picker("Cadence", selection: Binding(
 29                    get: { settings.cadence },
 30                    set: { newValue in
 31                        settings.cadence = newValue
 32                        saveSettings()
 33                    }
 34                )) {
 35                    ForEach(ScheduledReportCadence.allCases) { cadence in
 36                        Text(cadence.title).tag(cadence)
 37                    }
 38                }
 39                .disabled(!FeatureAccessService.hasAccess(to: .automatedMonitoring))
 40
 41                Picker("Format", selection: Binding(
 42                    get: { settings.format },
 43                    set: { newValue in
 44                        settings.format = newValue
 45                        saveSettings()
 46                    }
 47                )) {
 48                    ForEach([DomainExportFormat.markdown, .pdf, .json]) { format in
 49                        Text(format.title).tag(format)
 50                    }
 51                }
 52                .disabled(!FeatureAccessService.hasAccess(to: .automatedMonitoring))
 53
 54                LabeledContent(
 55                    "Last Generated",
 56                    value: settings.lastGeneratedAt?.formatted(date: .abbreviated, time: .shortened) ?? "Never"
 57                )
 58
 59                if let statusMessage {
 60                    Text(statusMessage)
 61                        .font(appDensity.font(.caption))
 62                        .foregroundStyle(Color(.appTextSecondary))
 63                }
 64
 65                Button(isGenerating ? "Generating…" : "Generate Now") {
 66                    Task { await generateNow() }
 67                }
 68                .disabled(isGenerating || !FeatureAccessService.hasAccess(to: .automatedMonitoring))
 69            }
 70            .listRowBackground(Color(.appSurface))
 71
 72            if logs.isEmpty {
 73                Section {
 74                    EmptyStateCardView(
 75                        title: "No Reports Yet",
 76                        message: "Generated reports for your watchlist appear here after a manual or scheduled run.",
 77                        suggestion: "Tap Generate Now, or enable Scheduled Reports above.",
 78                        systemImage: "doc.text",
 79                        showsCardBackground: false
 80                    )
 81                }
 82                .listRowBackground(Color(.appSurface))
 83            } else {
 84                Section("Recent Reports") {
 85                    ForEach(logs) { log in
 86                        Button {
 87                            share(log)
 88                        } label: {
 89                            VStack(alignment: .leading, spacing: 2) {
 90                                Text("\(log.domainCount) domains • \(log.format.title)")
 91                                    .foregroundStyle(.primary)
 92                                Text(log.generatedAt.formatted(date: .abbreviated, time: .shortened))
 93                                    .font(.caption)
 94                                    .foregroundStyle(Color(.appTextSecondary))
 95                            }
 96                        }
 97                    }
 98                }
 99                .listRowBackground(Color(.appSurface))
100            }
101        }
102        .scrollContentBackground(.hidden)
103        .background(Color(.appBackground))
104        .navigationTitle("Scheduled Reports")
105    }
106
107    private func saveSettings() {
108        ScheduledReportStorage.saveSettings(settings)
109        ScheduledReportScheduler.shared.syncSchedule()
110    }
111
112    private func generateNow() async {
113        isGenerating = true
114        let outcome = await ScheduledReportService.shared.generateReport(trigger: .manual, requireEnabledSetting: false)
115        statusMessage = outcome.message
116        settings = ScheduledReportStorage.loadSettings()
117        logs = ScheduledReportStorage.loadLogs()
118        isGenerating = false
119    }
120
121    private func share(_ log: ScheduledReportLog) {
122        let url = ScheduledReportStorage.reportsDirectory.appendingPathComponent(log.fileName)
123        guard let data = try? Data(contentsOf: url) else {
124            statusMessage = "That report is no longer available on disk."
125            return
126        }
127        ExportPresenter.share(filename: log.fileName, data: data)
128    }
129}