Initial project setup - Phases 1-3 complete
This commit is contained in:
150
UI/More/SSLProxyingListView.swift
Normal file
150
UI/More/SSLProxyingListView.swift
Normal file
@@ -0,0 +1,150 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user