use super::*;
#[derive(Clone, Copy, PartialEq)]
pub(super) enum NodeKind {
Spoke,
Hub,
}
impl NodeKind {
pub(super) fn short(self) -> &'static str {
match self {
NodeKind::Spoke => "Spoke",
NodeKind::Hub => "Hub",
}
}
pub(super) fn toggled(self) -> Self {
match self {
NodeKind::Spoke => NodeKind::Hub,
NodeKind::Hub => NodeKind::Spoke,
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub(super) enum KeySource {
Generate,
Paste,
}
impl KeySource {
pub(super) fn short(self) -> &'static str {
match self {
KeySource::Generate => "Generate",
KeySource::Paste => "Paste pubkey",
}
}
pub(super) fn toggled(self) -> Self {
match self {
KeySource::Generate => KeySource::Paste,
KeySource::Paste => KeySource::Generate,
}
}
}
pub(super) enum FieldKind {
Text,
Type(NodeKind),
Key(KeySource),
Pick { options: Vec<String>, idx: usize },
}
pub(super) struct Field {
pub(super) label: String,
pub(super) default: String,
pub(super) value: String,
pub(super) kind: FieldKind,
}
impl Field {
pub(super) fn new(label: &str, default: &str) -> Self {
Self {
label: label.into(),
default: default.into(),
value: String::new(),
kind: FieldKind::Text,
}
}
pub(super) fn type_toggle(kind: NodeKind) -> Self {
Self {
label: "Type".into(),
default: String::new(),
value: String::new(),
kind: FieldKind::Type(kind),
}
}
pub(super) fn key_toggle(src: KeySource) -> Self {
Self {
label: "Key".into(),
default: String::new(),
value: String::new(),
kind: FieldKind::Key(src),
}
}
pub(super) fn pick(label: &str, options: Vec<String>) -> Self {
Self {
label: label.into(),
default: String::new(),
value: String::new(),
kind: FieldKind::Pick { options, idx: 0 },
}
}
pub(super) fn is_choice(&self) -> bool {
matches!(
self.kind,
FieldKind::Type(_) | FieldKind::Key(_) | FieldKind::Pick { .. }
)
}
}
pub(super) struct Prompt {
pub(super) title: String,
pub(super) fields: Vec<Field>,
pub(super) idx: usize,
pub(super) action: Action,
}
impl Prompt {
pub(super) fn cur_mut(&mut self) -> &mut Field {
&mut self.fields[self.idx]
}
pub(super) fn kind(&self) -> NodeKind {
match self.fields.first().map(|f| &f.kind) {
Some(FieldKind::Type(k)) => *k,
_ => NodeKind::Spoke,
}
}
pub(super) fn keysrc(&self) -> KeySource {
self.fields
.iter()
.find_map(|f| {
if let FieldKind::Key(k) = &f.kind {
Some(*k)
} else {
None
}
})
.unwrap_or(KeySource::Generate)
}
pub(super) fn value_of(&self, label_starts: &str) -> String {
self.fields
.iter()
.find(|f| f.label.starts_with(label_starts))
.map(|f| match &f.kind {
FieldKind::Pick { options, idx } => options.get(*idx).cloned().unwrap_or_default(),
_ => {
let v = f.value.trim();
if v.is_empty() {
f.default.clone()
} else {
v.to_string()
}
}
})
.unwrap_or_default()
}
pub(super) fn base_fields(kind: NodeKind, default_addr: &str, hubs: &[String]) -> Vec<Field> {
let mut fields = vec![
Field::type_toggle(kind),
Field::new("Name", ""),
Field::new("Address (interface IP/prefix)", default_addr),
];
match kind {
NodeKind::Spoke => {
fields.push(Field::new("DNS (optional - e.g. a Pi-hole)", ""));
fields.push(Field::pick("Hub to dial", hubs.to_vec()));
}
NodeKind::Hub => {
fields.push(Field::new("Endpoint (host:port peers dial)", ""));
fields.push(Field::new("Allowed-IPs peers route here", "0.0.0.0/0"));
fields.push(Field::new("Keepalive seconds (optional)", ""));
}
}
fields
}
pub(super) fn key_fields(keysrc: KeySource) -> Vec<Field> {
let mut fields = vec![Field::key_toggle(keysrc)];
match keysrc {
KeySource::Generate => fields.push(Field::pick(
"Private key",
vec!["store in mesh.toml".into(), "redact (QR/file only)".into()],
)),
KeySource::Paste => fields.push(Field::new("Public key (paste existing)", "")),
}
fields
}
pub(super) fn create_node(
kind: NodeKind,
keysrc: KeySource,
default_addr: &str,
hubs: &[String],
) -> Self {
let mut fields = Self::base_fields(kind, default_addr, hubs);
fields.extend(Self::key_fields(keysrc));
Self {
title: "Create a mesh node".into(),
idx: 0,
action: Action::AddNode,
fields,
}
}
pub(super) fn set(&mut self, label_starts: &str, value: &str) {
if let Some(f) = self
.fields
.iter_mut()
.find(|f| f.label.starts_with(label_starts))
{
f.value = value.to_string();
}
}
pub(super) fn set_pick(&mut self, label_starts: &str, selected: &str) {
if let Some(f) = self
.fields
.iter_mut()
.find(|f| f.label.starts_with(label_starts))
&& let FieldKind::Pick { options, idx } = &mut f.kind
&& let Some(pos) = options.iter().position(|o| o == selected)
{
*idx = pos;
}
}
pub(super) fn edit_node(node: &Node, hubs: &[String]) -> Self {
let kind = if node.endpoint.is_some() {
NodeKind::Hub
} else {
NodeKind::Spoke
};
let mut p = Self {
title: format!("Edit `{}`", node.name),
fields: Self::base_fields(kind, &node.address, hubs),
idx: 0,
action: Action::EditNode {
original: node.name.clone(),
},
};
p.set("Name", &node.name);
p.set("Address", &node.address);
p.set("DNS", node.dns.as_deref().unwrap_or(""));
p.set("Endpoint", node.endpoint.as_deref().unwrap_or(""));
p.set("Allowed-IPs", node.allowed_ips.as_deref().unwrap_or(""));
p.set(
"Keepalive",
&node.keepalive.map(|k| k.to_string()).unwrap_or_default(),
);
if let Some(h) = node.hubs.first() {
p.set_pick("Hub to dial", h);
}
p
}
pub(super) fn cycle_pick(&mut self, delta: isize) {
if let FieldKind::Pick { options, idx } = &mut self.fields[self.idx].kind
&& !options.is_empty()
{
let n = options.len() as isize;
*idx = (((*idx as isize + delta) % n + n) % n) as usize;
}
}
pub(super) fn rebuild(
&mut self,
kind: NodeKind,
keysrc: KeySource,
default_addr: &str,
hubs: &[String],
) {
let old = std::mem::take(&mut self.fields);
let mut next = if matches!(self.action, Action::EditNode { .. }) {
Self::base_fields(kind, default_addr, hubs)
} else {
let mut f = Self::base_fields(kind, default_addr, hubs);
f.extend(Self::key_fields(keysrc));
f
};
for nf in next.iter_mut() {
if let Some(of) = old.iter().find(|f| f.label == nf.label) {
nf.value = of.value.clone();
if let (
FieldKind::Pick { options, idx },
FieldKind::Pick {
options: oo,
idx: oi,
},
) = (&mut nf.kind, &of.kind)
&& let Some(pos) = oo
.get(*oi)
.and_then(|sel| options.iter().position(|o| o == sel))
{
*idx = pos;
}
}
}
self.fields = next;
self.idx = self.idx.min(self.fields.len().saturating_sub(1));
}
pub(super) fn toggle_kind(&mut self, default_addr: &str, hubs: &[String]) {
self.rebuild(self.kind().toggled(), self.keysrc(), default_addr, hubs);
self.idx = 0;
}
pub(super) fn toggle_keysrc(&mut self, default_addr: &str, hubs: &[String]) {
let at = self.idx;
self.rebuild(self.kind(), self.keysrc().toggled(), default_addr, hubs);
self.idx = at.min(self.fields.len().saturating_sub(1));
}
}