Files
nexus/rust/core/src/models.rs
T

914 lines
28 KiB
Rust

//! Data model shared by storage, the session manager and the UI. Field names serialise exactly as
//! the Go structs did, so database JSON columns and `.link`/`.nexus` exports round-trip unchanged.
use chrono::{DateTime, FixedOffset, Local};
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::BTreeMap;
pub const DEFAULT_SSH_PORT: u16 = 22;
pub const DEFAULT_TELNET_PORT: u16 = 23;
pub const DEFAULT_RAW_PORT: u16 = 23;
pub const DEFAULT_GITHUB_PORT: u16 = 443;
pub const DEFAULT_VNC_PORT: u16 = 5900;
pub const DEFAULT_IRC_PORT: u16 = 6667;
pub const DEFAULT_IRC_TLS_PORT: u16 = 6697;
pub const DEFAULT_CONNECT_TIMEOUT: i64 = 30;
pub const DEFAULT_SCROLLBACK: i64 = 10000;
pub const DEFAULT_FONT_SIZE: i64 = 14;
pub const DEFAULT_FONT_FAMILY: &str = "JetBrains Mono, Menlo, Monaco, Consolas, monospace";
pub const DEFAULT_CURSOR_STYLE: &str = "block";
pub const DEFAULT_TERMINAL_TYPE: &str = "xterm-256color";
pub const DEFAULT_KEEPALIVE_INTERVAL: i64 = 30;
pub const DEFAULT_KEEPALIVE_COUNT_MAX: i64 = 3;
pub type Time = DateTime<FixedOffset>;
pub fn now() -> Time {
Local::now().fixed_offset()
}
/// Go's zero `time.Time`, which is what a missing timestamp decodes to.
pub fn zero_time() -> Time {
DateTime::parse_from_rfc3339("0001-01-01T00:00:00Z").unwrap()
}
/// Go writes nil slices and maps as `null`; treat that as empty.
fn nd<'de, D: Deserializer<'de>, T: Default + Deserialize<'de>>(d: D) -> Result<T, D::Error> {
Ok(Option::<T>::deserialize(d)?.unwrap_or_default())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum Protocol {
#[default]
Ssh,
Telnet,
Raw,
Github,
Vnc,
Irc,
Serial,
Local,
}
impl Protocol {
pub const SAVED: [Protocol; 7] =
[Protocol::Ssh, Protocol::Telnet, Protocol::Raw, Protocol::Github, Protocol::Vnc, Protocol::Irc, Protocol::Serial];
pub fn id(self) -> &'static str {
match self {
Protocol::Ssh => "ssh",
Protocol::Telnet => "telnet",
Protocol::Raw => "raw",
Protocol::Github => "github",
Protocol::Vnc => "vnc",
Protocol::Irc => "irc",
Protocol::Serial => "serial",
Protocol::Local => "local",
}
}
pub fn from_id(s: &str) -> Option<Self> {
[Protocol::Local].into_iter().chain(Self::SAVED).find(|p| p.id() == s)
}
pub fn label(self) -> &'static str {
match self {
Protocol::Ssh => "SSH",
Protocol::Telnet => "Telnet",
Protocol::Raw => "RAW TCP",
Protocol::Github => "GitHub",
Protocol::Vnc => "VNC",
Protocol::Irc => "IRC",
Protocol::Serial => "Serial",
Protocol::Local => "Local",
}
}
/// Protocols whose sessions render in a terminal view.
pub fn is_terminal(self) -> bool {
matches!(self, Protocol::Ssh | Protocol::Telnet | Protocol::Serial | Protocol::Local)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum HostKeyPolicy {
#[serde(rename = "strict")]
Strict,
#[default]
#[serde(rename = "ask")]
Ask,
#[serde(rename = "accept-new")]
AcceptNew,
#[serde(rename = "insecure")]
Insecure,
}
impl HostKeyPolicy {
pub const ALL: [HostKeyPolicy; 4] = [Self::Strict, Self::Ask, Self::AcceptNew, Self::Insecure];
pub fn label(self) -> &'static str {
match self {
Self::Strict => "Strict (known hosts only)",
Self::Ask => "Ask on new or changed keys",
Self::AcceptNew => "Accept new, reject changed",
Self::Insecure => "Insecure (accept anything)",
}
}
}
/// How a credential is obtained. Unknown strings (and the legacy "prompt") are kept as
/// ask-every-time, matching Go's normaliseSecretMode.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Default)]
pub enum SecretMode {
#[default]
#[serde(rename = "saved")]
Saved,
#[serde(rename = "session-only")]
SessionOnly,
#[serde(rename = "ask-every-time")]
AskEveryTime,
}
impl<'de> Deserialize<'de> for SecretMode {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
Ok(match Option::<String>::deserialize(d)?.as_deref().unwrap_or("") {
"saved" | "" => SecretMode::Saved,
"session-only" => SecretMode::SessionOnly,
_ => SecretMode::AskEveryTime,
})
}
}
impl SecretMode {
pub const ALL: [SecretMode; 3] = [Self::Saved, Self::SessionOnly, Self::AskEveryTime];
pub fn label(self) -> &'static str {
match self {
Self::Saved => "Save securely",
Self::SessionOnly => "Remember for this session",
Self::AskEveryTime => "Ask every time",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum LineEnding {
Lf,
Cr,
#[default]
Crlf,
None,
}
impl LineEnding {
pub const ALL: [LineEnding; 4] = [Self::Crlf, Self::Lf, Self::Cr, Self::None];
pub fn bytes(self) -> &'static [u8] {
match self {
Self::Lf => b"\n",
Self::Cr => b"\r",
Self::Crlf => b"\r\n",
Self::None => b"",
}
}
pub fn label(self) -> &'static str {
match self {
Self::Lf => "LF (\\n)",
Self::Cr => "CR (\\r)",
Self::Crlf => "CRLF (\\r\\n)",
Self::None => "None",
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Profile {
#[serde(default)]
pub id: String,
#[serde(default)]
pub display_name: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub protocol: Protocol,
#[serde(default)]
pub host: String,
#[serde(default)]
pub port: i64,
#[serde(default)]
pub username: String,
#[serde(default, deserialize_with = "nd")]
pub tags: Vec<String>,
#[serde(default)]
pub folder: String,
#[serde(default)]
pub environment: String,
#[serde(default)]
pub notes: String,
#[serde(default)]
pub favourite: bool,
#[serde(default)]
pub last_connected: Option<Time>,
#[serde(default)]
pub connect_timeout: i64,
#[serde(default)]
pub read_timeout: i64,
#[serde(default = "zero_time")]
pub created_at: Time,
#[serde(default = "zero_time")]
pub updated_at: Time,
#[serde(default)]
pub terminal_font_family: String,
#[serde(default)]
pub terminal_font_size: i64,
#[serde(default)]
pub terminal_scrollback: i64,
#[serde(default)]
pub terminal_cursor_style: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ssh: Option<SshConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub telnet: Option<TelnetConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw: Option<RawConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub github: Option<GitHubConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub vnc: Option<VncConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub irc: Option<IrcConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub serial: Option<SerialConfig>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct SshConfig {
#[serde(deserialize_with = "nd")]
pub auth_methods: Vec<String>,
pub secret_mode: SecretMode,
pub private_key_path: String,
pub key_secret_mode: SecretMode,
pub host_key_policy: HostKeyPolicy,
pub terminal_type: String,
pub keepalive_interval: i64,
pub keepalive_count_max: i64,
pub compression: bool,
pub agent_auth: bool,
pub agent_forwarding: bool,
pub auto_reconnect: bool,
pub max_reconnect_tries: i64,
#[serde(deserialize_with = "nd")]
pub login_scripts: Vec<LoginScriptStep>,
#[serde(rename = "totpSecret")]
pub totp_secret: String,
pub proxy_type: String,
pub proxy_host: String,
pub proxy_port: i64,
pub proxy_username: String,
pub proxy_password: String,
#[serde(deserialize_with = "nd")]
pub jump_hosts: Vec<JumpHost>,
#[serde(deserialize_with = "nd")]
pub port_forwards: Vec<PortForward>,
pub startup_command: String,
#[serde(deserialize_with = "nd")]
pub env_vars: BTreeMap<String, String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct LoginScriptStep {
#[serde(rename = "type")]
pub kind: String,
pub value: String,
pub timeout: i64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct JumpHost {
pub host: String,
pub port: i64,
pub username: String,
pub auth_method: String,
pub secret_mode: SecretMode,
pub private_key_path: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ForwardDirection {
#[default]
Local,
Remote,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct PortForward {
pub id: String,
pub direction: ForwardDirection,
pub bind_host: String,
pub bind_port: i64,
pub dest_host: String,
pub dest_port: i64,
pub enabled: bool,
pub label: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct TelnetConfig {
pub local_echo: bool,
pub line_ending: LineEnding,
pub terminal_type: String,
pub encoding: String,
pub negotiation_debug: bool,
pub line_mode: bool,
pub auto_reconnect: bool,
pub max_reconnect_tries: i64,
#[serde(deserialize_with = "nd")]
pub login_scripts: Vec<LoginScriptStep>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct RawConfig {
pub auto_reconnect: bool,
pub line_ending: LineEnding,
pub encoding: String,
pub show_timestamps: bool,
pub show_direction: bool,
#[serde(deserialize_with = "nd")]
pub payload_presets: Vec<PayloadPreset>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct PayloadPreset {
pub id: String,
pub name: String,
/// base64-encoded for binary safety
pub payload: String,
/// text, hex, escaped
pub format: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct GitHubConfig {
#[serde(rename = "baseURL")]
pub base_url: String,
pub secret_mode: SecretMode,
pub refresh_interval_secs: i64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct VncConfig {
pub vnc_host: String,
pub vnc_port: i64,
pub secret_mode: SecretMode,
pub view_only: bool,
pub scale_to_fit: bool,
pub quality: i64,
pub shared_session: bool,
pub ssh_tunnel: bool,
pub ssh_host: String,
pub ssh_port: i64,
pub ssh_username: String,
pub ssh_auth_method: String,
pub ssh_secret_mode: SecretMode,
#[serde(rename = "sshPrivateKeyPath")]
pub ssh_private_key: String,
pub ssh_agent_auth: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct IrcConfig {
pub nick: String,
pub real_name: String,
#[serde(rename = "useTLS")]
pub use_tls: bool,
pub server_password_required: bool,
pub server_password_mode: SecretMode,
pub nick_serv_identify: bool,
pub nick_serv_account: String,
pub nick_serv_mode: SecretMode,
#[serde(deserialize_with = "nd")]
pub auto_join_rooms: Vec<String>,
pub ssh_tunnel: bool,
pub ssh_host: String,
pub ssh_port: i64,
pub ssh_username: String,
pub ssh_auth_method: String,
pub ssh_secret_mode: SecretMode,
#[serde(rename = "sshPrivateKeyPath")]
pub ssh_private_key: String,
pub ssh_agent_auth: bool,
pub auto_reconnect: bool,
pub max_reconnect_tries: i64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum Parity {
#[default]
None,
Odd,
Even,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum FlowControl {
#[default]
None,
Xonxoff,
Rtscts,
}
/// Serial line settings. The device path lives in `Profile::host`.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", default)]
pub struct SerialConfig {
pub baud_rate: u32,
pub data_bits: u8,
pub parity: Parity,
pub stop_bits: u8,
pub flow_control: FlowControl,
pub line_ending: LineEnding,
pub local_echo: bool,
pub dtr_on_connect: bool,
pub rts_on_connect: bool,
pub encoding: String,
pub auto_reconnect: bool,
pub max_reconnect_tries: i64,
}
impl Default for SerialConfig {
fn default() -> Self {
Self {
baud_rate: 115200,
data_bits: 8,
parity: Parity::None,
stop_bits: 1,
flow_control: FlowControl::None,
line_ending: LineEnding::Cr,
local_echo: false,
dtr_on_connect: true,
rts_on_connect: true,
encoding: "utf-8".into(),
auto_reconnect: true,
max_reconnect_tries: 5,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct GitHubRepo {
pub full_name: String,
pub name: String,
pub owner: String,
pub description: String,
#[serde(rename = "htmlURL")]
pub html_url: String,
pub default_branch: String,
pub latest_committer: String,
pub latest_commit_at: String,
pub private: bool,
pub fork: bool,
pub archived: bool,
pub language: String,
pub stars: i64,
pub pushed_at: String,
pub updated_at: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum SessionState {
#[default]
Connecting,
Connected,
Disconnected,
Reconnecting,
Error,
}
#[derive(Clone, Debug)]
pub struct SessionInfo {
pub id: String,
pub profile_id: String,
pub profile_name: String,
pub protocol: Protocol,
pub host: String,
pub port: i64,
pub state: SessionState,
pub tab_name: String,
pub pinned: bool,
pub connected_at: Time,
pub error: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct KnownHost {
pub id: String,
pub host: String,
pub port: i64,
pub key_type: String,
pub fingerprint: String,
pub public_key: String,
pub first_seen: Time,
pub last_seen: Time,
pub trusted: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", default)]
pub struct AppPreferences {
pub theme: String,
pub terminal_theme: String,
pub default_protocol: String,
pub confirm_on_close: bool,
pub confirm_on_disconnect: bool,
pub show_status_bar: bool,
pub sidebar_width: i64,
pub default_font_family: String,
pub default_font_size: i64,
pub default_scrollback: i64,
pub default_cursor_style: String,
pub use_native_paste: bool,
pub local_shell: String,
}
impl Default for AppPreferences {
fn default() -> Self {
Self {
theme: "dark".into(),
terminal_theme: "charcoal".into(),
default_protocol: "ssh".into(),
confirm_on_close: true,
confirm_on_disconnect: true,
show_status_bar: true,
sidebar_width: 280,
default_font_family: DEFAULT_FONT_FAMILY.into(),
default_font_size: DEFAULT_FONT_SIZE,
default_scrollback: DEFAULT_SCROLLBACK,
default_cursor_style: DEFAULT_CURSOR_STYLE.into(),
use_native_paste: false,
local_shell: String::new(),
}
}
}
impl AppPreferences {
/// Fills zero values a stored preference blob may carry (Go's applyDefaults).
pub fn apply_defaults(&mut self) {
let d = Self::default();
if self.theme.is_empty() {
self.theme = d.theme;
}
if self.terminal_theme.is_empty() {
self.terminal_theme = d.terminal_theme;
}
if self.default_protocol.is_empty() {
self.default_protocol = d.default_protocol;
}
if self.sidebar_width == 0 {
self.sidebar_width = d.sidebar_width;
}
if self.default_font_family.is_empty() {
self.default_font_family = d.default_font_family;
}
if self.default_font_size == 0 {
self.default_font_size = d.default_font_size;
}
if self.default_scrollback == 0 {
self.default_scrollback = d.default_scrollback;
}
if self.default_cursor_style.is_empty() {
self.default_cursor_style = d.default_cursor_style;
}
}
}
#[derive(Clone, Debug)]
pub struct AuditEntry {
pub id: String,
pub timestamp: Time,
pub action: String,
pub profile_id: String,
pub session_id: String,
pub details: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Snippet {
pub id: String,
pub name: String,
pub command: String,
pub description: String,
pub folder: String,
pub tags: Vec<String>,
pub created_at: Time,
pub updated_at: Time,
}
pub fn new_id() -> String {
uuid::Uuid::new_v4().to_string()
}
impl Profile {
/// A profile with protocol defaults filled in (Go's NewProfile).
pub fn new(protocol: Protocol) -> Profile {
let now = now();
let mut p = Profile {
id: new_id(),
display_name: String::new(),
description: String::new(),
protocol,
host: String::new(),
port: 0,
username: String::new(),
tags: vec![],
folder: String::new(),
environment: String::new(),
notes: String::new(),
favourite: false,
last_connected: None,
connect_timeout: DEFAULT_CONNECT_TIMEOUT,
read_timeout: 0,
created_at: now,
updated_at: now,
terminal_font_family: DEFAULT_FONT_FAMILY.into(),
terminal_font_size: DEFAULT_FONT_SIZE,
terminal_scrollback: DEFAULT_SCROLLBACK,
terminal_cursor_style: DEFAULT_CURSOR_STYLE.into(),
ssh: None,
telnet: None,
raw: None,
github: None,
vnc: None,
irc: None,
serial: None,
};
match protocol {
Protocol::Ssh => {
p.port = DEFAULT_SSH_PORT.into();
p.ssh = Some(SshConfig::defaults());
}
Protocol::Telnet => {
p.port = DEFAULT_TELNET_PORT.into();
p.telnet = Some(TelnetConfig::defaults());
}
Protocol::Raw => {
p.port = DEFAULT_RAW_PORT.into();
p.raw = Some(RawConfig::defaults());
}
Protocol::Github => {
p.port = DEFAULT_GITHUB_PORT.into();
p.host = "github.com".into();
p.github = Some(GitHubConfig::defaults());
}
Protocol::Vnc => {
p.port = DEFAULT_VNC_PORT.into();
p.vnc = Some(VncConfig::defaults());
}
Protocol::Irc => {
p.port = DEFAULT_IRC_PORT.into();
p.irc = Some(IrcConfig::defaults());
}
Protocol::Serial => p.serial = Some(SerialConfig::default()),
Protocol::Local => {}
}
p
}
/// Fills missing fields the way Go's NormalizeProfileDefaults did.
pub fn normalize(&mut self) {
let d = Profile::new(self.protocol);
if self.id.is_empty() {
self.id = d.id.clone();
}
if self.connect_timeout == 0 {
self.connect_timeout = DEFAULT_CONNECT_TIMEOUT;
}
if self.terminal_font_family.is_empty() {
self.terminal_font_family = DEFAULT_FONT_FAMILY.into();
}
if self.terminal_font_size == 0 {
self.terminal_font_size = DEFAULT_FONT_SIZE;
}
if self.terminal_scrollback == 0 {
self.terminal_scrollback = DEFAULT_SCROLLBACK;
}
if self.terminal_cursor_style.is_empty() {
self.terminal_cursor_style = DEFAULT_CURSOR_STYLE.into();
}
if self.created_at == zero_time() {
self.created_at = d.created_at;
}
if self.updated_at == zero_time() {
self.updated_at = d.updated_at;
}
if self.port == 0 && self.protocol != Protocol::Serial {
self.port = d.port;
}
if self.protocol == Protocol::Github && self.host.is_empty() {
self.host = d.host.clone();
}
if self.display_name.is_empty() {
self.display_name = self.default_name();
}
match self.protocol {
Protocol::Ssh => {
let c = self.ssh.get_or_insert_with(SshConfig::defaults);
if c.auth_methods.is_empty() {
c.auth_methods = SshConfig::defaults().auth_methods;
}
if c.terminal_type.is_empty() {
c.terminal_type = DEFAULT_TERMINAL_TYPE.into();
}
if c.keepalive_interval == 0 {
c.keepalive_interval = DEFAULT_KEEPALIVE_INTERVAL;
}
if c.keepalive_count_max == 0 {
c.keepalive_count_max = DEFAULT_KEEPALIVE_COUNT_MAX;
}
}
Protocol::Telnet => {
let c = self.telnet.get_or_insert_with(TelnetConfig::defaults);
if c.terminal_type.is_empty() {
c.terminal_type = DEFAULT_TERMINAL_TYPE.into();
}
if c.encoding.is_empty() {
c.encoding = "utf-8".into();
}
}
Protocol::Raw => {
let c = self.raw.get_or_insert_with(RawConfig::defaults);
if c.encoding.is_empty() {
c.encoding = "utf-8".into();
}
for p in &mut c.payload_presets {
if p.id.is_empty() {
p.id = new_id();
}
if p.format.is_empty() {
p.format = "text".into();
}
}
}
Protocol::Github => {
let c = self.github.get_or_insert_with(GitHubConfig::defaults);
if c.base_url.is_empty() {
c.base_url = "https://api.github.com".into();
}
if c.refresh_interval_secs == 0 {
c.refresh_interval_secs = 300;
}
}
Protocol::Vnc => {
let c = self.vnc.get_or_insert_with(VncConfig::defaults);
if c.vnc_port == 0 {
c.vnc_port = DEFAULT_VNC_PORT.into();
}
if c.quality < 0 {
c.quality = 6;
}
if c.ssh_port == 0 {
c.ssh_port = DEFAULT_SSH_PORT.into();
}
if c.ssh_auth_method.is_empty() {
c.ssh_auth_method = "publickey".into();
}
}
Protocol::Irc => {
let c = self.irc.get_or_insert_with(IrcConfig::defaults);
if c.nick.is_empty() {
c.nick = "nexus".into();
}
if c.real_name.is_empty() {
c.real_name = "Nexus Terminal".into();
}
if c.ssh_port == 0 {
c.ssh_port = DEFAULT_SSH_PORT.into();
}
if c.ssh_auth_method.is_empty() {
c.ssh_auth_method = "publickey".into();
}
}
Protocol::Serial => {
self.serial.get_or_insert_with(SerialConfig::default);
}
Protocol::Local => {}
}
}
fn default_name(&self) -> String {
if !self.host.is_empty() {
return self.host.clone();
}
if let Some(v) = &self.vnc
&& !v.vnc_host.is_empty()
{
return v.vnc_host.clone();
}
match self.protocol {
Protocol::Github => "GitHub".into(),
p => p.label().into(),
}
}
/// The default tab name (Go's createTabName).
pub fn tab_name(&self) -> String {
if !self.display_name.is_empty() {
self.display_name.clone()
} else if !self.username.is_empty() {
format!("{}@{}", self.username, self.host)
} else {
self.host.clone()
}
}
pub fn auto_reconnect(&self) -> bool {
match self.protocol {
Protocol::Ssh => self.ssh.as_ref().is_some_and(|c| c.auto_reconnect),
Protocol::Telnet => self.telnet.as_ref().is_some_and(|c| c.auto_reconnect),
Protocol::Raw => self.raw.as_ref().is_some_and(|c| c.auto_reconnect),
Protocol::Irc => self.irc.as_ref().is_some_and(|c| c.auto_reconnect),
Protocol::Serial => self.serial.as_ref().is_some_and(|c| c.auto_reconnect),
_ => false,
}
}
pub fn max_reconnect_tries(&self) -> i64 {
let n = match self.protocol {
Protocol::Ssh => self.ssh.as_ref().map_or(0, |c| c.max_reconnect_tries),
Protocol::Telnet => self.telnet.as_ref().map_or(0, |c| c.max_reconnect_tries),
Protocol::Irc => self.irc.as_ref().map_or(0, |c| c.max_reconnect_tries),
Protocol::Serial => self.serial.as_ref().map_or(0, |c| c.max_reconnect_tries),
_ => 0,
};
if n > 0 { n } else { 5 }
}
}
impl SshConfig {
pub fn defaults() -> Self {
SshConfig {
auth_methods: vec!["publickey".into(), "password".into(), "keyboard-interactive".into()],
terminal_type: DEFAULT_TERMINAL_TYPE.into(),
keepalive_interval: DEFAULT_KEEPALIVE_INTERVAL,
keepalive_count_max: DEFAULT_KEEPALIVE_COUNT_MAX,
..Default::default()
}
}
}
impl TelnetConfig {
pub fn defaults() -> Self {
TelnetConfig { terminal_type: DEFAULT_TERMINAL_TYPE.into(), encoding: "utf-8".into(), ..Default::default() }
}
}
impl RawConfig {
pub fn defaults() -> Self {
RawConfig { encoding: "utf-8".into(), ..Default::default() }
}
}
impl GitHubConfig {
pub fn defaults() -> Self {
GitHubConfig { base_url: "https://api.github.com".into(), refresh_interval_secs: 300, ..Default::default() }
}
}
impl VncConfig {
pub fn defaults() -> Self {
VncConfig {
vnc_port: DEFAULT_VNC_PORT.into(),
scale_to_fit: true,
shared_session: true,
quality: 6,
ssh_port: DEFAULT_SSH_PORT.into(),
ssh_auth_method: "publickey".into(),
..Default::default()
}
}
}
impl IrcConfig {
pub fn defaults() -> Self {
IrcConfig {
nick: "nexus".into(),
real_name: "Nexus Terminal".into(),
server_password_mode: SecretMode::AskEveryTime,
ssh_port: DEFAULT_SSH_PORT.into(),
ssh_auth_method: "publickey".into(),
auto_reconnect: true,
..Default::default()
}
}
}