import SwiftUI import ProxyCore import GRDB struct PinnedDomainsView: View { @State private var domains: [PinnedDomain] = [] @State private var observation: AnyDatabaseCancellable? @State private var showClearConfirmation = false private let repo = PinnedDomainRepository() var body: some View { List { Section { VStack(alignment: .leading, spacing: 8) { Text("SSL Pinning Detection") .font(.headline) Text("Domains listed here were automatically detected as using SSL pinning. MITM interception is skipped for these domains — they use passthrough instead so the app still works.") .font(.subheadline) .foregroundStyle(.secondary) } .padding(.vertical, 4) } Section("Detected Domains (\(domains.count))") { if domains.isEmpty { Text("No pinned domains detected yet") .foregroundStyle(.secondary) .font(.subheadline) } else { ForEach(domains) { domain in VStack(alignment: .leading, spacing: 4) { Text(domain.domain) .font(.subheadline.weight(.medium)) if let reason = domain.reason { Text(reason) .font(.caption2) .foregroundStyle(.secondary) .lineLimit(2) } Text("Detected \(Date(timeIntervalSince1970: domain.detectedAt).formatted(.relative(presentation: .named)))") .font(.caption2) .foregroundStyle(.tertiary) } } .onDelete { offsets in for index in offsets { try? repo.unpin(domain: domains[index].domain) } } } } } .navigationTitle("Pinned Domains") .toolbar { if !domains.isEmpty { ToolbarItem(placement: .topBarTrailing) { Button("Clear All", role: .destructive) { showClearConfirmation = true } } } } .confirmationDialog("Clear All Pinned Domains?", isPresented: $showClearConfirmation) { Button("Clear All", role: .destructive) { try? repo.deleteAll() } } message: { Text("This will allow MITM interception to be attempted again for all domains. Pinned domains will be re-detected automatically if they still use SSL pinning.") } .task { observation = repo.observeAll() .start(in: DatabaseManager.shared.dbPool) { error in print("Pinned domains observation error: \(error)") } onChange: { newDomains in withAnimation { domains = newDomains } } } } }