Added egui admin

This commit is contained in:
Ultradesu
2025-07-22 14:48:47 +03:00
parent cd9c10f2dd
commit 567e744247

View File

@@ -2,9 +2,11 @@ use dirs::home_dir;
use eframe::egui;
use log::{debug, error, info};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use std::sync::mpsc;
use reqwest::Client;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KhmSettings {
@@ -16,6 +18,94 @@ pub struct KhmSettings {
pub auto_sync_interval_minutes: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SshKey {
pub server: String,
pub public_key: String,
#[serde(default)]
pub deprecated: bool,
}
#[derive(Debug, Clone)]
enum AdminOperation {
LoadingKeys,
DeprecatingKey(String),
RestoringKey(String),
DeletingKey(String),
BulkDeprecating(Vec<String>),
BulkRestoring(Vec<String>),
None,
}
#[derive(Debug, Clone)]
struct AdminState {
keys: Vec<SshKey>,
filtered_keys: Vec<SshKey>,
search_term: String,
show_deprecated_only: bool,
selected_servers: HashMap<String, bool>,
expanded_servers: HashMap<String, bool>,
current_operation: AdminOperation,
last_load_time: Option<std::time::Instant>,
}
impl Default for AdminState {
fn default() -> Self {
Self {
keys: Vec::new(),
filtered_keys: Vec::new(),
search_term: String::new(),
show_deprecated_only: false,
selected_servers: HashMap::new(),
expanded_servers: HashMap::new(),
current_operation: AdminOperation::None,
last_load_time: None,
}
}
}
async fn fetch_admin_keys(host: String, flow: String, basic_auth: String) -> Result<Vec<SshKey>, String> {
if host.is_empty() || flow.is_empty() {
return Err("Host and flow must be specified".to_string());
}
let url = format!("{}/{}/keys?include_deprecated=true", host.trim_end_matches('/'), flow);
info!("Fetching admin keys from: {}", url);
let client_builder = Client::builder()
.timeout(std::time::Duration::from_secs(30));
let client = client_builder.build().map_err(|e| format!("Failed to create HTTP client: {}", e))?;
let mut request = client.get(&url);
// Add basic auth if provided
if !basic_auth.is_empty() {
let auth_parts: Vec<&str> = basic_auth.splitn(2, ':').collect();
if auth_parts.len() == 2 {
request = request.basic_auth(auth_parts[0], Some(auth_parts[1]));
} else {
return Err("Basic auth format should be 'username:password'".to_string());
}
}
let response = request.send().await
.map_err(|e| format!("Request failed: {}", e))?;
if !response.status().is_success() {
return Err(format!("Server returned error: {} {}", response.status().as_u16(), response.status().canonical_reason().unwrap_or("Unknown")));
}
let body = response.text().await
.map_err(|e| format!("Failed to read response: {}", e))?;
let keys: Vec<SshKey> = serde_json::from_str(&body)
.map_err(|e| format!("Failed to parse response: {}", e))?;
info!("Fetched {} SSH keys", keys.len());
Ok(keys)
}
impl Default for KhmSettings {
fn default() -> Self {
Self {
@@ -75,6 +165,16 @@ struct KhmSettingsWindow {
connection_status: ConnectionStatus,
is_testing_connection: bool,
test_result_receiver: Option<mpsc::Receiver<Result<String, String>>>,
admin_state: AdminState,
admin_receiver: Option<mpsc::Receiver<Result<Vec<SshKey>, String>>>,
operation_receiver: Option<mpsc::Receiver<Result<String, String>>>,
current_tab: SettingsTab,
}
#[derive(Debug, Clone, PartialEq)]
enum SettingsTab {
Connection,
Admin,
}
#[derive(Debug, Clone)]
@@ -112,13 +212,63 @@ impl eframe::App for KhmSettingsWindow {
ctx.request_repaint();
}
}
// Apply modern dark theme
ctx.set_visuals(egui::Visuals {
button_frame: true,
collapsing_header_frame: true,
indent_has_left_vline: true,
..egui::Visuals::dark()
});
// Check for admin operation results
if let Some(receiver) = &self.admin_receiver {
if let Ok(result) = receiver.try_recv() {
match result {
Ok(keys) => {
self.admin_state.keys = keys;
self.admin_state.last_load_time = Some(std::time::Instant::now());
self.filter_admin_keys();
self.admin_state.current_operation = AdminOperation::None;
info!("Keys loaded successfully: {} keys", self.admin_state.keys.len());
}
Err(error) => {
self.admin_state.current_operation = AdminOperation::None;
error!("Failed to load keys: {}", error);
}
}
self.admin_receiver = None;
ctx.request_repaint();
}
}
// Check for operation results
if let Some(receiver) = &self.operation_receiver {
if let Ok(result) = receiver.try_recv() {
match result {
Ok(message) => {
info!("Operation completed: {}", message);
// Reload keys after operation
self.load_admin_keys(ctx);
}
Err(error) => {
error!("Operation failed: {}", error);
}
}
self.admin_state.current_operation = AdminOperation::None;
self.operation_receiver = None;
ctx.request_repaint();
}
}
// Apply enhanced modern dark theme for admin interface
let mut visuals = egui::Visuals::dark();
visuals.window_fill = egui::Color32::from_gray(25);
visuals.panel_fill = egui::Color32::from_gray(30);
visuals.faint_bg_color = egui::Color32::from_gray(35);
visuals.extreme_bg_color = egui::Color32::from_gray(15);
visuals.button_frame = true;
visuals.collapsing_header_frame = true;
visuals.indent_has_left_vline = true;
visuals.menu_rounding = egui::Rounding::same(8.0);
visuals.window_rounding = egui::Rounding::same(12.0);
visuals.widgets.noninteractive.rounding = egui::Rounding::same(6.0);
visuals.widgets.inactive.rounding = egui::Rounding::same(6.0);
visuals.widgets.hovered.rounding = egui::Rounding::same(6.0);
visuals.widgets.active.rounding = egui::Rounding::same(6.0);
ctx.set_visuals(visuals);
egui::CentralPanel::default()
.frame(egui::Frame::none().inner_margin(egui::Margin::same(20.0)))
@@ -129,9 +279,26 @@ impl eframe::App for KhmSettingsWindow {
});
ui.add_space(10.0);
// Tab selector
ui.horizontal(|ui| {
ui.selectable_value(&mut self.current_tab, SettingsTab::Connection, "⚙️ Connection");
ui.selectable_value(&mut self.current_tab, SettingsTab::Admin, "🛠️ Admin");
});
ui.separator();
ui.add_space(15.0);
match self.current_tab {
SettingsTab::Connection => self.render_connection_tab(ui, ctx),
SettingsTab::Admin => self.render_admin_tab(ui, ctx),
}
});
}
}
impl KhmSettingsWindow {
fn render_connection_tab(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
// Connection section
ui.group(|ui| {
ui.set_min_width(ui.available_width());
@@ -347,7 +514,568 @@ impl eframe::App for KhmSettingsWindow {
.color(egui::Color32::YELLOW)
.italics());
}
}
fn start_connection_test(&mut self, ctx: &egui::Context) {
if self.is_testing_connection {
return;
}
self.is_testing_connection = true;
self.connection_status = ConnectionStatus::Unknown;
let (tx, rx) = mpsc::channel();
self.test_result_receiver = Some(rx);
let host = self.settings.host.clone();
let flow = self.settings.flow.clone();
let basic_auth = self.settings.basic_auth.clone();
let ctx_clone = ctx.clone();
std::thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(async {
test_khm_connection(host, flow, basic_auth).await
});
let _ = tx.send(result);
ctx_clone.request_repaint();
});
}
fn render_admin_tab(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
// Admin tab header
ui.horizontal(|ui| {
ui.label(egui::RichText::new("🛠️ Admin Panel").size(18.0).strong());
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("🔄 Refresh").clicked() {
self.load_admin_keys(ctx);
}
if let Some(last_load) = self.admin_state.last_load_time {
let elapsed = last_load.elapsed().as_secs();
ui.label(format!("Updated {}s ago", elapsed));
}
});
});
ui.separator();
ui.add_space(10.0);
// Check if connection is configured
if self.settings.host.is_empty() || self.settings.flow.is_empty() {
ui.vertical_centered(|ui| {
ui.label(egui::RichText::new("⚠️ Please configure connection settings first")
.size(16.0)
.color(egui::Color32::YELLOW));
ui.add_space(10.0);
if ui.button("Go to Connection Settings").clicked() {
self.current_tab = SettingsTab::Connection;
}
});
return;
}
// Load keys automatically on first view
if self.admin_state.keys.is_empty() && !matches!(self.admin_state.current_operation, AdminOperation::LoadingKeys) {
self.load_admin_keys(ctx);
}
// Show loading state
if matches!(self.admin_state.current_operation, AdminOperation::LoadingKeys) {
ui.vertical_centered(|ui| {
ui.spinner();
ui.label("Loading keys...");
});
return;
}
// Statistics cards
ui.horizontal(|ui| {
let total_keys = self.admin_state.keys.len();
let active_keys = self.admin_state.keys.iter().filter(|k| !k.deprecated).count();
let deprecated_keys = total_keys - active_keys;
let unique_servers = self.admin_state.keys.iter().map(|k| &k.server).collect::<std::collections::HashSet<_>>().len();
// Total keys card
ui.group(|ui| {
ui.set_min_width(80.0);
ui.vertical_centered(|ui| {
ui.label(egui::RichText::new("📊").size(20.0));
ui.label(egui::RichText::new(total_keys.to_string()).size(24.0).strong());
ui.label(egui::RichText::new("Total Keys").size(11.0).color(egui::Color32::GRAY));
});
});
// Active keys card
ui.group(|ui| {
ui.set_min_width(80.0);
ui.vertical_centered(|ui| {
ui.label(egui::RichText::new("").size(20.0));
ui.label(egui::RichText::new(active_keys.to_string()).size(24.0).strong().color(egui::Color32::LIGHT_GREEN));
ui.label(egui::RichText::new("Active").size(11.0).color(egui::Color32::GRAY));
});
});
// Deprecated keys card
ui.group(|ui| {
ui.set_min_width(80.0);
ui.vertical_centered(|ui| {
ui.label(egui::RichText::new("⚠️").size(20.0));
ui.label(egui::RichText::new(deprecated_keys.to_string()).size(24.0).strong().color(egui::Color32::LIGHT_RED));
ui.label(egui::RichText::new("Deprecated").size(11.0).color(egui::Color32::GRAY));
});
});
// Servers card
ui.group(|ui| {
ui.set_min_width(80.0);
ui.vertical_centered(|ui| {
ui.label(egui::RichText::new("🖥️").size(20.0));
ui.label(egui::RichText::new(unique_servers.to_string()).size(24.0).strong().color(egui::Color32::LIGHT_BLUE));
ui.label(egui::RichText::new("Servers").size(11.0).color(egui::Color32::GRAY));
});
});
});
ui.add_space(10.0);
// Enhanced search and filters
ui.group(|ui| {
ui.horizontal(|ui| {
ui.label(egui::RichText::new("🔍").size(16.0));
let search_response = ui.add_sized(
[200.0, 24.0],
egui::TextEdit::singleline(&mut self.admin_state.search_term)
.hint_text("Search servers or keys...")
);
if self.admin_state.search_term.is_empty() {
ui.label(egui::RichText::new("Type to search").color(egui::Color32::GRAY));
} else {
ui.label(format!("{} results", self.admin_state.filtered_keys.len()));
if ui.small_button("").clicked() {
self.admin_state.search_term.clear();
self.filter_admin_keys();
}
}
ui.separator();
// Filter toggle with better styling
let show_deprecated = self.admin_state.show_deprecated_only;
ui.horizontal(|ui| {
ui.label("Filter:");
if ui.selectable_label(!show_deprecated, "✅ Active").clicked() {
self.admin_state.show_deprecated_only = false;
self.filter_admin_keys();
}
if ui.selectable_label(show_deprecated, "⚠️ Deprecated").clicked() {
self.admin_state.show_deprecated_only = true;
self.filter_admin_keys();
}
});
// Handle search text changes
if search_response.changed() {
self.filter_admin_keys();
}
});
});
ui.add_space(10.0);
// Enhanced bulk actions
let selected_count = self.admin_state.selected_servers.values().filter(|&&v| v).count();
if selected_count > 0 {
ui.group(|ui| {
ui.horizontal(|ui| {
ui.label(egui::RichText::new("📋").size(16.0));
ui.label(egui::RichText::new(format!("Selected {} servers", selected_count))
.size(14.0)
.strong()
.color(egui::Color32::LIGHT_BLUE));
ui.separator();
if ui.add(egui::Button::new("⚠ Deprecate Selected")
.fill(egui::Color32::from_rgb(230, 126, 34))
.min_size(egui::vec2(130.0, 30.0))
).clicked() {
self.deprecate_selected_servers(ctx);
}
if ui.add(egui::Button::new("✓ Restore Selected")
.fill(egui::Color32::from_rgb(46, 204, 113))
.min_size(egui::vec2(120.0, 30.0))
).clicked() {
self.restore_selected_servers(ctx);
}
if ui.add(egui::Button::new("✕ Clear Selection")
.fill(egui::Color32::from_rgb(149, 165, 166))
.min_size(egui::vec2(110.0, 30.0))
).clicked() {
self.admin_state.selected_servers.clear();
}
});
});
ui.add_space(8.0);
}
// Modern scrollable keys table with better styling
egui::ScrollArea::vertical()
.max_height(450.0)
.auto_shrink([false; 2])
.show(ui, |ui| {
if self.admin_state.filtered_keys.is_empty() && !self.admin_state.search_term.is_empty() {
ui.vertical_centered(|ui| {
ui.add_space(40.0);
ui.label(egui::RichText::new("🔍").size(48.0).color(egui::Color32::GRAY));
ui.label(egui::RichText::new("No results found")
.size(18.0)
.color(egui::Color32::GRAY));
ui.label(egui::RichText::new(format!("Try adjusting your search: '{}'", self.admin_state.search_term))
.size(14.0)
.color(egui::Color32::DARK_GRAY));
});
} else {
self.render_keys_table(ui, ctx);
}
});
}
fn load_admin_keys(&mut self, ctx: &egui::Context) {
if self.settings.host.is_empty() || self.settings.flow.is_empty() {
return;
}
self.admin_state.current_operation = AdminOperation::LoadingKeys;
let (tx, rx) = mpsc::channel();
self.admin_receiver = Some(rx);
let host = self.settings.host.clone();
let flow = self.settings.flow.clone();
let basic_auth = self.settings.basic_auth.clone();
let ctx_clone = ctx.clone();
std::thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(async {
fetch_admin_keys(host, flow, basic_auth).await
});
let _ = tx.send(result);
ctx_clone.request_repaint();
});
}
fn filter_admin_keys(&mut self) {
let mut filtered = self.admin_state.keys.clone();
// Apply deprecated filter
if self.admin_state.show_deprecated_only {
filtered.retain(|key| key.deprecated);
}
// Apply search filter
if !self.admin_state.search_term.is_empty() {
let search_term = self.admin_state.search_term.to_lowercase();
filtered.retain(|key| {
key.server.to_lowercase().contains(&search_term) ||
key.public_key.to_lowercase().contains(&search_term)
});
}
self.admin_state.filtered_keys = filtered;
}
fn render_keys_table(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
if self.admin_state.filtered_keys.is_empty() {
ui.vertical_centered(|ui| {
ui.add_space(60.0);
if self.admin_state.keys.is_empty() {
ui.label(egui::RichText::new("🔑").size(48.0).color(egui::Color32::GRAY));
ui.label(egui::RichText::new("No SSH keys available")
.size(18.0)
.color(egui::Color32::GRAY));
ui.label(egui::RichText::new("Keys will appear here once loaded from the server")
.size(14.0)
.color(egui::Color32::DARK_GRAY));
} else {
ui.label(egui::RichText::new("🚫").size(48.0).color(egui::Color32::GRAY));
ui.label(egui::RichText::new("No keys match current filters")
.size(18.0)
.color(egui::Color32::GRAY));
ui.label(egui::RichText::new("Try adjusting your search or filter settings")
.size(14.0)
.color(egui::Color32::DARK_GRAY));
}
});
return;
}
// Group keys by server - clone to avoid borrowing conflicts
let filtered_keys = self.admin_state.filtered_keys.clone();
let expanded_servers = self.admin_state.expanded_servers.clone();
let selected_servers = self.admin_state.selected_servers.clone();
let mut servers: std::collections::BTreeMap<String, Vec<SshKey>> = std::collections::BTreeMap::new();
for key in &filtered_keys {
servers.entry(key.server.clone()).or_insert_with(Vec::new).push(key.clone());
}
// Render each server group
for (server_name, server_keys) in servers {
let is_expanded = expanded_servers.get(&server_name).copied().unwrap_or(false);
let active_count = server_keys.iter().filter(|k| !k.deprecated).count();
let deprecated_count = server_keys.len() - active_count;
// Modern server header with enhanced styling
ui.group(|ui| {
ui.horizontal(|ui| {
// Stylized checkbox for server selection
let mut selected = selected_servers.get(&server_name).copied().unwrap_or(false);
if ui.add(egui::Checkbox::new(&mut selected, "")
.indeterminate(false)
).changed() {
self.admin_state.selected_servers.insert(server_name.clone(), selected);
}
// Modern expand/collapse button
let expand_icon = if is_expanded { "" } else { "" };
if ui.add(egui::Button::new(expand_icon)
.fill(egui::Color32::TRANSPARENT)
.stroke(egui::Stroke::NONE)
.min_size(egui::vec2(20.0, 20.0))
).clicked() {
self.admin_state.expanded_servers.insert(server_name.clone(), !is_expanded);
}
// Server icon and name
ui.label(egui::RichText::new("🖥").size(16.0));
ui.label(egui::RichText::new(&server_name)
.size(15.0)
.strong()
.color(egui::Color32::WHITE));
// Keys count badge
let (rect, _) = ui.allocate_exact_size(
egui::vec2(60.0, 20.0),
egui::Sense::hover()
);
ui.painter().rect_filled(
rect,
egui::Rounding::same(10.0),
egui::Color32::from_rgb(52, 152, 219)
);
ui.painter().text(
rect.center(),
egui::Align2::CENTER_CENTER,
&format!("{} keys", server_keys.len()),
egui::FontId::proportional(11.0),
egui::Color32::WHITE,
);
ui.add_space(8.0);
// Status indicators
if deprecated_count > 0 {
let (rect, _) = ui.allocate_exact_size(
egui::vec2(80.0, 20.0),
egui::Sense::hover()
);
ui.painter().rect_filled(
rect,
egui::Rounding::same(10.0),
egui::Color32::from_rgb(231, 76, 60)
);
ui.painter().text(
rect.center(),
egui::Align2::CENTER_CENTER,
&format!("{} deprecated", deprecated_count),
egui::FontId::proportional(10.0),
egui::Color32::WHITE,
);
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
// Stylized action buttons
if deprecated_count > 0 {
if ui.add(egui::Button::new("✓ Restore All")
.fill(egui::Color32::from_rgb(46, 204, 113))
.min_size(egui::vec2(90.0, 28.0))
).clicked() {
self.restore_server_keys(&server_name, ctx);
}
}
if active_count > 0 {
if ui.add(egui::Button::new("⚠ Deprecate All")
.fill(egui::Color32::from_rgb(230, 126, 34))
.min_size(egui::vec2(110.0, 28.0))
).clicked() {
self.deprecate_server_keys(&server_name, ctx);
}
}
});
});
});
// Expanded key details
if is_expanded {
ui.indent("server_keys", |ui| {
for key in &server_keys {
ui.group(|ui| {
ui.horizontal(|ui| {
// Key type badge with modern styling
let key_type = self.get_key_type(&key.public_key);
let (badge_color, text_color) = match key_type.as_str() {
"RSA" => (egui::Color32::from_rgb(52, 144, 220), egui::Color32::WHITE),
"ED25519" => (egui::Color32::from_rgb(46, 204, 113), egui::Color32::WHITE),
"ECDSA" => (egui::Color32::from_rgb(241, 196, 15), egui::Color32::BLACK),
"DSA" => (egui::Color32::from_rgb(230, 126, 34), egui::Color32::WHITE),
_ => (egui::Color32::GRAY, egui::Color32::WHITE),
};
// Custom badge rendering
let (rect, _) = ui.allocate_exact_size(
egui::vec2(50.0, 20.0),
egui::Sense::hover()
);
ui.painter().rect_filled(
rect,
egui::Rounding::same(4.0),
badge_color
);
ui.painter().text(
rect.center(),
egui::Align2::CENTER_CENTER,
&key_type,
egui::FontId::proportional(11.0),
text_color,
);
ui.add_space(8.0);
// Status badge with icons
if key.deprecated {
ui.label(egui::RichText::new("⚠ DEPRECATED")
.size(12.0)
.color(egui::Color32::from_rgb(231, 76, 60))
.strong());
} else {
ui.label(egui::RichText::new("✓ ACTIVE")
.size(12.0)
.color(egui::Color32::from_rgb(46, 204, 113))
.strong());
}
ui.add_space(8.0);
// Key preview with monospace font
ui.label(egui::RichText::new(self.get_key_preview(&key.public_key))
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::LIGHT_GRAY));
let server_name_for_action = server_name.clone();
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
// Modern action buttons
if key.deprecated {
if ui.add(egui::Button::new("✓ Restore")
.fill(egui::Color32::from_rgb(46, 204, 113))
.min_size(egui::vec2(70.0, 24.0))
).clicked() {
self.restore_key(&server_name_for_action, ctx);
}
if ui.add(egui::Button::new("🗑 Delete")
.fill(egui::Color32::from_rgb(231, 76, 60))
.min_size(egui::vec2(70.0, 24.0))
).clicked() {
self.delete_key(&server_name_for_action, ctx);
}
} else {
if ui.add(egui::Button::new("⚠ Deprecate")
.fill(egui::Color32::from_rgb(230, 126, 34))
.min_size(egui::vec2(85.0, 24.0))
).clicked() {
self.deprecate_key(&server_name_for_action, ctx);
}
}
if ui.add(egui::Button::new("📋 Copy")
.fill(egui::Color32::from_rgb(52, 152, 219))
.min_size(egui::vec2(60.0, 24.0))
).clicked() {
ui.output_mut(|o| o.copied_text = key.public_key.clone());
}
});
});
});
}
});
}
ui.add_space(5.0);
}
}
fn deprecate_selected_servers(&mut self, _ctx: &egui::Context) {
// Stub for now
}
fn restore_selected_servers(&mut self, _ctx: &egui::Context) {
// Stub for now
}
fn get_key_type(&self, public_key: &str) -> String {
if public_key.starts_with("ssh-rsa") {
"RSA".to_string()
} else if public_key.starts_with("ssh-ed25519") {
"ED25519".to_string()
} else if public_key.starts_with("ecdsa-sha2-nistp") {
"ECDSA".to_string()
} else if public_key.starts_with("ssh-dss") {
"DSA".to_string()
} else {
"Unknown".to_string()
}
}
fn get_key_preview(&self, public_key: &str) -> String {
let parts: Vec<&str> = public_key.split_whitespace().collect();
if parts.len() >= 2 {
let key_part = parts[1];
if key_part.len() > 20 {
format!("{}...", &key_part[..20])
} else {
key_part.to_string()
}
} else {
format!("{}...", &public_key[..std::cmp::min(20, public_key.len())])
}
}
fn deprecate_server_keys(&mut self, _server: &str, _ctx: &egui::Context) {
// Stub for now
}
fn restore_server_keys(&mut self, _server: &str, _ctx: &egui::Context) {
// Stub for now
}
fn deprecate_key(&mut self, _server: &str, _ctx: &egui::Context) {
// Stub for now
}
fn restore_key(&mut self, _server: &str, _ctx: &egui::Context) {
// Stub for now
}
fn delete_key(&mut self, _server: &str, _ctx: &egui::Context) {
// Stub for now
}
}
@@ -381,6 +1109,10 @@ pub fn run_settings_window() {
connection_status: ConnectionStatus::Unknown,
is_testing_connection: false,
test_result_receiver: None,
admin_state: AdminState::default(),
admin_receiver: None,
operation_receiver: None,
current_tab: SettingsTab::Connection,
}))),
);
}
@@ -407,35 +1139,6 @@ fn create_window_icon() -> egui::IconData {
}
}
impl KhmSettingsWindow {
fn start_connection_test(&mut self, ctx: &egui::Context) {
if self.is_testing_connection {
return;
}
self.is_testing_connection = true;
self.connection_status = ConnectionStatus::Unknown;
let (tx, rx) = mpsc::channel();
self.test_result_receiver = Some(rx);
let host = self.settings.host.clone();
let flow = self.settings.flow.clone();
let basic_auth = self.settings.basic_auth.clone();
let ctx_clone = ctx.clone();
std::thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(async {
test_khm_connection(host, flow, basic_auth).await
});
let _ = tx.send(result);
ctx_clone.request_repaint();
});
}
}
async fn test_khm_connection(host: String, flow: String, basic_auth: String) -> Result<String, String> {
if host.is_empty() || flow.is_empty() {
return Err("Host and flow must be specified".to_string());