use std::path::PathBuf;
use crate::wg;
use super::edit::{backup, stem, write_temp};
use super::overlay::ConfirmAction;
use super::*;
impl App {
pub(super) fn selected_iface(&self) -> Option<&Iface> {
self.iface_state.selected().and_then(|i| self.ifaces.get(i))
}
pub(super) fn select_iface(&mut self, name: &str) {
if let Some(i) = self.ifaces.iter().position(|f| f.name == name) {
self.iface_state.select(Some(i));
}
}
pub(super) fn next_iface_name(&self) -> String {
let used: std::collections::BTreeSet<&str> =
self.ifaces.iter().map(|i| i.name.as_str()).collect();
(0..=254)
.map(|n| format!("wg{n}"))
.find(|c| !used.contains(c.as_str()))
.unwrap_or_else(|| "wg0".into())
}
pub(super) fn start_create_conf(&mut self) {
const SKELETON: &str = "[Interface]\n# Paste your provider's config over this, or fill it in.\nPrivateKey = \nAddress = \n# DNS = 1.1.1.1\n\n[Peer]\nPublicKey = \n# PresharedKey = \nEndpoint = \nAllowedIPs = 0.0.0.0/0\n";
match write_temp(SKELETON) {
Ok(tmp) => {
self.pending_editor = Some(EditorReq {
tmp,
original: None,
was_up: false,
seed: SKELETON.to_string(),
})
}
Err(e) => self.set_status(format!("can't open an editor buffer: {e}")),
}
}
pub(super) fn start_edit_conf(&mut self) {
let Some(iface) = self.selected_iface().cloned() else {
self.set_status("no interface selected");
return;
};
let content = match std::fs::read_to_string(&iface.path) {
Ok(t) => t,
Err(e) => {
self.set_status(format!("can't read {}: {e}", iface.path.display()));
return;
}
};
match write_temp(&content) {
Ok(tmp) => {
self.pending_editor = Some(EditorReq {
tmp,
original: Some(iface.path.clone()),
was_up: iface.up,
seed: content,
})
}
Err(e) => self.set_status(format!("can't open an editor buffer: {e}")),
}
}
pub(super) fn editor_done(&mut self, req: EditorReq) {
let content = std::fs::read_to_string(&req.tmp).unwrap_or_default();
let _ = std::fs::remove_file(&req.tmp);
if content == req.seed || content.trim().is_empty() {
self.set_status(if req.original.is_some() {
"no changes - nothing saved"
} else {
"nothing saved"
});
return;
}
match wg::validate_config(&content) {
Ok(()) => self.open_conf_name_prompt(content, req.original, req.was_up),
Err(e) => {
self.overlay = Some(Overlay::Invalid {
reason: e.to_string(),
content,
original: req.original,
was_up: req.was_up,
})
}
}
}
pub(super) fn reopen_editor(
&mut self,
content: String,
original: Option<PathBuf>,
was_up: bool,
) {
match write_temp(&content) {
Ok(tmp) => {
self.pending_editor = Some(EditorReq {
tmp,
original,
was_up,
seed: content,
})
}
Err(e) => self.set_status(format!("can't reopen editor: {e}")),
}
}
pub(super) fn open_conf_name_prompt(
&mut self,
content: String,
original: Option<PathBuf>,
was_up: bool,
) {
let default_name = original
.as_deref()
.map(stem)
.unwrap_or_else(|| self.next_iface_name());
let mut name = Field::new("Name (.conf interface name)", "");
name.value = default_name;
let mut fields = vec![name];
if original.is_none() && self.dirs.len() > 1 {
let opts: Vec<String> = self.dirs.iter().map(|d| d.display().to_string()).collect();
fields.push(Field::pick("Directory", opts));
}
let title = if original.is_some() {
"Save interface".to_string()
} else {
"Name the interface".to_string()
};
self.prompt = Some(Prompt {
title,
fields,
idx: 0,
action: Action::SaveConf {
content,
original,
was_up,
},
});
}
pub(super) fn confirm_delete_iface(&mut self) {
let Some(iface) = self.selected_iface() else {
self.set_status("no interface selected");
return;
};
if iface.up {
self.set_status(format!(
"`{}` is up - toggle it down (↵) before deleting",
iface.name
));
return;
}
let name = iface.name.clone();
let path = iface.path.clone();
self.overlay = Some(Overlay::Confirm {
prompt: format!(" delete `{name}.conf`? (a .bak is kept) y / n "),
action: ConfirmAction::DeleteIface(path),
});
}
pub(super) fn delete_iface(&mut self, path: PathBuf) {
backup(&path);
match std::fs::remove_file(&path) {
Ok(()) => {
let name = stem(&path);
self.reload(format!("deleted `{name}.conf` (.bak kept)"));
}
Err(e) => self.set_status(format!(
"can't delete {}: {e} (need sudo ewg?)",
path.display()
)),
}
}
pub(super) fn toggle_boot(&mut self) {
let Some(iface) = self.selected_iface().cloned() else {
self.set_status("no interface selected");
return;
};
match iface.enabled {
None => self.set_status("can't manage boot state - systemd/systemctl not available"),
Some(enabled) => match wg::set_boot(&iface.name, !enabled) {
Ok(()) => {
let name = iface.name.clone();
self.reload(format!(
"`{name}` will {} start on boot",
if enabled { "no longer" } else { "now" }
));
self.select_iface(&name);
}
Err(e) => self.set_status(format!("{e} (need sudo ewg?)")),
},
}
}
pub(super) fn inspect_iface(&mut self) {
let Some(iface) = self.selected_iface().cloned() else {
self.set_status("no interface selected");
return;
};
let mut body = match std::fs::read_to_string(&iface.path) {
Ok(t) => t,
Err(e) => {
self.set_status(format!("can't read {}: {e}", iface.path.display()));
return;
}
};
if iface.up
&& let Ok(live) = wg::show(&iface.name)
&& !live.trim().is_empty()
{
body.push_str("\n# ---- live (wg show) ----\n");
body.push_str(&live);
}
self.overlay = Some(Overlay::Text {
title: format!(" {} ", tilde(&iface.path)),
body,
scroll: 0,
});
}
}