use crate::app::App;
use crate::app::Screen;
use crate::app::reload_state::{get_mtime, snapshot_include_dir_mtimes, snapshot_include_mtimes};
use crate::app::{HostForm, SnippetForm, TunnelForm};
use crate::snippet::Snippet;
use crate::ssh_config::model::PatternEntry;
use crate::tunnel::TunnelRule;
#[derive(Clone)]
pub struct FormBaseline {
pub alias: String,
pub hostname: String,
pub user: String,
pub port: String,
pub identity_file: String,
pub proxy_jump: String,
pub askpass: String,
pub vault_ssh: String,
pub vault_addr: String,
pub tags: String,
}
#[derive(Clone)]
pub struct TunnelFormBaseline {
pub tunnel_type: crate::tunnel::TunnelType,
pub bind_port: String,
pub remote_host: String,
pub remote_port: String,
pub bind_address: String,
}
#[derive(Clone)]
pub struct SnippetFormBaseline {
pub name: String,
pub command: String,
pub description: String,
}
#[derive(Clone)]
pub struct ProviderFormBaseline {
pub url: String,
pub token: String,
pub profile: String,
pub project: String,
pub compartment: String,
pub regions: String,
pub alias_prefix: String,
pub user: String,
pub identity_file: String,
pub verify_tls: bool,
pub auto_sync: bool,
pub vault_role: String,
pub vault_addr: String,
}
impl App {
pub fn clear_form_mtime(&mut self) {
self.conflict.form_mtime = None;
self.conflict.form_include_mtimes.clear();
self.conflict.form_include_dir_mtimes.clear();
self.conflict.provider_form_mtime = None;
}
pub fn capture_form_mtime(&mut self) {
self.conflict.form_mtime = get_mtime(&self.reload.config_path);
self.conflict.form_include_mtimes = snapshot_include_mtimes(&self.hosts_state.ssh_config);
self.conflict.form_include_dir_mtimes =
snapshot_include_dir_mtimes(&self.hosts_state.ssh_config);
}
pub fn capture_provider_form_mtime(&mut self) {
let path = dirs::home_dir().map(|h| h.join(".purple/providers"));
self.conflict.provider_form_mtime = path.as_ref().and_then(|p| get_mtime(p));
}
pub fn capture_form_baseline(&mut self) {
self.forms.host_baseline = Some(FormBaseline {
alias: self.forms.host.alias.clone(),
hostname: self.forms.host.hostname.clone(),
user: self.forms.host.user.clone(),
port: self.forms.host.port.clone(),
identity_file: self.forms.host.identity_file.clone(),
proxy_jump: self.forms.host.proxy_jump.clone(),
askpass: self.forms.host.askpass.clone(),
vault_ssh: self.forms.host.vault_ssh.clone(),
vault_addr: self.forms.host.vault_addr.clone(),
tags: self.forms.host.tags.clone(),
});
}
pub fn host_form_is_dirty(&self) -> bool {
match &self.forms.host_baseline {
Some(b) => {
self.forms.host.alias != b.alias
|| self.forms.host.hostname != b.hostname
|| self.forms.host.user != b.user
|| self.forms.host.port != b.port
|| self.forms.host.identity_file != b.identity_file
|| self.forms.host.proxy_jump != b.proxy_jump
|| self.forms.host.askpass != b.askpass
|| self.forms.host.vault_ssh != b.vault_ssh
|| self.forms.host.vault_addr != b.vault_addr
|| self.forms.host.tags != b.tags
}
None => false,
}
}
pub fn close_host_form(&mut self) {
self.close_host_form_inner(None);
}
pub fn close_host_form_after_save(&mut self, target_alias: &str) {
self.close_host_form_inner(Some(target_alias));
}
fn close_host_form_inner(&mut self, select: Option<&str>) {
log::debug!("[purple] close_host_form select={:?}", select);
self.clear_form_mtime();
self.forms.host_baseline = None;
self.set_screen(Screen::HostList);
if let Some(alias) = select {
self.select_host_by_alias(alias);
}
self.flush_pending_vault_write();
}
pub fn close_provider_form(&mut self) {
log::debug!("[purple] close_provider_form");
self.clear_form_mtime();
self.providers.form_baseline = None;
self.set_screen(Screen::Providers);
self.flush_pending_vault_write();
}
pub fn close_tunnel_form(&mut self, return_to: Screen) {
log::debug!(
"[purple] close_tunnel_form return_to={:?}",
std::mem::discriminant(&return_to)
);
self.clear_form_mtime();
self.tunnels.form_baseline = None;
self.set_screen(return_to);
}
pub fn close_snippet_form(&mut self, target_aliases: Vec<String>) {
log::debug!(
"[purple] close_snippet_form aliases={}",
target_aliases.len()
);
self.snippets.form_baseline = None;
self.set_screen(Screen::SnippetPicker { target_aliases });
}
pub fn open_host_add_form(&mut self) {
log::debug!("[purple] open_host_add_form");
self.forms.host = HostForm::new();
self.set_screen(Screen::AddHost);
self.capture_form_mtime();
self.capture_form_baseline();
}
pub fn open_host_pattern_add_form(&mut self) {
log::debug!("[purple] open_host_pattern_add_form");
self.forms.host = HostForm::new_pattern();
self.set_screen(Screen::AddHost);
self.capture_form_mtime();
self.capture_form_baseline();
}
pub fn open_host_edit_form(
&mut self,
host: crate::ssh_config::model::HostEntry,
stale_hint: Option<String>,
) -> bool {
if let Some(ref source) = host.source_file {
self.notify_error(crate::messages::included_host_lives_in(
&host.alias,
&source.display(),
));
return false;
}
let raw = match self.hosts_state.ssh_config.raw_host_entry(&host.alias) {
Some(entry) => entry,
None => {
self.notify_warning(crate::messages::HOST_NOT_FOUND_IN_CONFIG);
return false;
}
};
let inherited = self.hosts_state.ssh_config.inherited_hints(&host.alias);
log::debug!("[purple] open_host_edit_form alias={}", host.alias);
self.forms.host = HostForm::from_entry(&raw, inherited);
if let Some(hint) = stale_hint {
self.notify_warning(crate::messages::stale_host(&hint));
}
self.set_screen(Screen::EditHost { alias: host.alias });
self.capture_form_mtime();
self.capture_form_baseline();
true
}
pub fn open_host_pattern_edit_form(&mut self, pattern: &PatternEntry) {
log::debug!(
"[purple] open_host_pattern_edit_form pattern={}",
pattern.pattern
);
self.forms.host = HostForm::from_pattern_entry(pattern);
self.set_screen(Screen::EditHost {
alias: pattern.pattern.clone(),
});
self.capture_form_mtime();
self.capture_form_baseline();
}
pub fn open_tunnel_add_form(&mut self, alias: String) {
log::debug!("[purple] open_tunnel_add_form alias={}", alias);
self.tunnels.form = TunnelForm::new();
self.set_screen(Screen::TunnelForm {
alias,
editing: None,
});
self.capture_form_mtime();
self.capture_tunnel_form_baseline();
}
pub fn open_tunnel_edit_form(&mut self, alias: String, rule: &TunnelRule, editing: usize) {
log::debug!(
"[purple] open_tunnel_edit_form alias={} editing={}",
alias,
editing
);
self.tunnels.form = TunnelForm::from_rule(rule);
self.set_screen(Screen::TunnelForm {
alias,
editing: Some(editing),
});
self.capture_form_mtime();
self.capture_tunnel_form_baseline();
}
pub fn open_snippet_add_form(&mut self, target_aliases: Vec<String>) {
log::debug!(
"[purple] open_snippet_add_form aliases={}",
target_aliases.len()
);
self.snippets.form = SnippetForm::new();
self.set_screen(Screen::SnippetForm {
target_aliases,
editing: None,
});
self.capture_snippet_form_baseline();
}
pub fn open_snippet_edit_form(
&mut self,
snippet: &Snippet,
target_aliases: Vec<String>,
editing: usize,
) {
log::debug!(
"[purple] open_snippet_edit_form name={} editing={}",
snippet.name,
editing
);
self.snippets.form = SnippetForm::from_snippet(snippet);
self.set_screen(Screen::SnippetForm {
target_aliases,
editing: Some(editing),
});
self.capture_snippet_form_baseline();
}
pub fn open_provider_form(&mut self, id: crate::providers::config::ProviderConfigId) {
let provider_impl = crate::providers::get_provider(id.provider.as_str());
let short_label = provider_impl
.as_ref()
.map(|p| p.short_label().to_string())
.unwrap_or_else(|| id.provider.clone());
let existing_section = self.providers.config.section_by_id(&id).cloned();
let label_entry = existing_section.is_none() && id.label.as_deref() == Some("");
let provider_first_field =
crate::app::ProviderFormField::fields_for(id.provider.as_str())[0];
let first_field = if label_entry {
crate::app::ProviderFormField::Label
} else {
provider_first_field
};
log::debug!(
"[purple] open_provider_form provider={} label_entry={}",
id.provider,
label_entry
);
self.providers.form = if let Some(section) = existing_section {
let cursor_pos = match first_field {
crate::app::ProviderFormField::Url => section.url.chars().count(),
crate::app::ProviderFormField::Token => section.token.chars().count(),
_ => 0,
};
crate::app::ProviderFormFields {
label: String::new(),
label_entry: false,
url: section.url.clone(),
token: section.token.clone(),
profile: section.profile.clone(),
project: section.project.clone(),
compartment: section.compartment.clone(),
regions: section.regions.clone(),
alias_prefix: section.alias_prefix.clone(),
user: section.user.clone(),
identity_file: section.identity_file.clone(),
verify_tls: section.verify_tls,
auto_sync: section.auto_sync,
vault_role: section.vault_role.clone(),
vault_addr: section.vault_addr.clone(),
focused_field: first_field,
cursor_pos,
expanded: true,
}
} else {
let default_prefix = match id.label.as_deref() {
Some("") | None => short_label.clone(),
Some(l) => format!("{}-{}", short_label, l),
};
crate::app::ProviderFormFields {
label: String::new(),
label_entry,
url: String::new(),
token: String::new(),
profile: String::new(),
project: String::new(),
compartment: String::new(),
regions: String::new(),
alias_prefix: default_prefix,
user: "root".to_string(),
identity_file: String::new(),
verify_tls: true,
auto_sync: id
.kind()
.is_none_or(crate::providers::ProviderKind::default_auto_sync),
vault_role: String::new(),
vault_addr: String::new(),
focused_field: first_field,
cursor_pos: 0,
expanded: false,
}
};
self.set_screen(Screen::ProviderForm { id });
self.capture_provider_form_mtime();
self.capture_provider_form_baseline();
}
pub fn capture_tunnel_form_baseline(&mut self) {
self.tunnels.form_baseline = Some(TunnelFormBaseline {
tunnel_type: self.tunnels.form.tunnel_type,
bind_port: self.tunnels.form.bind_port.clone(),
remote_host: self.tunnels.form.remote_host.clone(),
remote_port: self.tunnels.form.remote_port.clone(),
bind_address: self.tunnels.form.bind_address.clone(),
});
}
pub fn tunnel_form_is_dirty(&self) -> bool {
match &self.tunnels.form_baseline {
Some(b) => {
self.tunnels.form.tunnel_type != b.tunnel_type
|| self.tunnels.form.bind_port != b.bind_port
|| self.tunnels.form.remote_host != b.remote_host
|| self.tunnels.form.remote_port != b.remote_port
|| self.tunnels.form.bind_address != b.bind_address
}
None => false,
}
}
pub fn capture_snippet_form_baseline(&mut self) {
self.snippets.form_baseline = Some(SnippetFormBaseline {
name: self.snippets.form.name.clone(),
command: self.snippets.form.command.clone(),
description: self.snippets.form.description.clone(),
});
}
pub fn snippet_form_is_dirty(&self) -> bool {
match &self.snippets.form_baseline {
Some(b) => {
self.snippets.form.name != b.name
|| self.snippets.form.command != b.command
|| self.snippets.form.description != b.description
}
None => false,
}
}
pub fn capture_provider_form_baseline(&mut self) {
self.providers.form_baseline = Some(ProviderFormBaseline {
url: self.providers.form.url.clone(),
token: self.providers.form.token.clone(),
profile: self.providers.form.profile.clone(),
project: self.providers.form.project.clone(),
compartment: self.providers.form.compartment.clone(),
regions: self.providers.form.regions.clone(),
alias_prefix: self.providers.form.alias_prefix.clone(),
user: self.providers.form.user.clone(),
identity_file: self.providers.form.identity_file.clone(),
verify_tls: self.providers.form.verify_tls,
auto_sync: self.providers.form.auto_sync,
vault_role: self.providers.form.vault_role.clone(),
vault_addr: self.providers.form.vault_addr.clone(),
});
}
pub fn provider_form_is_dirty(&self) -> bool {
match &self.providers.form_baseline {
Some(b) => {
self.providers.form.url != b.url
|| self.providers.form.token != b.token
|| self.providers.form.profile != b.profile
|| self.providers.form.project != b.project
|| self.providers.form.compartment != b.compartment
|| self.providers.form.regions != b.regions
|| self.providers.form.alias_prefix != b.alias_prefix
|| self.providers.form.user != b.user
|| self.providers.form.identity_file != b.identity_file
|| self.providers.form.verify_tls != b.verify_tls
|| self.providers.form.auto_sync != b.auto_sync
|| self.providers.form.vault_role != b.vault_role
|| self.providers.form.vault_addr != b.vault_addr
}
None => false,
}
}
pub fn config_changed_since_form_open(&self) -> bool {
match self.conflict.form_mtime {
Some(open_mtime) => {
if get_mtime(&self.reload.config_path) != Some(open_mtime) {
return true;
}
self.conflict
.form_include_mtimes
.iter()
.any(|(path, old_mtime)| get_mtime(path) != *old_mtime)
|| self
.conflict
.form_include_dir_mtimes
.iter()
.any(|(path, old_mtime)| get_mtime(path) != *old_mtime)
}
None => false,
}
}
pub fn provider_config_changed_since_form_open(&self) -> bool {
let path = dirs::home_dir().map(|h| h.join(".purple/providers"));
let current_mtime = path.as_ref().and_then(|p| get_mtime(p));
self.conflict.provider_form_mtime != current_mtime
}
}