summaryrefslogtreecommitdiff
path: root/DomainDig/ContentView.swift
diff options
context:
space:
mode:
Diffstat (limited to 'DomainDig/ContentView.swift')
-rw-r--r--DomainDig/ContentView.swift133
1 files changed, 133 insertions, 0 deletions
diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift
index 3716f88..5a469e1 100644
--- a/DomainDig/ContentView.swift
+++ b/DomainDig/ContentView.swift
@@ -11,6 +11,7 @@ enum LookupInputMode: String, CaseIterable, Identifiable {
enum ResultSection: String, Hashable {
case domain
+ case intelligence
case ownership
case dns
case web
@@ -78,6 +79,10 @@ struct ContentView: View {
.padding(.top, appDensity.metrics.cardSpacing)
}
}
+ if let report = viewModel.currentReport {
+ intelligenceSection(report: report)
+ .padding(.top, appDensity.metrics.sectionSpacing)
+ }
domainOverviewSection
.padding(.top, appDensity.metrics.sectionSpacing)
ownershipSection
@@ -415,6 +420,14 @@ struct ContentView: View {
)
}
+ private func intelligenceSection(report: DomainReport) -> some View {
+ IntelligenceSectionView(
+ isCollapsed: sectionCollapsedBinding(.intelligence),
+ report: report,
+ showsPlaceholder: FeatureAccessService.currentTier != .proPlus
+ )
+ }
+
private var ownershipSection: some View {
OwnershipSectionView(
isCollapsed: sectionCollapsedBinding(.ownership),
@@ -1558,6 +1571,126 @@ struct OwnershipSectionView: View {
}
}
+struct IntelligenceSectionView: View {
+ @Environment(\.appDensity) private var appDensity
+ @Binding var isCollapsed: Bool
+ let report: DomainReport
+ let showsPlaceholder: Bool
+
+ var body: some View {
+ CollapsibleSectionView(title: "Data+ Intelligence", isCollapsed: $isCollapsed) {
+ CardView(allowsHorizontalScroll: false) {
+ if showsPlaceholder {
+ MessageRowView(text: "Richer intelligence history, hosting analysis, and risk signals are available in Pro+", isError: false)
+ } else {
+ if let provider = report.inferredProvider {
+ intelligenceBlock(title: "Infrastructure") {
+ LabeledValueRow(row: .init(label: "Provider", value: provider.name, tone: .primary))
+ if !provider.evidence.isEmpty {
+ MessageRowView(text: provider.evidence.joined(separator: " • "), isError: false)
+ }
+ if !report.priorProviders.isEmpty {
+ LabeledValueRow(row: .init(label: "Prior", value: report.priorProviders.joined(separator: ", "), tone: .secondary))
+ }
+ }
+ }
+ if let classification = report.domainClassification {
+ intelligenceBlock(title: "Classification") {
+ LabeledValueRow(row: .init(label: "Purpose", value: classification.kind.title, tone: .primary))
+ MessageRowView(text: classification.reasons.joined(separator: " • "), isError: false)
+ }
+ }
+ intelligenceBlock(title: "Risk Signals") {
+ if report.riskSignals.isEmpty {
+ MessageRowView(text: "No material historical risk signals detected", isError: false)
+ } else {
+ ForEach(report.riskSignals.prefix(4)) { signal in
+ VStack(alignment: .leading, spacing: 3) {
+ Text(signal.title)
+ .font(appDensity.font(.caption, weight: .semibold))
+ Text(signal.detail)
+ .font(appDensity.font(.caption2))
+ .foregroundStyle(.secondary)
+ }
+ }
+ }
+ }
+ intelligenceBlock(title: "Ownership History") {
+ if report.ownershipTransitions.isEmpty {
+ MessageRowView(text: "No ownership transitions observed locally", isError: false)
+ } else {
+ ForEach(report.ownershipTransitions.prefix(4)) { event in
+ intelligenceEventRow(date: event.date, title: event.summary)
+ }
+ }
+ }
+ intelligenceBlock(title: "Hosting History") {
+ if report.hostingTransitions.isEmpty {
+ MessageRowView(text: "No hosting transitions observed locally", isError: false)
+ } else {
+ ForEach(report.hostingTransitions.prefix(4)) { event in
+ intelligenceEventRow(date: event.date, title: event.summary)
+ }
+ }
+ }
+ intelligenceBlock(title: "Subdomain Intelligence") {
+ if report.subdomainHistory.isEmpty {
+ MessageRowView(text: "No subdomain history available", isError: false)
+ } else {
+ ForEach(report.subdomainHistory.prefix(5)) { item in
+ VStack(alignment: .leading, spacing: 3) {
+ HStack {
+ Text(item.hostname)
+ .font(appDensity.font(.caption))
+ Spacer()
+ if item.isEphemeral {
+ Text("Ephemeral")
+ .font(appDensity.font(.caption2))
+ .foregroundStyle(.yellow)
+ }
+ }
+ Text("First \(item.firstSeen.formatted(date: .abbreviated, time: .omitted)) • Last \(item.lastSeen.formatted(date: .abbreviated, time: .omitted)) • Seen \(item.recurrenceCount)x")
+ .font(appDensity.font(.caption2))
+ .foregroundStyle(.secondary)
+ }
+ }
+ }
+ }
+ intelligenceBlock(title: "Timeline") {
+ if report.intelligenceTimeline.isEmpty {
+ MessageRowView(text: "No inferred intelligence events yet", isError: false)
+ } else {
+ ForEach(report.intelligenceTimeline.prefix(5)) { event in
+ intelligenceEventRow(date: event.date, title: "\(event.title): \(event.detail)")
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @ViewBuilder
+ private func intelligenceBlock<Content: View>(title: String, @ViewBuilder content: () -> Content) -> some View {
+ VStack(alignment: .leading, spacing: 8) {
+ Text(title)
+ .font(appDensity.font(.subheadline, weight: .semibold))
+ .foregroundStyle(.cyan)
+ content()
+ }
+ }
+
+ private func intelligenceEventRow(date: Date, title: String) -> some View {
+ VStack(alignment: .leading, spacing: 3) {
+ Text(date.formatted(date: .abbreviated, time: .omitted))
+ .font(appDensity.font(.caption2))
+ .foregroundStyle(.secondary)
+ Text(title)
+ .font(appDensity.font(.caption))
+ }
+ }
+}
+
struct SubdomainsSectionView: View {
@Environment(\.appDensity) private var appDensity
@Binding var isCollapsed: Bool