Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/service/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod ssh_config;
pub mod ssh_keys;

pub use ssh_config::{SshServer, load_ssh_servers};
pub use ssh_config::{SshServer, delete_ssh_server, load_ssh_servers};
pub use ssh_keys::{delete_key_pair, load_ssh_keys, read_key_content, regenerate_public_key};
59 changes: 59 additions & 0 deletions src/service/ssh_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,62 @@ fn parse_ssh_config(content: &str) -> Result<Vec<SshServer>, Box<dyn std::error:

Ok(servers)
}

pub fn delete_ssh_server(server_name: &str) -> Result<(), Box<dyn std::error::Error>> {
let home_dir = std::env::var("HOME")?;
let config_path = Path::new(&home_dir).join(".ssh").join("config");

if !config_path.exists() {
return Err("SSH config file does not exist".into());
}

let content = fs::read_to_string(&config_path)?;
let new_content = remove_server_from_config(&content, server_name)?;

fs::write(&config_path, new_content)?;
Ok(())
}

fn remove_server_from_config(
content: &str,
server_name: &str,
) -> Result<String, Box<dyn std::error::Error>> {
let lines: Vec<&str> = content.lines().collect();
let mut new_lines = Vec::new();
let mut skip_section = false;
let mut in_host_section = false;

for line in lines {
let trimmed_line = line.trim();

if trimmed_line.to_lowercase().starts_with("host ") {
let host_name = trimmed_line[5..].trim();

if host_name == server_name {
skip_section = true;
in_host_section = true;
continue;
} else {
skip_section = false;
in_host_section = true;
}
} else if trimmed_line.is_empty() && in_host_section {
if skip_section {
skip_section = false;
in_host_section = false;
continue;
}
in_host_section = false;
} else if !trimmed_line.is_empty() && !trimmed_line.starts_with('#') && in_host_section {
if skip_section {
continue;
}
}

if !skip_section {
new_lines.push(line);
}
}

Ok(new_lines.join("\n"))
}
57 changes: 38 additions & 19 deletions src/ui/component/server_card.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::service::SshServer;
use crate::ui::modal::edit_server::create_edit_server_dialog;
use crate::ui::modal::{
delete_server::create_delete_server_dialog, edit_server::create_edit_server_dialog,
};
use gtk4::prelude::*;
use gtk4::{Box, Button, Frame, Image, Label, Orientation};
use gtk4::{Box as GtkBox, Button, Frame, Image, Label, Orientation};
use libadwaita::prelude::AdwDialogExt;
use std::process::Command;
use std::rc::Rc;
Expand All @@ -23,20 +25,20 @@ pub fn create_server_card(
card.set_margin_bottom(8);
card.set_width_request(300);

let main_container = Box::new(Orientation::Vertical, 12);
let main_container = GtkBox::new(Orientation::Vertical, 12);
main_container.set_margin_top(16);
main_container.set_margin_bottom(16);
main_container.set_margin_start(16);
main_container.set_margin_end(16);

let header_container = Box::new(Orientation::Horizontal, 12);
let header_container = GtkBox::new(Orientation::Horizontal, 12);
header_container.set_halign(gtk4::Align::Start);

let server_icon = Image::from_icon_name("network-server-symbolic");
server_icon.set_icon_size(gtk4::IconSize::Large);
header_container.append(&server_icon);

let title_container = Box::new(Orientation::Vertical, 4);
let title_container = GtkBox::new(Orientation::Vertical, 4);
title_container.set_halign(gtk4::Align::Start);
title_container.set_hexpand(true);
title_container.set_margin_end(16);
Expand All @@ -56,7 +58,7 @@ pub fn create_server_card(

let is_special_host = server.name == "aur.archlinux.org";

let actions_container = Box::new(Orientation::Horizontal, 8);
let actions_container = GtkBox::new(Orientation::Horizontal, 8);
actions_container.set_halign(gtk4::Align::End);
actions_container.set_valign(gtk4::Align::Center);

Expand Down Expand Up @@ -85,11 +87,24 @@ pub fn create_server_card(
}
actions_container.append(&edit_button);

let delete_button = Button::builder()
.icon_name("user-trash-symbolic")
.css_classes(vec!["circular", "flat", "destructive-action"])
.build();

if is_special_host {
delete_button.set_sensitive(false);
delete_button.set_tooltip_text(Some("This host is special and can't be deleted"));
} else {
delete_button.set_tooltip_text(Some("Delete server"));
}
actions_container.append(&delete_button);

header_container.append(&actions_container);
main_container.append(&header_container);

if let Some(port) = server.port {
let details_container = Box::new(Orientation::Vertical, 6);
let details_container = GtkBox::new(Orientation::Vertical, 6);
details_container.set_margin_top(8);

let port_label = Label::new(Some(&format!("Port: {}", port)));
Expand Down Expand Up @@ -121,10 +136,6 @@ pub fn create_server_card(

match result {
Ok(_) => {
println!(
"Opening SSH connection to {} in {}",
server_name_clone, terminal
);
success = true;
break;
}
Expand All @@ -141,12 +152,7 @@ pub fn create_server_card(
.spawn();

match fallback_result {
Ok(_) => {
println!(
"Opening SSH connection to {} using fallback method",
server_name_clone
);
}
Ok(_) => {}
Err(e) => {
eprintln!("Failed to open any terminal for SSH connection: {}", e);
}
Expand All @@ -158,8 +164,6 @@ pub fn create_server_card(
let parent_window_clone = parent_window.cloned();
let on_save_clone = on_save.clone();
edit_button.connect_clicked(move |_| {
println!("Edit button clicked for server: {}", server_clone.name);
println!("on_save_clone is: {:?}", on_save_clone.is_some());
let edit_dialog = create_edit_server_dialog(
&server_clone,
on_save_clone.as_ref().map(|f| {
Expand All @@ -174,6 +178,21 @@ pub fn create_server_card(
}
});

let server_clone_for_delete = server.clone();
let parent_window_clone_for_delete = parent_window.cloned();
let on_save_clone_for_delete = on_save.clone();
delete_button.connect_clicked(move |_| {
let delete_dialog = create_delete_server_dialog(
&server_clone_for_delete.name,
on_save_clone_for_delete.clone(),
);
if let Some(parent) = &parent_window_clone_for_delete {
delete_dialog.present(Some(parent));
} else {
delete_dialog.show();
}
});

card.set_child(Some(&main_container));

if is_special_host {
Expand Down
40 changes: 40 additions & 0 deletions src/ui/modal/delete_server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use crate::service::delete_ssh_server;
use libadwaita::prelude::*;
use libadwaita::{AlertDialog, ResponseAppearance};
use std::rc::Rc;

pub fn create_delete_server_dialog(
server_name: &str,
on_delete: Option<Rc<dyn Fn() + 'static>>,
) -> AlertDialog {
let dialog = AlertDialog::builder()
.heading("Delete Server")
.body(&format!(
"Are you sure you want to delete the server \"{}\" ?\n\nThis action is irreversible.",
server_name
))
.build();

dialog.add_response("cancel", "Cancel");
dialog.add_response("delete", "Delete");

dialog.set_response_appearance("delete", ResponseAppearance::Destructive);
dialog.set_default_response(Some("cancel"));
dialog.set_close_response("cancel");

let server_name = server_name.to_string();
dialog.connect_response(None, move |_, response| {
if response == "delete" {
match delete_ssh_server(&server_name) {
Ok(_) => {
if let Some(refresh_fn) = &on_delete {
refresh_fn();
}
}
Err(e) => eprintln!("Error deleting server '{}': {}", server_name, e),
}
}
});

dialog
}
1 change: 1 addition & 0 deletions src/ui/modal/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod about;
pub mod add_server;
pub mod delete_key;
pub mod delete_server;
pub mod edit_server;
pub mod info_key;