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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
use crate::app::model::Model;
use crate::components::common::{
AzureDiscoveryMsg, ComponentId, Msg, NamespaceActivityMsg, QueueActivityMsg,
};
use tuirealm::State;
use tuirealm::terminal::TerminalAdapter;
impl<T> Model<T>
where
T: TerminalAdapter,
{
/// Handle namespace-related messages from the UI
///
/// Processes namespace loading, selection, and cancellation operations.
/// This includes managing the namespace picker UI, handling discovery mode
/// navigation, and coordinating with Azure resource management.
///
/// # Arguments
/// * `msg` - The namespace activity message to process
///
/// # Returns
/// * `Some(Msg)` - Next UI action to take
/// * `None` - No further action needed
pub fn update_namespace(&mut self, msg: NamespaceActivityMsg) -> Option<Msg> {
match msg {
NamespaceActivityMsg::NamespacesLoaded(namespaces) => {
log::info!(
"Received namespaces loaded event with {} namespaces",
namespaces.len()
);
if namespaces.is_empty() {
log::warn!("No namespaces found - showing empty namespace picker");
if let Err(e) = self.remount_namespace_picker(Some(namespaces)) {
self.error_reporter.report_simple(
e,
"NamespaceHandler",
"update_namespace",
);
}
return None;
}
// Check if we should auto-select when there's only one namespace
let should_auto_select =
namespaces.len() == 1 && self.state_manager.should_auto_progress();
if should_auto_select {
log::info!(
"Auto-progression mode: Only one namespace available, automatically selecting: '{}'",
namespaces[0]
);
self.set_selected_namespace(Some(namespaces[0].clone()));
// Mount the namespace picker temporarily to avoid component not found errors
// This ensures the UI state is consistent before proceeding to queue loading
if let Err(e) = self.remount_namespace_picker(Some(namespaces.clone())) {
log::warn!("Failed to mount namespace picker for automatic selection: {e}");
}
log::info!("Proceeding to handle namespace selection automatically");
return self.handle_namespace_selection();
} else if namespaces.len() == 1 {
log::info!(
"Navigation mode: Only one namespace available but forcing picker to allow navigation: '{}'",
namespaces[0]
);
}
// Multiple namespaces - show picker
log::info!("Multiple namespaces found, showing namespace picker");
if let Err(e) = self.remount_namespace_picker(Some(namespaces)) {
self.error_reporter
.report_simple(e, "NamespaceHandler", "update_namespace");
}
None
}
NamespaceActivityMsg::NamespaceSelected => self.handle_namespace_selection(),
NamespaceActivityMsg::NamespaceCancelled => {
// In discovery mode or navigation mode, go back one level in hierarchy
if self.state_manager.navigation_context
== crate::app::managers::state_manager::NavigationContext::DiscoveryMode
{
log::info!(
"Discovery mode: Going back to subscription selection (bypass resource groups)"
);
// Clear all selections to restart from subscription picker
self.state_manager.selected_subscription = None;
self.state_manager.selected_resource_group = None;
self.set_selected_namespace(None);
// Change state to AzureDiscovery before unmounting to avoid rendering issues
self.set_app_state(crate::app::model::AppState::AzureDiscovery);
// Unmount namespace picker
if let Err(e) = self.app.umount(&ComponentId::NamespacePicker) {
log::error!("Failed to unmount namespace picker: {e}");
}
// Go back to subscription selection instead of resource groups
Some(Msg::AzureDiscovery(
AzureDiscoveryMsg::DiscoveringSubscriptions,
))
} else if self.state_manager.selected_subscription.is_some()
&& self.state_manager.selected_resource_group.is_some()
{
log::info!("Discovery mode: Going back to resource group selection");
// Clear selected namespace
self.set_selected_namespace(None);
// Change state to AzureDiscovery before unmounting to avoid rendering issues
self.set_app_state(crate::app::model::AppState::AzureDiscovery);
// Unmount namespace picker
if let Err(e) = self.app.umount(&ComponentId::NamespacePicker) {
log::error!("Failed to unmount namespace picker: {e}");
}
// Go back to resource group selection
if let Some(subscription) = &self.state_manager.selected_subscription {
Some(Msg::AzureDiscovery(
AzureDiscoveryMsg::DiscoveringResourceGroups(subscription.clone()),
))
} else {
log::error!("No subscription selected when going back to resource groups");
None
}
} else {
// Not in discovery mode, just close
log::info!("Not in discovery mode, closing namespace picker");
if let Err(e) = self.app.umount(&ComponentId::NamespacePicker) {
log::error!("Failed to unmount namespace picker: {e}");
}
self.set_quit(true);
Some(Msg::AppClose)
}
}
NamespaceActivityMsg::NamespaceUnselected => {
log::debug!("User navigating back from queue selection");
// Clear selected namespace
self.set_selected_namespace(None);
// Set navigation context to indicate user is navigating from queue selection
self.state_manager.start_queue_navigation();
log::debug!("Navigation context set to QueueNavigation mode");
// Check if we're in discovery mode
if self.state_manager.selected_subscription.is_some()
&& self.state_manager.selected_resource_group.is_some()
&& !self.state_manager.discovered_namespaces.is_empty()
{
// In discovery mode - go back to namespace selection
log::info!("Discovery mode: Going back to namespace selection");
let namespaces: Vec<String> = self
.state_manager
.discovered_namespaces
.iter()
.map(|ns| ns.name.clone())
.collect();
return Some(Msg::NamespaceActivity(
NamespaceActivityMsg::NamespacesLoaded(namespaces),
));
} else {
// Check if we have configuration-based subscription ID
let config = crate::config::get_config_or_panic();
let has_subscription_id = config.azure_ad().subscription_id().is_ok();
if !has_subscription_id && !self.state_manager.discovered_namespaces.is_empty()
{
// Still in discovery mode
log::info!("Using discovered namespaces for namespace picker");
let namespaces: Vec<String> = self
.state_manager
.discovered_namespaces
.iter()
.map(|ns| ns.name.clone())
.collect();
return Some(Msg::NamespaceActivity(
NamespaceActivityMsg::NamespacesLoaded(namespaces),
));
} else {
// Navigation mode - load namespaces from Azure respecting navigation context
self.load_namespaces(self.state_manager.navigation_context.clone());
}
}
None
}
}
}
/// Handle namespace selection by storing the selected namespace and loading queues
fn handle_namespace_selection(&mut self) -> Option<Msg> {
// Try to get namespace from component state first, then fall back to stored state
let namespace = if let Ok(State::One(tuirealm::StateValue::String(ns))) =
self.app.state(&ComponentId::NamespacePicker)
{
log::info!("Selected namespace from component: {ns}");
ns
} else if let Some(stored_namespace) = &self.state_manager.selected_namespace {
log::info!("Using stored namespace: {stored_namespace}");
stored_namespace.clone()
} else {
log::error!("No namespace available in component state or stored state");
return None;
};
log::info!("Processing namespace selection: '{namespace}'");
log::info!(
"Auth method: {}",
crate::config::get_config_or_panic().azure_ad().auth_method
);
// Store the selected namespace first
self.set_selected_namespace(Some(namespace.clone()));
// Check if we're in discovery mode and need to fetch connection string
if self.state_manager.discovered_connection_string.is_none()
&& self.state_manager.selected_subscription.is_some()
&& self.state_manager.selected_resource_group.is_some()
{
// Find the full namespace object
if let Some(_ns) = self
.state_manager
.discovered_namespaces
.iter()
.find(|n| n.name == namespace)
{
let Some(subscription_id) = &self.state_manager.selected_subscription else {
log::error!("No subscription selected for namespace fetch");
return None;
};
let Some(resource_group) = &self.state_manager.selected_resource_group else {
log::error!("No resource group selected for namespace fetch");
return None;
};
return Some(Msg::AzureDiscovery(
AzureDiscoveryMsg::FetchingConnectionString {
subscription_id: subscription_id.clone(),
resource_group: resource_group.clone(),
namespace: namespace.clone(),
},
));
}
}
// Check if we're using discovered resources (no subscription ID in config)
let config = crate::config::get_config_or_panic();
let has_subscription_id = config.azure_ad().has_subscription_id();
log::info!(
"Discovery mode check: has_subscription_id={}, discovered_connection_string={:?}",
has_subscription_id,
self.state_manager.discovered_connection_string.is_some()
);
if !has_subscription_id && self.state_manager.discovered_connection_string.is_some() {
// We're in discovery mode
// Note: We don't unmount the namespace picker here anymore to avoid view errors
// The picker will be unmounted by the queue loading process
// In discovery mode, we can still list queues using the discovered resources
if let (
Some(subscription_id),
Some(resource_group),
Some(namespace),
Some(auth_service),
) = (
&self.state_manager.selected_subscription,
&self.state_manager.selected_resource_group,
&self.state_manager.selected_namespace,
&self.auth_service,
) {
log::info!("Discovery mode: Loading queues for namespace {namespace}");
self.queue_manager.load_queues_with_discovery(
subscription_id.clone(),
resource_group.clone(),
namespace.clone(),
auth_service.clone(),
self.http_client.clone(),
);
} else {
log::warn!("Discovery mode but missing required information to list queues");
return Some(Msg::QueueActivity(QueueActivityMsg::QueuesLoaded(vec![])));
}
} else {
// Normal mode with subscription ID configured
// For all authentication methods, proceed to queue discovery
log::info!("Not in discovery mode - proceeding to load queues");
self.load_queues();
}
None
}
}