summaryrefslogtreecommitdiff
path: root/DomainDig/ContentView.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-22 10:14:30 -0500
committerChristian Cleberg <[email protected]>2026-04-22 10:14:30 -0500
commit9384a668bf36f23f71e25d05b490d1040d308845 (patch)
tree6681f4b262a9414c7fc96148652599215b7973fc /DomainDig/ContentView.swift
parent7b41195620eadd54d25fa98dcd36735cba906ba3 (diff)
downloaddomain-dig-9384a668bf36f23f71e25d05b490d1040d308845.tar.gz
domain-dig-9384a668bf36f23f71e25d05b490d1040d308845.tar.bz2
domain-dig-9384a668bf36f23f71e25d05b490d1040d308845.zip
feat(v2.8.0): add workflows and repeatable inspection automation
* introduce DomainWorkflow model and persistence * add WorkflowsView and execution flow * support batch inspection via workflows * add workflow-based export (TXT/CSV/JSON) * enable bulk add from history/watchlist * add quick actions for common operations * integrate workflows with tracking and history
Diffstat (limited to 'DomainDig/ContentView.swift')
-rw-r--r--DomainDig/ContentView.swift112
1 files changed, 112 insertions, 0 deletions
diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift
index ed2da49..b023547 100644
--- a/DomainDig/ContentView.swift
+++ b/DomainDig/ContentView.swift
@@ -18,6 +18,10 @@ enum ResultSection: String, Hashable {
case subdomains
}
+private struct WorkflowNavigationTarget: Hashable {
+ let workflowID: UUID
+}
+
struct ContentView: View {
@Environment(\.appDensity) private var appDensity
@State private var viewModel = DomainViewModel()
@@ -30,6 +34,8 @@ struct ContentView: View {
@State private var showTrackLimitAlert = false
@State private var inputMode: LookupInputMode = .single
@State private var collapsedSections: Set<ResultSection> = [.network]
+ @State private var showingCurrentDomainWorkflowSheet = false
+ @State private var showingBatchWorkflowSheet = false
var body: some View {
NavigationStack(path: $navigationPath) {
@@ -68,6 +74,7 @@ struct ContentView: View {
confidence: viewModel.currentSnapshot.availabilityConfidence,
snapshotNote: viewModel.currentSnapshot.note,
trackedDomain: viewModel.currentTrackedDomain,
+ workflows: viewModel.currentDomainWorkflows,
trackingLimitMessage: viewModel.trackingLimitMessage,
onTrack: {
if !viewModel.trackCurrentDomain() {
@@ -82,6 +89,15 @@ struct ContentView: View {
guard let trackedDomain = viewModel.currentTrackedDomain else { return }
trackingNoteDraft = trackedDomain.note ?? ""
editingTrackedDomain = trackedDomain
+ },
+ onAddToWorkflow: {
+ showingCurrentDomainWorkflowSheet = true
+ },
+ onOpenWorkflow: { workflow in
+ navigationPath.append(WorkflowNavigationTarget(workflowID: workflow.id))
+ },
+ onRunWorkflow: { workflow in
+ viewModel.rerunCurrentDomain(in: workflow)
}
)
.padding(.top, appDensity.metrics.sectionSpacing)
@@ -225,6 +241,12 @@ struct ContentView: View {
}
NavigationLink {
+ WorkflowsView(viewModel: viewModel)
+ } label: {
+ Label("Workflows", systemImage: "square.stack.3d.down.right")
+ }
+
+ NavigationLink {
SettingsView(viewModel: viewModel)
} label: {
Label("Settings", systemImage: "gearshape")
@@ -235,6 +257,9 @@ struct ContentView: View {
}
}
}
+ .navigationDestination(for: WorkflowNavigationTarget.self) { target in
+ WorkflowDetailView(viewModel: viewModel, workflowID: target.workflowID)
+ }
}
.onAppear {
domainFieldFocused = true
@@ -277,6 +302,20 @@ struct ContentView: View {
}
}
}
+ .sheet(isPresented: $showingCurrentDomainWorkflowSheet) {
+ WorkflowBulkAddSheet(
+ viewModel: viewModel,
+ title: "Add Domain to Workflow",
+ availableDomains: [viewModel.searchedDomain]
+ )
+ }
+ .sheet(isPresented: $showingBatchWorkflowSheet) {
+ WorkflowBulkAddSheet(
+ viewModel: viewModel,
+ title: "Add Batch Domains",
+ availableDomains: viewModel.batchResults.map(\.domain)
+ )
+ }
}
private var inputSection: some View {
@@ -353,6 +392,30 @@ struct ContentView: View {
HStack {
Spacer()
if viewModel.resultsLoaded {
+ Menu {
+ if !viewModel.isCurrentDomainTracked {
+ Button("Track this domain") {
+ if !viewModel.trackCurrentDomain() {
+ showTrackLimitAlert = true
+ }
+ }
+ }
+ Button("Add to workflow") {
+ showingCurrentDomainWorkflowSheet = true
+ }
+ Button("Copy report JSON") {
+ guard let json = viewModel.exportJSONString() else { return }
+ AppClipboard.copy(json)
+ AppHaptics.copy()
+ }
+ Button("Export report") {
+ shareSingleResults(format: .json)
+ }
+ } label: {
+ Image(systemName: "bolt.circle")
+ .font(appDensity.font(.body, design: .default))
+ .foregroundStyle(.secondary)
+ }
Button {
viewModel.toggleSavedDomain()
} label: {
@@ -392,6 +455,10 @@ struct ContentView: View {
}
if !viewModel.currentBatchResultEntries.isEmpty {
Menu {
+ Button("Add to Workflow") {
+ showingBatchWorkflowSheet = true
+ }
+ Divider()
Button("Export Batch TXT") {
shareBatchResults(format: .text)
}
@@ -982,10 +1049,14 @@ struct DomainSectionView: View {
let confidence: ConfidenceLevel?
let snapshotNote: String?
let trackedDomain: TrackedDomain?
+ let workflows: [DomainWorkflow]
let trackingLimitMessage: String?
let onTrack: () -> Void
let onTogglePinned: () -> Void
let onEditNote: (() -> Void)?
+ let onAddToWorkflow: (() -> Void)?
+ let onOpenWorkflow: ((DomainWorkflow) -> Void)?
+ let onRunWorkflow: ((DomainWorkflow) -> Void)?
var body: some View {
CollapsibleSectionView(title: "Domain", isCollapsed: $isCollapsed) {
@@ -1032,6 +1103,47 @@ struct DomainSectionView: View {
MessageRowView(text: trackingLimitMessage, isError: false)
.padding(.top, 4)
}
+ if let onAddToWorkflow {
+ Button {
+ onAddToWorkflow()
+ } label: {
+ Label("Add to workflow", systemImage: "plus.rectangle.on.folder")
+ .font(appDensity.font(.caption))
+ }
+ .buttonStyle(.bordered)
+ .padding(.top, 4)
+ }
+ if !workflows.isEmpty {
+ VStack(alignment: .leading, spacing: 8) {
+ Text("Part of workflow")
+ .font(appDensity.font(.caption))
+ .foregroundStyle(.secondary)
+
+ ForEach(workflows) { workflow in
+ HStack {
+ Text(workflow.name)
+ .font(appDensity.font(.caption))
+ .foregroundStyle(.primary)
+ Spacer()
+ if let onOpenWorkflow {
+ Button("Open") {
+ onOpenWorkflow(workflow)
+ }
+ .buttonStyle(.bordered)
+ .font(appDensity.font(.caption2))
+ }
+ if let onRunWorkflow {
+ Button("Run") {
+ onRunWorkflow(workflow)
+ }
+ .buttonStyle(.bordered)
+ .font(appDensity.font(.caption2))
+ }
+ }
+ }
+ }
+ .padding(.top, 4)
+ }
if availabilityLoading {
ProgressView("Checking availability…")
.appLoadingStyle()