import SwiftUI import ProxyCore import GRDB struct DNSSpoofingView: View { @State private var isEnabled = IPCManager.shared.isDNSSpoofingEnabled @State private var rules: [DNSSpoofRule] = [] @State private var showAddRule = false @State private var editingRule: DNSSpoofRule? @State private var ruleToDelete: DNSSpoofRule? @State private var showDeleteConfirmation = false @State private var observation: AnyDatabaseCancellable? private let rulesRepo = RulesRepository() var body: some View { List { Section { ToggleHeaderView( title: "DNS Spoofing", description: "Redirect domain resolution to a different target. Useful for routing production domains to development servers.", isEnabled: $isEnabled ) .onChange(of: isEnabled) { _, newValue in IPCManager.shared.isDNSSpoofingEnabled = newValue IPCManager.shared.post(.configurationChanged) } } .listRowInsets(EdgeInsets()) Section("Rules") { if rules.isEmpty { EmptyStateView( icon: "network", title: "No DNS Spoofing Rules", subtitle: "Tap + to create a new rule." ) } else { ForEach(rules) { rule in Button { editingRule = rule } label: { VStack(alignment: .leading, spacing: 4) { HStack { Text(rule.sourceDomain) .font(.subheadline) Image(systemName: "arrow.right") .font(.caption) .foregroundStyle(.secondary) Text(rule.targetDomain) .font(.subheadline) .foregroundStyle(.blue) } } } .tint(.primary) } .onDelete { indexSet in if let index = indexSet.first { ruleToDelete = rules[index] showDeleteConfirmation = true } } } } } .navigationTitle("DNS Spoofing") .toolbar { ToolbarItem(placement: .topBarTrailing) { Button { showAddRule = true } label: { Image(systemName: "plus") } } } .sheet(isPresented: $showAddRule) { AddDNSSpoofRuleSheet(rulesRepo: rulesRepo, isPresented: $showAddRule) } .sheet(item: $editingRule) { rule in AddDNSSpoofRuleSheet(rulesRepo: rulesRepo, existingRule: rule, isPresented: .constant(true)) } .confirmationDialog("Delete this rule?", isPresented: $showDeleteConfirmation, presenting: ruleToDelete) { rule in Button("Delete", role: .destructive) { if let id = rule.id { try? rulesRepo.deleteDNSSpoofRule(id: id) } } } .task { observation = rulesRepo.observeDNSSpoofRules() .start(in: DatabaseManager.shared.dbPool) { error in print("DNS Spoof observation error: \(error)") } onChange: { newRules in rules = newRules } } } } // MARK: - Add DNS Spoof Rule Sheet private struct AddDNSSpoofRuleSheet: View { let rulesRepo: RulesRepository let existingRule: DNSSpoofRule? @Binding var isPresented: Bool @Environment(\.dismiss) private var dismiss @State private var sourceDomain = "" @State private var targetDomain = "" init(rulesRepo: RulesRepository, existingRule: DNSSpoofRule? = nil, isPresented: Binding) { self.rulesRepo = rulesRepo self.existingRule = existingRule self._isPresented = isPresented } var body: some View { NavigationStack { Form { Section { TextField("Source Domain (e.g. api.example.com)", text: $sourceDomain) .textInputAutocapitalization(.never) .autocorrectionDisabled() TextField("Target Domain (e.g. dev.example.com)", text: $targetDomain) .textInputAutocapitalization(.never) .autocorrectionDisabled() } Section { Button("Save") { if let existing = existingRule { let updated = DNSSpoofRule( id: existing.id, sourceDomain: sourceDomain, targetDomain: targetDomain, isEnabled: existing.isEnabled, createdAt: existing.createdAt ) try? rulesRepo.updateDNSSpoofRule(updated) } else { var rule = DNSSpoofRule( sourceDomain: sourceDomain, targetDomain: targetDomain ) try? rulesRepo.insertDNSSpoofRule(&rule) } isPresented = false dismiss() } .disabled(sourceDomain.isEmpty || targetDomain.isEmpty) } } .navigationTitle(existingRule == nil ? "New DNS Rule" : "Edit DNS Rule") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { isPresented = false dismiss() } } } .onAppear { if let rule = existingRule { sourceDomain = rule.sourceDomain targetDomain = rule.targetDomain } } } } }