use super::*;
impl App {
pub(super) fn open_namespaces(&mut self) {
self.ns_list = vec!["<all>".into()];
self.ns_state.select(Some(0));
self.ns_filter.clear();
self.mode = Mode::Namespaces;
let client = self.cluster.client.clone();
let kind = self.cluster.resolve("namespaces").map(|k| k.ar);
let tx = self.tx.clone();
let genr = self.generation;
tokio::spawn(async move {
let Some(ar) = kind else { return };
let api: Api<DynamicObject> = Api::all_with(client, &ar);
if let Ok(list) = api.list(&ListParams::default()).await {
let mut names: Vec<String> = list
.items
.into_iter()
.filter_map(|o| o.metadata.name)
.collect();
names.sort();
names.insert(0, "<all>".into());
let _ = tx
.send(Msg::Namespaces {
generation: genr,
list: names,
})
.await;
}
});
}
pub fn filtered_namespaces(&self) -> Vec<String> {
let mut out = vec!["<all>".to_string()];
let rest = self.ns_list.iter().filter(|n| n.as_str() != "<all>");
if self.ns_filter.is_empty() {
out.extend(rest.cloned());
} else {
let mut scored: Vec<(i64, &String)> = rest
.filter_map(|n| self.matcher.fuzzy_match(n, &self.ns_filter).map(|s| (s, n)))
.collect();
scored.sort_by(|a, b| b.0.cmp(&a.0).then_with(|| a.1.cmp(b.1)));
out.extend(scored.into_iter().map(|(_, n)| n.clone()));
}
out
}
pub(super) fn key_namespaces(&mut self, key: KeyEvent) {
let len = self.filtered_namespaces().len();
match key.code {
KeyCode::Esc => {
if self.ns_filter.is_empty() {
self.mode = Mode::Table;
} else {
self.ns_filter.clear();
self.ns_state.select(Some(0));
}
}
KeyCode::Down => list_step(&mut self.ns_state, len, true),
KeyCode::Up => list_step(&mut self.ns_state, len, false),
KeyCode::Enter => {
let filtered = self.filtered_namespaces();
let has_real_match = filtered.iter().any(|n| n != "<all>");
let chosen = if !self.ns_filter.trim().is_empty() && !has_real_match {
Some(self.ns_filter.trim().to_string())
} else {
self.ns_state
.selected()
.and_then(|i| filtered.get(i).cloned())
};
if let Some(ns) = chosen {
self.set_namespace(ns);
}
}
KeyCode::Backspace => {
self.ns_filter.pop();
self.select_best_namespace_match();
}
KeyCode::Char(c) => {
self.ns_filter.push(c);
self.select_best_namespace_match();
}
_ => {}
}
}
pub(super) fn select_best_namespace_match(&mut self) {
let idx = if !self.ns_filter.is_empty() && self.filtered_namespaces().len() > 1 {
1 } else {
0
};
self.ns_state.select(Some(idx));
}
pub(super) fn set_namespace(&mut self, sel: String) {
self.namespace = if sel == "<all>" || sel.is_empty() {
String::new()
} else {
sel
};
let label = if self.namespace.is_empty() {
"all namespaces".to_string()
} else {
self.namespace.clone()
};
self.flash = format!("namespace: {label}");
self.flash_err = false;
self.ns_filter.clear();
self.mode = Mode::Table;
self.table_state.select(Some(0));
self.start_watch();
}
pub(super) fn open_contexts(&mut self) {
self.ctx_filter.clear();
self.ctx_list.clear();
self.ctx_state.select(None);
self.mode = Mode::Contexts;
let tx = self.tx.clone();
let genr = self.generation;
tokio::spawn(async move {
let mut list = Cluster::list_contexts();
list.sort();
let _ = tx
.send(Msg::Contexts {
generation: genr,
list,
})
.await;
});
}
pub fn filtered_contexts(&self) -> Vec<String> {
if self.ctx_filter.is_empty() {
return self.ctx_list.clone();
}
let mut scored: Vec<(i64, &String)> = self
.ctx_list
.iter()
.filter_map(|c| {
self.matcher
.fuzzy_match(c, &self.ctx_filter)
.map(|s| (s, c))
})
.collect();
scored.sort_by(|a, b| b.0.cmp(&a.0).then_with(|| a.1.cmp(b.1)));
scored.into_iter().map(|(_, c)| c.clone()).collect()
}
pub(super) fn key_contexts(&mut self, key: KeyEvent) {
let len = self.filtered_contexts().len();
match key.code {
KeyCode::Esc => {
if self.ctx_filter.is_empty() {
self.mode = Mode::Table;
} else {
self.ctx_filter.clear();
self.ctx_state.select(Some(0));
}
}
KeyCode::Down => list_step(&mut self.ctx_state, len, true),
KeyCode::Up => list_step(&mut self.ctx_state, len, false),
KeyCode::Enter => {
if let Some(name) = self
.ctx_state
.selected()
.and_then(|i| self.filtered_contexts().get(i).cloned())
{
self.mode = Mode::Table;
self.switch_context(name);
}
}
KeyCode::Backspace => {
self.ctx_filter.pop();
self.ctx_state.select(Some(0));
}
KeyCode::Char(c) => {
self.ctx_filter.push(c);
self.ctx_state.select(Some(0));
}
_ => {}
}
}
pub(super) fn switch_context(&mut self, name: String) {
if name == self.cluster.context {
return;
}
self.flash = format!("switching to {name}…");
self.flash_err = false;
self.bump_generation();
self.store.clear();
self.invalidate_rows();
let tx = self.tx.clone();
let genr = self.generation;
tokio::spawn(async move {
let result = Cluster::connect_context(&name)
.await
.map(Box::new)
.map_err(|e| e.to_string());
let _ = tx
.send(Msg::ContextSwitched {
generation: genr,
name,
result,
})
.await;
});
}
pub(super) fn apply_context_switch(&mut self, name: String, mut cluster: Box<Cluster>) {
cluster.add_aliases(&self.user_aliases);
self.bump_generation();
self.namespace = cluster.default_namespace.clone();
self.cluster = *cluster;
self.stack.clear();
self.kind = None;
self.kind_plural.clear();
self.labels = None;
self.fields = None;
self.scope_label = None;
self.filter.clear();
self.rbac_allowed = None;
self.last_rbac_ns = None;
self.flash = format!("context: {name}");
self.flash_err = false;
self.switch_kind("pods");
}
}