- 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
83 lines
2.8 KiB
Swift
83 lines
2.8 KiB
Swift
import SwiftUI
|
|
import ProxyCore
|
|
import GRDB
|
|
|
|
struct PinView: View {
|
|
@State private var pinnedRequests: [CapturedTraffic] = []
|
|
@State private var observation: AnyDatabaseCancellable?
|
|
@State private var showClearAllConfirmation = false
|
|
|
|
private let trafficRepo = TrafficRepository()
|
|
|
|
var body: some View {
|
|
Group {
|
|
if pinnedRequests.isEmpty {
|
|
EmptyStateView(
|
|
icon: "pin.slash",
|
|
title: "No Pinned Requests",
|
|
subtitle: "Pin requests from the Home tab to save them here for quick access."
|
|
)
|
|
} else {
|
|
List {
|
|
ForEach(pinnedRequests) { request in
|
|
if let id = request.id {
|
|
NavigationLink(value: id) {
|
|
TrafficRowView(traffic: request)
|
|
}
|
|
}
|
|
}
|
|
.onDelete { offsets in
|
|
for index in offsets {
|
|
let request = pinnedRequests[index]
|
|
if let id = request.id {
|
|
try? trafficRepo.togglePin(id: id, isPinned: false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.navigationDestination(for: Int64.self) { id in
|
|
RequestDetailView(trafficId: id)
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Pin")
|
|
.toolbar {
|
|
if !pinnedRequests.isEmpty {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Button(role: .destructive) {
|
|
showClearAllConfirmation = true
|
|
} label: {
|
|
Text("Clear All")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.confirmationDialog(
|
|
"Clear All Pins",
|
|
isPresented: $showClearAllConfirmation,
|
|
titleVisibility: .visible
|
|
) {
|
|
Button("Clear All Pins", role: .destructive) {
|
|
for request in pinnedRequests {
|
|
if let id = request.id {
|
|
try? trafficRepo.togglePin(id: id, isPinned: false)
|
|
}
|
|
}
|
|
}
|
|
Button("Cancel", role: .cancel) {}
|
|
} message: {
|
|
Text("This will unpin all \(pinnedRequests.count) pinned requests.")
|
|
}
|
|
.task {
|
|
observation = trafficRepo.observePinnedTraffic()
|
|
.start(in: DatabaseManager.shared.dbPool) { error in
|
|
print("Pin observation error: \(error)")
|
|
} onChange: { pinned in
|
|
withAnimation {
|
|
pinnedRequests = pinned
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|