summaryrefslogtreecommitdiff
path: root/DomainDig/ContentView.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-21 22:08:09 -0500
committerChristian Cleberg <[email protected]>2026-04-21 22:08:09 -0500
commit22ecca12b4fe0e0850401754c674632f322d919d (patch)
tree52845c3020c2471d607e7a1ad9681de83cbcbb85 /DomainDig/ContentView.swift
parent1f58092dcb67cded75a850db364f3b46d86181f3 (diff)
downloaddomain-dig-22ecca12b4fe0e0850401754c674632f322d919d.tar.gz
domain-dig-22ecca12b4fe0e0850401754c674632f322d919d.tar.bz2
domain-dig-22ecca12b4fe0e0850401754c674632f322d919d.zip
feat(v2.3.0): add ownership intelligence and subdomain discovery
* implement RDAP-based ownership section * add ownership diffing and change classification * add passive subdomain discovery via certificate transparency * highlight interesting subdomains * introduce Data+ scaffolding for future features * include ownership and subdomains in export and history
Diffstat (limited to 'DomainDig/ContentView.swift')
-rw-r--r--DomainDig/ContentView.swift100
1 files changed, 100 insertions, 0 deletions
diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift
index 2be81b3..261b7ca 100644
--- a/DomainDig/ContentView.swift
+++ b/DomainDig/ContentView.swift
@@ -60,6 +60,20 @@ struct ContentView: View {
}
)
.padding(.top, 16)
+ OwnershipSectionView(
+ rows: viewModel.ownershipRows,
+ loading: viewModel.ownershipLoading,
+ error: viewModel.ownershipError,
+ showsHistoryPlaceholder: !DataAccessService.hasAccess(to: .ownershipHistory)
+ )
+ .padding(.top, 16)
+ SubdomainsSectionView(
+ rows: viewModel.subdomainRows,
+ loading: viewModel.subdomainsLoading,
+ error: viewModel.subdomainsError,
+ showsExtendedPlaceholder: !DataAccessService.hasAccess(to: .extendedSubdomains)
+ )
+ .padding(.top, 16)
if !viewModel.currentDiffSections.isEmpty {
DomainDiffView(
title: "Latest Changes",
@@ -772,6 +786,92 @@ struct DomainSectionView: View {
}
}
+struct OwnershipSectionView: View {
+ let rows: [InfoRowViewData]
+ let loading: Bool
+ let error: String?
+ let showsHistoryPlaceholder: Bool
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 12) {
+ SectionTitleView(title: "Ownership")
+ CardView(allowsHorizontalScroll: false) {
+ if loading {
+ ProgressView("Fetching RDAP ownership…")
+ .appLoadingStyle()
+ } else {
+ ForEach(rows) { row in
+ LabeledValueRow(row: row)
+ }
+ if let error, rows.allSatisfy({ $0.value == "Unavailable" }) {
+ MessageRowView(text: error, isError: error != "Unavailable")
+ .padding(.top, 4)
+ }
+ if showsHistoryPlaceholder {
+ MessageRowView(text: "Ownership history (coming soon)", isError: false)
+ .padding(.top, 4)
+ }
+ }
+ }
+ }
+ }
+}
+
+struct SubdomainsSectionView: View {
+ let rows: [SubdomainRowViewData]
+ let loading: Bool
+ let error: String?
+ let showsExtendedPlaceholder: Bool
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 12) {
+ HStack {
+ SectionTitleView(title: "Subdomains")
+ Spacer()
+ Text("\(rows.count)")
+ .font(.system(.caption2, design: .monospaced))
+ .foregroundStyle(.secondary)
+ }
+
+ CardView(allowsHorizontalScroll: false) {
+ if loading {
+ ProgressView("Checking certificate transparency…")
+ .appLoadingStyle()
+ } else if rows.isEmpty {
+ MessageRowView(text: error ?? "No passive subdomains found", isError: false)
+ if showsExtendedPlaceholder {
+ MessageRowView(text: "Extended subdomain discovery (Data+)", isError: false)
+ .padding(.top, 4)
+ }
+ } else {
+ ForEach(rows) { row in
+ HStack(spacing: 8) {
+ Text(row.hostname)
+ .font(.system(.caption, design: .monospaced))
+ .foregroundStyle(.primary)
+ .textSelection(.enabled)
+ Spacer()
+ if row.isInteresting {
+ Text("Interesting")
+ .font(.system(.caption2, design: .monospaced))
+ .foregroundStyle(.yellow)
+ .padding(.horizontal, 8)
+ .padding(.vertical, 4)
+ .background(Color.yellow.opacity(0.14))
+ .clipShape(Capsule())
+ }
+ }
+ }
+ if showsExtendedPlaceholder {
+ MessageRowView(text: "Extended subdomain discovery (Data+)", isError: false)
+ .padding(.top, 4)
+ }
+ }
+ }
+ }
+ }
+}
+
struct DNSSectionView: View {
let dnssecLabel: String?
let sections: [DNSRecordSectionViewData]