use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct Config {
#[serde(default)]
pub defaults: Defaults,
#[serde(default)]
pub clusters: HashMap<String, Cluster>,
#[serde(default)]
pub interactive: InteractiveConfig,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum JumpHostConfig {
SshConfigHostRef {
ssh_config_host: String,
},
Detailed {
host: String,
#[serde(default)]
user: Option<String>,
#[serde(default)]
port: Option<u16>,
#[serde(default)]
ssh_key: Option<String>,
},
Simple(String),
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct Defaults {
pub user: Option<String>,
pub port: Option<u16>,
pub ssh_key: Option<String>,
pub parallel: Option<usize>,
pub timeout: Option<u64>,
pub jump_host: Option<JumpHostConfig>,
pub server_alive_interval: Option<u64>,
pub server_alive_count_max: Option<usize>,
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct InteractiveConfig {
#[serde(default = "default_interactive_mode")]
pub default_mode: InteractiveMode,
#[serde(default = "default_prompt_format")]
pub prompt_format: String,
#[serde(default)]
pub history_file: Option<String>,
#[serde(default)]
pub colors: HashMap<String, String>,
#[serde(default)]
pub keybindings: KeyBindings,
#[serde(default)]
pub broadcast_prefix: Option<String>,
#[serde(default)]
pub node_switch_prefix: Option<String>,
#[serde(default)]
pub show_timestamps: bool,
#[serde(default)]
pub work_dir: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum InteractiveMode {
#[default]
SingleNode,
Multiplex,
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct KeyBindings {
#[serde(default = "default_switch_node")]
pub switch_node: String,
#[serde(default = "default_broadcast_toggle")]
pub broadcast_toggle: String,
#[serde(default = "default_quit")]
pub quit: String,
#[serde(default)]
pub clear_screen: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Cluster {
pub nodes: Vec<NodeConfig>,
#[serde(flatten)]
pub defaults: ClusterDefaults,
#[serde(default)]
pub interactive: Option<InteractiveConfig>,
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct ClusterDefaults {
pub user: Option<String>,
pub port: Option<u16>,
pub ssh_key: Option<String>,
pub parallel: Option<usize>,
pub timeout: Option<u64>,
pub jump_host: Option<JumpHostConfig>,
pub server_alive_interval: Option<u64>,
pub server_alive_count_max: Option<usize>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum NodeConfig {
Simple(String),
Detailed {
host: String,
#[serde(default)]
port: Option<u16>,
#[serde(default)]
user: Option<String>,
#[serde(default)]
jump_host: Option<JumpHostConfig>,
},
}
#[derive(Debug, Default)]
pub struct InteractiveConfigUpdate {
pub default_mode: Option<InteractiveMode>,
pub prompt_format: Option<String>,
pub history_file: Option<String>,
pub work_dir: Option<String>,
pub show_timestamps: Option<bool>,
pub colors: Option<HashMap<String, String>>,
}
pub(super) fn default_interactive_mode() -> InteractiveMode {
InteractiveMode::SingleNode
}
pub(super) fn default_prompt_format() -> String {
"[{node}:{user}@{host}:{pwd}]$ ".to_string()
}
pub(super) fn default_switch_node() -> String {
"Ctrl+N".to_string()
}
pub(super) fn default_broadcast_toggle() -> String {
"Ctrl+B".to_string()
}
pub(super) fn default_quit() -> String {
"Ctrl+Q".to_string()
}
impl JumpHostConfig {
pub fn to_connection_string(&self) -> String {
match self {
JumpHostConfig::Simple(s) => s.clone(),
JumpHostConfig::Detailed {
host,
user,
port,
ssh_key: _,
} => {
let mut result = String::new();
if let Some(u) = user {
result.push_str(u);
result.push('@');
}
result.push_str(host);
if let Some(p) = port {
result.push(':');
result.push_str(&p.to_string());
}
result
}
JumpHostConfig::SshConfigHostRef { ssh_config_host } => {
format!("@{}", ssh_config_host)
}
}
}
pub fn ssh_key(&self) -> Option<&str> {
match self {
JumpHostConfig::Simple(_) => None,
JumpHostConfig::Detailed { ssh_key, .. } => ssh_key.as_deref(),
JumpHostConfig::SshConfigHostRef { .. } => None,
}
}
pub fn is_ssh_config_ref(&self) -> bool {
match self {
JumpHostConfig::Simple(s) => s.starts_with('@'),
JumpHostConfig::SshConfigHostRef { .. } => true,
JumpHostConfig::Detailed { .. } => false,
}
}
pub fn ssh_config_host(&self) -> Option<&str> {
match self {
JumpHostConfig::Simple(s) if s.starts_with('@') => Some(&s[1..]),
JumpHostConfig::SshConfigHostRef { ssh_config_host } => Some(ssh_config_host),
_ => None,
}
}
}