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
//! Configuration-template commands — `:config-save`, `:config-delete`,
//! `:config-apply`, `:config-inspect`. Each routes through either an
//! existing `spawn_config_*` helper (delete / apply / inspect) or
//! issues a direct `create_config_template` call with audit + pending
//! plumbing (save).
//!
//! Ninth slice of the `execute_command` split. Same parent-module
//! visibility pattern as the other `cmd_*` sub-modules.
use super::{flatten_err, write_audit_line, Action, App, AppMsg};
impl App {
pub(crate) fn cmd_config_save(&mut self, rest: &[&str]) {
match rest.first().copied() {
None => {
self.error_message =
Some("usage: :config-save <template-name> (uses selected env)".into());
}
Some(template) => {
if self.read_only {
self.error_message = Some("read-only mode — config-save disabled".into());
return;
}
let Some(env) = self.selected_env().cloned() else {
self.error_message = Some("no env selected".into());
return;
};
let Some(env_id) = env.id.clone() else {
self.error_message = Some("env has no internal ID — refresh and retry".into());
return;
};
let template = template.to_string();
let app_name = env.application.clone();
let aws = self.aws.clone();
let tx = self.msg_tx.clone();
let gen = self.generation;
self.status_message = Some(format!(
"saving config from {} as template '{template}'…",
env.name
));
let action = Action::ConfigSave;
let display_env = env.name.clone();
let template_for_msg = template.clone();
write_audit_line(
self.context.account_id.as_deref(),
self.context.profile.as_deref(),
&self.context.region,
&format!(
"stage=dispatched action={action:?} target={display_env} template={template}"
),
);
self.push_pending(action.label(), display_env.clone());
tokio::spawn(async move {
let result = aws
.create_config_template(&app_name, &template, &env_id)
.await
.map_err(|e| flatten_err("create_config_template", e));
let labelled = result
.map(|_| ())
.map_err(|e| format!("config-save '{template_for_msg}': {e}"));
let _ = tx.send(AppMsg::ActionResult {
gen,
action,
env_name: display_env,
result: labelled,
});
});
}
}
}
pub(crate) fn cmd_config_delete(&mut self, rest: &[&str]) {
match (rest.first().copied(), rest.get(1).copied()) {
(Some(app_name), Some(_)) => {
// Template names can contain spaces — join everything
// after the app name so :config-delete app "Dev config
// pre-redis" works as typed.
let template = rest[1..].join(" ");
self.spawn_config_delete_template(app_name.to_string(), template);
}
_ => {
self.error_message =
Some("usage: :config-delete <application> <template-name>".into());
}
}
}
pub(crate) fn cmd_config_apply(&mut self, rest: &[&str]) {
match rest.first().copied() {
None => {
self.error_message = Some(
"usage: :config-apply <template-name> (applies to selected env; template name may contain spaces)".into(),
);
}
Some(_) => {
// Join all rest tokens so multi-word template names work
// as typed. The overlay's `a`/enter keys bypass this
// parser and call spawn_config_apply_template directly.
let template = rest.join(" ");
let Some(env) = self.selected_env().cloned() else {
self.error_message = Some("no env selected".into());
return;
};
self.spawn_config_apply_template(env.name.clone(), template);
}
}
}
/// Single-arg form: `:config-inspect TEMPLATE` (template name
/// may contain spaces). Uses the selected env's application.
/// Two-arg form with whitespace is ambiguous with multi-word
/// template names, so the overlay's `i` keybind is the right
/// path for cross-app inspection.
pub(crate) fn cmd_config_inspect(&mut self, rest: &[&str]) {
if rest.is_empty() {
self.error_message = Some(
"usage: :config-inspect TEMPLATE (uses selected env's app; use `i` in :saved-configs for cross-app inspect)".into(),
);
return;
}
let template = rest.join(" ");
let Some(env) = self.selected_env().cloned() else {
self.error_message = Some("no env selected".into());
return;
};
self.spawn_config_inspect_template(env.application, template);
}
}