Skip to content

Commit 5dee188

Browse files
committed
refactor: extract network browser submodels
1 parent 7c629bc commit 5dee188

9 files changed

Lines changed: 540 additions & 456 deletions

File tree

internal/app/app.go

Lines changed: 16 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -123,43 +123,6 @@ type Model struct {
123123
// SSM session state
124124
selectedInstance *awsservice.EC2Instance
125125

126-
// VPC browser state
127-
vpcs []awsservice.VPC
128-
filteredVPCs []awsservice.VPC
129-
vpcIdx int
130-
subnets []awsservice.Subnet
131-
filteredSubnets []awsservice.Subnet
132-
subnetIdx int
133-
selectedVPC *awsservice.VPC
134-
selectedSubnet *awsservice.Subnet
135-
availableIPs []string
136-
filteredIPs []string
137-
ipScrollOffset int
138-
ipFilter string
139-
ipFilterActive bool
140-
reachabilityRegions []string
141-
filteredReachabilityRegions []string
142-
reachabilityRegion string
143-
reachabilityRegionIdx int
144-
reachabilityRegionFilter string
145-
reachabilityRegionFiltering bool
146-
reachabilityTargets []awsservice.ReachabilityTarget
147-
filteredReachabilityTargets []awsservice.ReachabilityTarget
148-
reachabilitySourceTypes []string
149-
reachabilitySourceTypeIdx int
150-
reachabilityDestTypes []string
151-
reachabilityDestTypeIdx int
152-
reachabilityIdx int
153-
reachabilityFilter string
154-
reachabilityFilterActive bool
155-
reachabilitySource *awsservice.ReachabilityTarget
156-
reachabilityDestination *awsservice.ReachabilityTarget
157-
reachabilityDestinationIP string
158-
reachabilityProtocolIdx int
159-
reachabilityPortInput string
160-
reachabilityConfigField int
161-
reachabilityResult *awsservice.ReachabilityAnalysisResult
162-
reachabilityScrollOffset int
163126
// Security Group browser state
164127
securityGroups []awsservice.SecurityGroup
165128
filteredSecurityGroups []awsservice.SecurityGroup
@@ -207,16 +170,18 @@ type Model struct {
207170
eksNodeGroupScroll int
208171

209172
// Feature submodels
210-
ec2Browser ec2InstanceBrowserModel
211-
cwMetrics cloudWatchMetricsModel
212-
cwLogs cloudWatchLogsModel
213-
rds rdsModel
214-
route53 route53Model
215-
iam iamModel
216-
bedrock bedrockModel
217-
secrets secretsModel
218-
s3 s3Model
219-
lambda lambdaModel
173+
ec2Browser ec2InstanceBrowserModel
174+
vpc vpcModel
175+
reachability reachabilityModel
176+
cwMetrics cloudWatchMetricsModel
177+
cwLogs cloudWatchLogsModel
178+
rds rdsModel
179+
route53 route53Model
180+
iam iamModel
181+
bedrock bedrockModel
182+
secrets secretsModel
183+
s3 s3Model
184+
lambda lambdaModel
220185

221186
// Inspector browser state
222187
inspectorWorkflows []inspector.Workflow
@@ -316,6 +281,8 @@ func New(cfg *config.Config, configPath string, version string, checklistPath ..
316281
contextTable: newContextTable(),
317282
}
318283
model.ec2Browser = newEC2InstanceBrowserModel()
284+
model.vpc = newVPCModel()
285+
model.reachability = newReachabilityModel()
319286
model.cwMetrics = newCloudWatchMetricsModel()
320287
model.cwLogs = newCloudWatchLogsModel()
321288
model.rds = newRDSModel()
@@ -491,22 +458,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
491458
return m.updateFeatureList(msg)
492459
case screenInstanceList:
493460
return m.updateInstanceList(msg)
494-
case screenVPCList:
495-
return m.updateVPCList(msg)
496-
case screenSubnetList:
497-
return m.updateSubnetList(msg)
498-
case screenSubnetDetail:
499-
return m.updateSubnetDetail(msg)
500-
case screenReachabilityRegionList:
501-
return m.updateReachabilityRegionList(msg)
502-
case screenReachabilitySourceList:
503-
return m.updateReachabilitySourceList(msg)
504-
case screenReachabilityDestinationList:
505-
return m.updateReachabilityDestinationList(msg)
506-
case screenReachabilityConfig:
507-
return m.updateReachabilityConfig(msg)
508-
case screenReachabilityResult:
509-
return m.updateReachabilityResult(msg)
510461
case screenInspectorHome:
511462
return m.updateInspectorHome(msg)
512463
case screenInspectorWorkflowPlaceholder:
@@ -624,27 +575,9 @@ func (m Model) updateFeatureList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
624575
case domain.FeatureEC2InstanceBrowser:
625576
return m.ec2Browser.Start(&m)
626577
case domain.FeatureVPCBrowser:
627-
return m.startLoading(m.loadVPCs())
578+
return m.vpc.Start(&m)
628579
case domain.FeatureReachabilityAnalyzer:
629-
m.reachabilityRegions = availableReachabilityRegions(m.cfg.Region)
630-
m.filteredReachabilityRegions = m.reachabilityRegions
631-
m.reachabilityRegion = m.cfg.Region
632-
m.reachabilityRegionIdx = indexOfString(m.reachabilityRegions, m.reachabilityRegion)
633-
if m.reachabilityRegionIdx < 0 {
634-
m.reachabilityRegionIdx = 0
635-
}
636-
m.reachabilityRegionFilter = ""
637-
m.reachabilityRegionFiltering = false
638-
m.reachabilityTargets = nil
639-
m.filteredReachabilityTargets = nil
640-
m.reachabilitySource = nil
641-
m.reachabilityDestination = nil
642-
m.reachabilityDestinationIP = ""
643-
m.reachabilityResult = nil
644-
m.reachabilityScrollOffset = 0
645-
m.awsRepo = nil
646-
m.screen = screenReachabilityRegionList
647-
return m, nil
580+
return m.reachability.Start(&m)
648581
case domain.FeatureRDSBrowser:
649582
return m.rds.Start(&m)
650583
case domain.FeatureRoute53Browser:
@@ -713,22 +646,6 @@ func (m Model) View() string {
713646
v = m.viewFeatureList()
714647
case screenInstanceList:
715648
v = m.viewInstanceList()
716-
case screenVPCList:
717-
v = m.viewVPCList()
718-
case screenSubnetList:
719-
v = m.viewSubnetList()
720-
case screenSubnetDetail:
721-
v = m.viewSubnetDetail()
722-
case screenReachabilityRegionList:
723-
v = m.viewReachabilityRegionList()
724-
case screenReachabilitySourceList:
725-
v = m.viewReachabilitySourceList()
726-
case screenReachabilityDestinationList:
727-
v = m.viewReachabilityDestinationList()
728-
case screenReachabilityConfig:
729-
v = m.viewReachabilityConfig()
730-
case screenReachabilityResult:
731-
v = m.viewReachabilityResult()
732649
case screenInspectorHome:
733650
v = m.viewInspectorHome()
734651
case screenInspectorWorkflowPlaceholder:

internal/app/app_test.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -486,15 +486,15 @@ func TestReachabilityFeatureOpensRegionSelection(t *testing.T) {
486486
if model.screen != screenReachabilityRegionList {
487487
t.Fatalf("expected region selection screen, got %v", model.screen)
488488
}
489-
if model.reachabilityRegion != "us-east-1" {
490-
t.Fatalf("expected default reachability region us-east-1, got %q", model.reachabilityRegion)
489+
if model.reachability.region != "us-east-1" {
490+
t.Fatalf("expected default reachability region us-east-1, got %q", model.reachability.region)
491491
}
492492
}
493493

494494
func TestReachabilityStatusBarUsesOverrideRegion(t *testing.T) {
495495
m := New(testConfig(), "", "dev")
496496
m.screen = screenReachabilitySourceList
497-
m.reachabilityRegion = "ap-northeast-2"
497+
m.reachability.region = "ap-northeast-2"
498498

499499
bar := m.renderStatusBar()
500500
if !strings.Contains(bar, "region:ap-northeast-2") {
@@ -511,19 +511,19 @@ func TestReachabilityTargetsLoadedBuildsSourceTypeFilter(t *testing.T) {
511511
},
512512
}
513513

514-
updated, _, handled := m.handleEC2VPCMsg(msg)
514+
updated, _, handled := m.reachability.HandleMessage(&m, msg)
515515
if !handled {
516516
t.Fatal("expected message to be handled")
517517
}
518518
model := updated.(Model)
519-
if got := strings.Join(model.reachabilitySourceTypes, ","); got != "EC2 instances,Network interfaces" {
519+
if got := strings.Join(model.reachability.sourceTypes, ","); got != "EC2 instances,Network interfaces" {
520520
t.Fatalf("unexpected source types: %q", got)
521521
}
522-
if len(model.filteredReachabilityTargets) != 1 {
523-
t.Fatalf("expected only EC2 instances to be visible initially, got %d", len(model.filteredReachabilityTargets))
522+
if len(model.reachability.filteredTargets) != 1 {
523+
t.Fatalf("expected only EC2 instances to be visible initially, got %d", len(model.reachability.filteredTargets))
524524
}
525-
if model.filteredReachabilityTargets[0].Type != "EC2 instances" {
526-
t.Fatalf("expected EC2 instances to be prioritized, got %+v", model.filteredReachabilityTargets)
525+
if model.reachability.filteredTargets[0].Type != "EC2 instances" {
526+
t.Fatalf("expected EC2 instances to be prioritized, got %+v", model.reachability.filteredTargets)
527527
}
528528
}
529529

@@ -2572,7 +2572,7 @@ func TestCWLogStreamsLoadedAppendExtendsExistingList(t *testing.T) {
25722572

25732573
func TestReachabilityResultLinesUseReadableSections(t *testing.T) {
25742574
m := New(testConfig(), "", "dev")
2575-
m.reachabilityResult = &awsservice.ReachabilityAnalysisResult{
2575+
m.reachability.result = &awsservice.ReachabilityAnalysisResult{
25762576
Status: "failed",
25772577
NetworkPathFound: false,
25782578
Source: awsservice.ReachabilityTarget{Name: "src", ID: "eni-1"},
@@ -2587,7 +2587,7 @@ func TestReachabilityResultLinesUseReadableSections(t *testing.T) {
25872587
},
25882588
}
25892589

2590-
lines := m.reachabilityResultLines()
2590+
lines := m.reachability.resultLines(m)
25912591
rendered := strings.Join(lines, "\n")
25922592
if !strings.Contains(rendered, "Summary") {
25932593
t.Fatalf("expected Summary section, got %q", rendered)
@@ -2602,13 +2602,13 @@ func TestReachabilityResultLinesUseReadableSections(t *testing.T) {
26022602

26032603
func TestReachabilityLoadingDetailsShowSourceAndDestination(t *testing.T) {
26042604
m := New(testConfig(), "", "dev")
2605-
m.reachabilityRegion = "ap-northeast-2"
2606-
m.reachabilitySource = &awsservice.ReachabilityTarget{Name: "source-eni", ID: "eni-1"}
2607-
m.reachabilityDestination = &awsservice.ReachabilityTarget{Name: "dest-eni", ID: "eni-2"}
2608-
m.reachabilityProtocolIdx = 0
2609-
m.reachabilityPortInput = "443"
2605+
m.reachability.region = "ap-northeast-2"
2606+
m.reachability.source = &awsservice.ReachabilityTarget{Name: "source-eni", ID: "eni-1"}
2607+
m.reachability.destination = &awsservice.ReachabilityTarget{Name: "dest-eni", ID: "eni-2"}
2608+
m.reachability.protocolIdx = 0
2609+
m.reachability.portInput = "443"
26102610

2611-
details := m.reachabilityLoadingDetails()
2611+
details := m.reachability.loadingDetails(m)
26122612
if len(details) < 4 {
26132613
t.Fatalf("expected vertical loading details, got %#v", details)
26142614
}
@@ -2635,10 +2635,10 @@ func TestReachabilityLoadingDetailsShowSourceAndDestination(t *testing.T) {
26352635
func TestReachabilityLoadingDetailsTruncateLongLabelsForNarrowWidth(t *testing.T) {
26362636
m := New(testConfig(), "", "dev")
26372637
m.width = 30
2638-
m.reachabilitySource = &awsservice.ReachabilityTarget{Name: strings.Repeat("source-", 8), ID: "eni-1"}
2639-
m.reachabilityDestination = &awsservice.ReachabilityTarget{Name: strings.Repeat("dest-", 8), ID: "eni-2"}
2638+
m.reachability.source = &awsservice.ReachabilityTarget{Name: strings.Repeat("source-", 8), ID: "eni-1"}
2639+
m.reachability.destination = &awsservice.ReachabilityTarget{Name: strings.Repeat("dest-", 8), ID: "eni-2"}
26402640

2641-
details := m.reachabilityLoadingDetails()
2641+
details := m.reachability.loadingDetails(m)
26422642
if !strings.Contains(details[1], "…") {
26432643
t.Fatalf("expected truncated source label, got %#v", details)
26442644
}

internal/app/feature_submodel.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ type featureSubmodel interface {
1010
}
1111

1212
func (m *Model) featureSubmodels() []featureSubmodel {
13-
return []featureSubmodel{&m.ec2Browser, &m.cwMetrics, &m.cwLogs, &m.rds, &m.route53, &m.iam, &m.bedrock, &m.secrets, &m.s3, &m.lambda}
13+
return []featureSubmodel{&m.ec2Browser, &m.vpc, &m.reachability, &m.cwMetrics, &m.cwLogs, &m.rds, &m.route53, &m.iam, &m.bedrock, &m.secrets, &m.s3, &m.lambda}
1414
}

internal/app/filter.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,6 @@ func (m *Model) applyFilterTarget(target filterTarget) {
165165
case filterInstances:
166166
m.filtered = applyFilter(m.instances, m.filterValue(target))
167167
m.instIdx = 0
168-
case filterSubnetIPs:
169-
m.applyIPFilter()
170168
case filterSecurityGroups:
171169
m.filteredSecurityGroups = applyFilter(m.securityGroups, m.filterValue(target))
172170
m.sgIdx = 0
@@ -186,12 +184,6 @@ func (m *Model) applyFilterTarget(target filterTarget) {
186184
m.filteredCtxList = applyFilter(m.ctxList, m.filterValue(target))
187185
m.ctxIdx = 0
188186
m.syncContextTable()
189-
case filterVPCs:
190-
m.filteredVPCs = applyFilter(m.vpcs, m.filterValue(target))
191-
m.vpcIdx = 0
192-
case filterSubnets:
193-
m.filteredSubnets = applyFilter(m.subnets, m.filterValue(target))
194-
m.subnetIdx = 0
195187
case filterInspectorChecklistFiles:
196188
m.filteredChecklistFiles = applyFilter(m.inspectorChecklistFiles, m.filterValue(target))
197189
m.inspectorChecklistFileIdx = 0

internal/app/help.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,13 @@ func (m Model) helpModeShortcuts() []helpShortcut {
9696
}
9797
shortcuts = append(shortcuts, helpShortcut{"esc", "Close filter mode"})
9898
return shortcuts
99-
case m.screen == screenReachabilityRegionList && m.reachabilityRegionFiltering:
99+
case m.screen == screenReachabilityRegionList && m.reachability.regionFiltering:
100100
return []helpShortcut{
101101
{"type", "Update the region filter"},
102102
{"backspace", "Delete the previous character"},
103103
{"enter / esc", "Close region filter mode"},
104104
}
105-
case (m.screen == screenReachabilitySourceList || m.screen == screenReachabilityDestinationList) && m.reachabilityFilterActive:
105+
case (m.screen == screenReachabilitySourceList || m.screen == screenReachabilityDestinationList) && m.reachability.filterActive:
106106
return []helpShortcut{
107107
{"type", "Update the target filter"},
108108
{"backspace", "Delete the previous character"},

internal/app/screen_ec2.go

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,53 +19,6 @@ func (m Model) handleEC2VPCMsg(msg tea.Msg) (tea.Model, tea.Cmd, bool) {
1919
m.screen = screenInstanceList
2020
return m, nil, true
2121

22-
case vpcsLoadedMsg:
23-
m.vpcs = msg.vpcs
24-
m.resetFilter(filterVPCs)
25-
m.vpcIdx = 0
26-
m.screen = screenVPCList
27-
return m, nil, true
28-
29-
case subnetsLoadedMsg:
30-
m.subnets = msg.subnets
31-
m.resetFilter(filterSubnets)
32-
m.subnetIdx = 0
33-
m.screen = screenSubnetList
34-
return m, nil, true
35-
36-
case availableIPsLoadedMsg:
37-
m.availableIPs = msg.ips
38-
m.resetFilter(filterSubnetIPs)
39-
m.screen = screenSubnetDetail
40-
return m, nil, true
41-
42-
case reachabilityTargetsLoadedMsg:
43-
m.reachabilityTargets = msg.targets
44-
m.reachabilitySourceTypes = buildReachabilityTargetTypes(msg.targets, false)
45-
m.reachabilitySourceTypeIdx = 0
46-
m.reachabilityDestTypes = nil
47-
m.reachabilityDestTypeIdx = 0
48-
m.filteredReachabilityTargets = applyReachabilityTargetFilter(msg.targets, m.selectedReachabilitySourceType(), "")
49-
m.reachabilityIdx = 0
50-
m.reachabilityFilter = ""
51-
m.reachabilityFilterActive = false
52-
m.reachabilitySource = nil
53-
m.reachabilityDestination = nil
54-
m.reachabilityDestinationIP = ""
55-
m.reachabilityProtocolIdx = 0
56-
m.reachabilityPortInput = "443"
57-
m.reachabilityConfigField = 0
58-
m.reachabilityResult = nil
59-
m.reachabilityScrollOffset = 0
60-
m.screen = screenReachabilitySourceList
61-
return m, nil, true
62-
63-
case reachabilityAnalysisLoadedMsg:
64-
m.reachabilityResult = msg.result
65-
m.reachabilityScrollOffset = 0
66-
m.screen = screenReachabilityResult
67-
return m, nil, true
68-
6922
case ssmSessionDoneMsg:
7023
if msg.err != nil {
7124
m.errMsg = msg.err.Error()

0 commit comments

Comments
 (0)