Add iPad support, auto-pinning, and comprehensive logging
- Adaptive iPhone/iPad layout with NavigationSplitView sidebar - Auto-detect SSL-pinned domains, fall back to passthrough - Certificate install via local HTTP server (Safari profile flow) - App Group-backed CA, per-domain leaf cert LRU cache - DB-backed config repository, Darwin notification throttling - Rules engine, breakpoint rules, pinned domain tracking - os.Logger instrumentation across tunnel/proxy/mitm/capture/cert/rules/db/ipc/ui - Fix dyld framework embed, race conditions, thread safety
This commit is contained in:
@@ -6,6 +6,9 @@ struct BlockListView: View {
|
||||
@State private var isEnabled = IPCManager.shared.isBlockListEnabled
|
||||
@State private var entries: [BlockListEntry] = []
|
||||
@State private var showAddRule = false
|
||||
@State private var editingEntry: BlockListEntry?
|
||||
@State private var entryToDelete: BlockListEntry?
|
||||
@State private var showDeleteConfirmation = false
|
||||
@State private var observation: AnyDatabaseCancellable?
|
||||
|
||||
private let rulesRepo = RulesRepository()
|
||||
@@ -32,22 +35,26 @@ struct BlockListView: View {
|
||||
.font(.subheadline)
|
||||
} else {
|
||||
ForEach(entries) { entry in
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(entry.name ?? entry.urlPattern)
|
||||
.font(.subheadline.weight(.medium))
|
||||
Text(entry.urlPattern)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
Text(entry.action.displayName)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.tertiary)
|
||||
Button {
|
||||
editingEntry = entry
|
||||
} label: {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(entry.name ?? entry.urlPattern)
|
||||
.font(.subheadline.weight(.medium))
|
||||
Text(entry.urlPattern)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
Text(entry.action.displayName)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
}
|
||||
.tint(.primary)
|
||||
}
|
||||
.onDelete { indexSet in
|
||||
for index in indexSet {
|
||||
if let id = entries[index].id {
|
||||
try? rulesRepo.deleteBlockEntry(id: id)
|
||||
}
|
||||
if let index = indexSet.first {
|
||||
entryToDelete = entries[index]
|
||||
showDeleteConfirmation = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,6 +74,18 @@ struct BlockListView: View {
|
||||
try? rulesRepo.insertBlockEntry(&entry)
|
||||
}
|
||||
}
|
||||
.sheet(item: $editingEntry) { entry in
|
||||
NewBlockRuleView(existingEntry: entry) { updated in
|
||||
try? rulesRepo.updateBlockEntry(updated)
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Delete this rule?", isPresented: $showDeleteConfirmation, presenting: entryToDelete) { entry in
|
||||
Button("Delete", role: .destructive) {
|
||||
if let id = entry.id {
|
||||
try? rulesRepo.deleteBlockEntry(id: id)
|
||||
}
|
||||
}
|
||||
}
|
||||
.task {
|
||||
observation = rulesRepo.observeBlockListEntries()
|
||||
.start(in: DatabaseManager.shared.dbPool) { error in
|
||||
@@ -81,6 +100,7 @@ struct BlockListView: View {
|
||||
// MARK: - New Block Rule
|
||||
|
||||
struct NewBlockRuleView: View {
|
||||
let existingEntry: BlockListEntry?
|
||||
let onSave: (BlockListEntry) -> Void
|
||||
|
||||
@State private var name = ""
|
||||
@@ -90,6 +110,11 @@ struct NewBlockRuleView: View {
|
||||
@State private var blockAction: BlockAction = .blockAndHide
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
init(existingEntry: BlockListEntry? = nil, onSave: @escaping (BlockListEntry) -> Void) {
|
||||
self.existingEntry = existingEntry
|
||||
self.onSave = onSave
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
@@ -118,7 +143,7 @@ struct NewBlockRuleView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("New Block Rule")
|
||||
.navigationTitle(existingEntry == nil ? "New Block Rule" : "Edit Block Rule")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
@@ -127,11 +152,14 @@ struct NewBlockRuleView: View {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Save") {
|
||||
let entry = BlockListEntry(
|
||||
id: existingEntry?.id,
|
||||
name: name.isEmpty ? nil : name,
|
||||
urlPattern: urlPattern,
|
||||
method: method,
|
||||
includeSubpaths: includeSubpaths,
|
||||
blockAction: blockAction
|
||||
blockAction: blockAction,
|
||||
isEnabled: existingEntry?.isEnabled ?? true,
|
||||
createdAt: existingEntry?.createdAt ?? Date().timeIntervalSince1970
|
||||
)
|
||||
onSave(entry)
|
||||
dismiss()
|
||||
@@ -139,6 +167,15 @@ struct NewBlockRuleView: View {
|
||||
.disabled(urlPattern.isEmpty)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if let entry = existingEntry {
|
||||
name = entry.name ?? ""
|
||||
urlPattern = entry.urlPattern
|
||||
method = entry.method
|
||||
includeSubpaths = entry.includeSubpaths
|
||||
blockAction = entry.action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user