1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
import SwiftUI
struct BatchResultsView: View {
@Bindable var viewModel: DomainViewModel
let title: String
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack(alignment: .top) {
SectionTitleView(title: title)
Spacer()
if viewModel.batchLookupRunning {
VStack(alignment: .trailing, spacing: 4) {
ProgressView(value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1)))
.tint(.cyan)
.frame(width: 120)
Text(viewModel.batchProgressLabel)
.font(.system(.caption2, design: .monospaced))
.foregroundStyle(.secondary)
}
} else if !viewModel.batchResults.isEmpty {
Text("\(viewModel.batchResults.count) domains")
.font(.system(.caption2, design: .monospaced))
.foregroundStyle(.secondary)
}
}
if viewModel.batchResults.isEmpty {
MessageCardView(text: "No batch results yet", isError: false)
} else {
CardView(allowsHorizontalScroll: false) {
ForEach(viewModel.batchResults) { result in
if let entry = viewModel.historyEntry(for: result) {
NavigationLink {
HistoryDetailView(viewModel: viewModel, entry: entry)
} label: {
BatchResultRowView(result: result)
}
.buttonStyle(.plain)
} else {
BatchResultRowView(result: result)
}
}
}
}
}
}
}
struct BatchResultRowView: View {
let result: BatchLookupResult
var body: some View {
VStack(alignment: .leading, spacing: 6) {
HStack(alignment: .firstTextBaseline, spacing: 8) {
Text(result.domain)
.font(.system(.callout, design: .monospaced))
.foregroundStyle(.primary)
.lineLimit(1)
Spacer(minLength: 8)
Text(result.resultSource.label.lowercased())
.font(.system(.caption2, design: .monospaced))
.foregroundStyle(.secondary)
Text(result.quickStatus)
.font(.system(.caption2, design: .monospaced))
.foregroundStyle(quickStatusColor)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(quickStatusColor.opacity(0.16))
.clipShape(Capsule())
}
HStack(spacing: 10) {
Text(availabilityText)
Text(result.primaryIP ?? "No IP")
Text(result.timestamp.formatted(date: .abbreviated, time: .shortened))
}
.font(.system(.caption2, design: .monospaced))
.foregroundStyle(.secondary)
if let summaryMessage = result.summaryMessage {
Text(summaryMessage)
.font(.system(.caption2, design: .monospaced))
.foregroundStyle(.secondary)
}
if let errorMessage = result.errorMessage {
Text(errorMessage)
.font(.system(.caption2, design: .monospaced))
.foregroundStyle(result.status == .failed ? .red : .secondary)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 4)
}
private var availabilityText: String {
switch result.availability {
case .available:
return "Available"
case .registered:
return "Registered"
case .unknown, .none:
return "Unknown"
}
}
private var quickStatusColor: Color {
switch result.status {
case .pending:
return .secondary
case .running:
return .cyan
case .completed:
if result.changeSeverity == .high || result.certificateWarningLevel == .critical {
return .red
}
if result.changeSeverity == .medium || result.certificateWarningLevel == .warning {
return .yellow
}
if result.quickStatus == "Changed" {
return .blue
}
return .green
case .failed:
return .red
}
}
}
|