151 lines
5.2 KiB
Swift
151 lines
5.2 KiB
Swift
import SwiftUI
|
|
import ProxyCore
|
|
import GRDB
|
|
|
|
struct SSLProxyingListView: View {
|
|
@State private var isEnabled = IPCManager.shared.isSSLProxyingEnabled
|
|
@State private var entries: [SSLProxyingEntry] = []
|
|
@State private var showAddInclude = false
|
|
@State private var showAddExclude = false
|
|
@State private var observation: AnyDatabaseCancellable?
|
|
|
|
private let rulesRepo = RulesRepository()
|
|
|
|
var includeEntries: [SSLProxyingEntry] {
|
|
entries.filter(\.isInclude)
|
|
}
|
|
|
|
var excludeEntries: [SSLProxyingEntry] {
|
|
entries.filter { !$0.isInclude }
|
|
}
|
|
|
|
var body: some View {
|
|
List {
|
|
Section {
|
|
ToggleHeaderView(
|
|
title: "SSL Proxying",
|
|
description: "Decrypt HTTPS traffic from included domains. Excluded domains are always passed through.",
|
|
isEnabled: $isEnabled
|
|
)
|
|
.onChange(of: isEnabled) { _, newValue in
|
|
IPCManager.shared.isSSLProxyingEnabled = newValue
|
|
IPCManager.shared.post(.configurationChanged)
|
|
}
|
|
}
|
|
.listRowInsets(EdgeInsets())
|
|
|
|
Section("Include") {
|
|
if includeEntries.isEmpty {
|
|
Text("No include entries")
|
|
.foregroundStyle(.secondary)
|
|
.font(.subheadline)
|
|
} else {
|
|
ForEach(includeEntries) { entry in
|
|
Text(entry.domainPattern)
|
|
}
|
|
.onDelete { indexSet in
|
|
for index in indexSet {
|
|
if let id = includeEntries[index].id {
|
|
try? rulesRepo.deleteSSLEntry(id: id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Section("Exclude") {
|
|
if excludeEntries.isEmpty {
|
|
Text("No exclude entries")
|
|
.foregroundStyle(.secondary)
|
|
.font(.subheadline)
|
|
} else {
|
|
ForEach(excludeEntries) { entry in
|
|
Text(entry.domainPattern)
|
|
}
|
|
.onDelete { indexSet in
|
|
for index in indexSet {
|
|
if let id = excludeEntries[index].id {
|
|
try? rulesRepo.deleteSSLEntry(id: id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("SSL Proxying")
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Menu {
|
|
Button("Add Include Entry") { showAddInclude = true }
|
|
Button("Add Exclude Entry") { showAddExclude = true }
|
|
Divider()
|
|
Button("Clear All Rules", role: .destructive) {
|
|
try? rulesRepo.deleteAllSSLEntries()
|
|
}
|
|
} label: {
|
|
Image(systemName: "ellipsis.circle")
|
|
}
|
|
}
|
|
}
|
|
.sheet(isPresented: $showAddInclude) {
|
|
DomainEntrySheet(title: "New Include Entry", isInclude: true) { pattern in
|
|
var entry = SSLProxyingEntry(domainPattern: pattern, isInclude: true)
|
|
try? rulesRepo.insertSSLEntry(&entry)
|
|
}
|
|
}
|
|
.sheet(isPresented: $showAddExclude) {
|
|
DomainEntrySheet(title: "New Exclude Entry", isInclude: false) { pattern in
|
|
var entry = SSLProxyingEntry(domainPattern: pattern, isInclude: false)
|
|
try? rulesRepo.insertSSLEntry(&entry)
|
|
}
|
|
}
|
|
.task {
|
|
observation = rulesRepo.observeSSLEntries()
|
|
.start(in: DatabaseManager.shared.dbPool) { error in
|
|
print("SSL observation error: \(error)")
|
|
} onChange: { newEntries in
|
|
entries = newEntries
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Domain Entry Sheet
|
|
|
|
struct DomainEntrySheet: View {
|
|
let title: String
|
|
let isInclude: Bool
|
|
let onSave: (String) -> Void
|
|
|
|
@State private var domainPattern = ""
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Form {
|
|
Section {
|
|
TextField("Domain Pattern", text: $domainPattern)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
} footer: {
|
|
Text("Supports wildcards: * (zero or more) and ? (single character). Example: *.example.com")
|
|
}
|
|
}
|
|
.navigationTitle(title)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button("Cancel") { dismiss() }
|
|
}
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button("Save") {
|
|
onSave(domainPattern)
|
|
dismiss()
|
|
}
|
|
.disabled(domainPattern.isEmpty)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|