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:
@@ -5,41 +5,79 @@ struct TrafficRowView: View {
|
||||
let traffic: CapturedTraffic
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
HStack(spacing: 6) {
|
||||
MethodBadge(method: traffic.method)
|
||||
StatusBadge(statusCode: traffic.statusCode)
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
HStack(alignment: .top, spacing: 10) {
|
||||
HStack(spacing: 6) {
|
||||
MethodBadge(method: traffic.method)
|
||||
StatusBadge(statusCode: traffic.statusCode)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
Spacer(minLength: 12)
|
||||
|
||||
Text(traffic.startDate, format: .dateTime.hour().minute().second().secondFraction(.fractional(3)))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
VStack(alignment: .trailing, spacing: 4) {
|
||||
Text(traffic.startDate, format: .dateTime.hour().minute().second().secondFraction(.fractional(3)))
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
Text(traffic.formattedDuration)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
Text(traffic.formattedDuration)
|
||||
.font(.caption2.weight(.semibold))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
Text(traffic.url)
|
||||
.font(.caption)
|
||||
.lineLimit(3)
|
||||
.foregroundStyle(.primary)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(primaryLine)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundStyle(.primary)
|
||||
.lineLimit(2)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
if traffic.requestBodySize > 0 {
|
||||
Label(formatBytes(traffic.requestBodySize), systemImage: "arrow.up.circle.fill")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.green)
|
||||
Text(secondaryLine)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
HStack(spacing: 8) {
|
||||
TransferPill(
|
||||
systemImage: "arrow.up.circle.fill",
|
||||
text: formatBytes(traffic.requestBodySize),
|
||||
tint: .green
|
||||
)
|
||||
TransferPill(
|
||||
systemImage: "arrow.down.circle.fill",
|
||||
text: responseSizeText,
|
||||
tint: .blue
|
||||
)
|
||||
|
||||
if let responseContentType = traffic.responseContentType {
|
||||
MetaPill(text: shortContentType(responseContentType))
|
||||
} else if let requestContentType = traffic.requestContentType {
|
||||
MetaPill(text: shortContentType(requestContentType))
|
||||
}
|
||||
if traffic.responseBodySize > 0 {
|
||||
Label(formatBytes(traffic.responseBodySize), systemImage: "arrow.down.circle.fill")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.blue)
|
||||
|
||||
if traffic.scheme == "https" && !traffic.isSslDecrypted {
|
||||
MetaPill(text: "Encrypted", tint: .orange)
|
||||
}
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
if traffic.isPinned {
|
||||
Image(systemName: "pin.fill")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 22, style: .continuous)
|
||||
.fill(Color(.secondarySystemGroupedBackground))
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 22, style: .continuous)
|
||||
.strokeBorder(Color.primary.opacity(0.05), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
|
||||
private func formatBytes(_ bytes: Int) -> String {
|
||||
@@ -47,4 +85,64 @@ struct TrafficRowView: View {
|
||||
if bytes < 1_048_576 { return String(format: "%.1f KB", Double(bytes) / 1024) }
|
||||
return String(format: "%.1f MB", Double(bytes) / 1_048_576)
|
||||
}
|
||||
|
||||
private var primaryLine: String {
|
||||
let components = URLComponents(string: traffic.url)
|
||||
let path = components?.path ?? traffic.url
|
||||
let query = components?.percentEncodedQuery.map { "?\($0)" } ?? ""
|
||||
let route = path.isEmpty ? traffic.domain : path + query
|
||||
return route.isEmpty ? traffic.url : route
|
||||
}
|
||||
|
||||
private var secondaryLine: String {
|
||||
if let statusText = traffic.statusText, let statusCode = traffic.statusCode {
|
||||
return "\(traffic.domain) • \(statusCode) \(statusText)"
|
||||
}
|
||||
return traffic.domain
|
||||
}
|
||||
|
||||
private var responseSizeText: String {
|
||||
if traffic.responseBodySize > 0 {
|
||||
return formatBytes(traffic.responseBodySize)
|
||||
}
|
||||
if traffic.statusCode == nil {
|
||||
return "Pending"
|
||||
}
|
||||
return "0 B"
|
||||
}
|
||||
|
||||
private func shortContentType(_ contentType: String) -> String {
|
||||
let base = contentType.split(separator: ";").first.map(String.init) ?? contentType
|
||||
return base.replacingOccurrences(of: "application/", with: "")
|
||||
.replacingOccurrences(of: "text/", with: "")
|
||||
}
|
||||
}
|
||||
|
||||
private struct TransferPill: View {
|
||||
let systemImage: String
|
||||
let text: String
|
||||
let tint: Color
|
||||
|
||||
var body: some View {
|
||||
Label(text, systemImage: systemImage)
|
||||
.font(.caption2.weight(.semibold))
|
||||
.foregroundStyle(tint)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
.background(tint.opacity(0.12), in: Capsule())
|
||||
}
|
||||
}
|
||||
|
||||
private struct MetaPill: View {
|
||||
let text: String
|
||||
var tint: Color = .secondary
|
||||
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.font(.caption2.weight(.semibold))
|
||||
.foregroundStyle(tint)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
.background(tint.opacity(0.10), in: Capsule())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user