Fixed build workflow

This commit is contained in:
Alexandr Bogomiakov
2025-07-23 23:53:46 +03:00
parent 201d008d81
commit 977d67cbf0
15 changed files with 1467 additions and 880 deletions

View File

@@ -1,15 +1,15 @@
use crate::gui::api::{fetch_keys, SshKey};
use crate::gui::common::KhmSettings;
use eframe::egui;
use log::{error, info};
use std::collections::HashMap;
use std::sync::mpsc;
use crate::gui::api::{SshKey, fetch_keys};
use crate::gui::common::KhmSettings;
#[derive(Debug, Clone)]
pub enum AdminOperation {
LoadingKeys,
DeprecatingKey,
RestoringKey,
RestoringKey,
DeletingKey,
BulkDeprecating,
BulkRestoring,
@@ -47,52 +47,54 @@ impl AdminState {
/// Filter keys based on current search term and deprecated filter
pub fn filter_keys(&mut self) {
let mut filtered = self.keys.clone();
// Apply deprecated filter
if self.show_deprecated_only {
filtered.retain(|key| key.deprecated);
}
// Apply search filter
if !self.search_term.is_empty() {
let search_term = self.search_term.to_lowercase();
filtered.retain(|key| {
key.server.to_lowercase().contains(&search_term) ||
key.public_key.to_lowercase().contains(&search_term)
key.server.to_lowercase().contains(&search_term)
|| key.public_key.to_lowercase().contains(&search_term)
});
}
self.filtered_keys = filtered;
}
/// Load keys from server
pub fn load_keys(&mut self, settings: &KhmSettings, ctx: &egui::Context) -> Option<mpsc::Receiver<Result<Vec<SshKey>, String>>> {
pub fn load_keys(
&mut self,
settings: &KhmSettings,
ctx: &egui::Context,
) -> Option<mpsc::Receiver<Result<Vec<SshKey>, String>>> {
if settings.host.is_empty() || settings.flow.is_empty() {
return None;
}
self.current_operation = AdminOperation::LoadingKeys;
let (tx, rx) = mpsc::channel();
let host = settings.host.clone();
let flow = settings.flow.clone();
let basic_auth = 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_keys(host, flow, basic_auth).await
});
let result = rt.block_on(async { fetch_keys(host, flow, basic_auth).await });
let _ = tx.send(result);
ctx_clone.request_repaint();
});
Some(rx)
}
/// Handle keys load result
pub fn handle_keys_loaded(&mut self, result: Result<Vec<SshKey>, String>) {
match result {
@@ -109,7 +111,7 @@ impl AdminState {
}
}
}
/// Get selected servers list
pub fn get_selected_servers(&self) -> Vec<String> {
self.selected_servers
@@ -117,19 +119,24 @@ impl AdminState {
.filter_map(|(server, &selected)| if selected { Some(server.clone()) } else { None })
.collect()
}
/// Clear selected servers
pub fn clear_selection(&mut self) {
self.selected_servers.clear();
}
/// Get statistics
pub fn get_statistics(&self) -> AdminStatistics {
let total_keys = self.keys.len();
let active_keys = self.keys.iter().filter(|k| !k.deprecated).count();
let deprecated_keys = total_keys - active_keys;
let unique_servers = self.keys.iter().map(|k| &k.server).collect::<std::collections::HashSet<_>>().len();
let unique_servers = self
.keys
.iter()
.map(|k| &k.server)
.collect::<std::collections::HashSet<_>>()
.len();
AdminStatistics {
total_keys,
active_keys,

View File

@@ -1,46 +1,81 @@
use super::state::{get_key_preview, get_key_type, AdminState};
use crate::gui::api::SshKey;
use eframe::egui;
use std::collections::BTreeMap;
use super::state::{AdminState, get_key_type, get_key_preview};
use crate::gui::api::SshKey;
/// Render statistics cards
pub fn render_statistics(ui: &mut egui::Ui, admin_state: &AdminState) {
let stats = admin_state.get_statistics();
ui.group(|ui| {
ui.set_min_width(ui.available_width());
ui.vertical(|ui| {
ui.label(egui::RichText::new("📊 Statistics").size(16.0).strong());
ui.add_space(8.0);
ui.horizontal(|ui| {
ui.columns(4, |cols| {
// Total keys
cols[0].vertical_centered_justified(|ui| {
ui.label(egui::RichText::new("📊").size(20.0));
ui.label(egui::RichText::new(stats.total_keys.to_string()).size(24.0).strong());
ui.label(egui::RichText::new("Total Keys").size(11.0).color(egui::Color32::GRAY));
ui.label(
egui::RichText::new(stats.total_keys.to_string())
.size(24.0)
.strong(),
);
ui.label(
egui::RichText::new("Total Keys")
.size(11.0)
.color(egui::Color32::GRAY),
);
});
// Active keys
cols[1].vertical_centered_justified(|ui| {
ui.label(egui::RichText::new("").size(20.0));
ui.label(egui::RichText::new(stats.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));
ui.label(
egui::RichText::new(stats.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
cols[2].vertical_centered_justified(|ui| {
ui.label(egui::RichText::new("").size(20.0));
ui.label(egui::RichText::new(stats.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));
ui.label(
egui::RichText::new(stats.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
cols[3].vertical_centered_justified(|ui| {
ui.label(egui::RichText::new("💻").size(20.0));
ui.label(egui::RichText::new(stats.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.label(
egui::RichText::new(stats.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),
);
});
});
});
@@ -51,45 +86,59 @@ pub fn render_statistics(ui: &mut egui::Ui, admin_state: &AdminState) {
/// Render search and filter controls
pub fn render_search_controls(ui: &mut egui::Ui, admin_state: &mut AdminState) -> bool {
let mut changed = false;
ui.group(|ui| {
ui.set_min_width(ui.available_width());
ui.vertical(|ui| {
ui.label(egui::RichText::new("🔍 Search").size(16.0).strong());
ui.add_space(8.0);
// Search field with full width
ui.horizontal(|ui| {
ui.label(egui::RichText::new("🔍").size(14.0));
let search_response = ui.add_sized(
[ui.available_width() * 0.6, 20.0],
egui::TextEdit::singleline(&mut admin_state.search_term)
.hint_text("Search servers or keys...")
.hint_text("Search servers or keys..."),
);
if admin_state.search_term.is_empty() {
ui.label(egui::RichText::new("Type to search").size(11.0).color(egui::Color32::GRAY));
ui.label(
egui::RichText::new("Type to search")
.size(11.0)
.color(egui::Color32::GRAY),
);
} else {
ui.label(egui::RichText::new(format!("{} results", admin_state.filtered_keys.len())).size(11.0));
if ui.add(egui::Button::new(egui::RichText::new("").color(egui::Color32::WHITE))
.fill(egui::Color32::from_rgb(170, 170, 170))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(89, 89, 89)))
.rounding(egui::Rounding::same(3.0))
.min_size(egui::vec2(18.0, 18.0))
).on_hover_text("Clear search").clicked() {
ui.label(
egui::RichText::new(format!("{} results", admin_state.filtered_keys.len()))
.size(11.0),
);
if ui
.add(
egui::Button::new(
egui::RichText::new("").color(egui::Color32::WHITE),
)
.fill(egui::Color32::from_rgb(170, 170, 170))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(89, 89, 89)))
.rounding(egui::Rounding::same(3.0))
.min_size(egui::vec2(18.0, 18.0)),
)
.on_hover_text("Clear search")
.clicked()
{
admin_state.search_term.clear();
changed = true;
}
}
// Handle search text changes
if search_response.changed() {
changed = true;
}
});
ui.add_space(5.0);
// Filter controls
ui.horizontal(|ui| {
ui.label("Filter:");
@@ -98,76 +147,104 @@ pub fn render_search_controls(ui: &mut egui::Ui, admin_state: &mut AdminState) -
admin_state.show_deprecated_only = false;
changed = true;
}
if ui.selectable_label(show_deprecated, "❗ Deprecated").clicked() {
if ui
.selectable_label(show_deprecated, "❗ Deprecated")
.clicked()
{
admin_state.show_deprecated_only = true;
changed = true;
}
});
});
});
if changed {
admin_state.filter_keys();
}
changed
}
/// Render bulk actions controls
pub fn render_bulk_actions(ui: &mut egui::Ui, admin_state: &mut AdminState) -> BulkAction {
let selected_count = admin_state.selected_servers.values().filter(|&&v| v).count();
let selected_count = admin_state
.selected_servers
.values()
.filter(|&&v| v)
.count();
if selected_count == 0 {
return BulkAction::None;
}
let mut action = BulkAction::None;
ui.group(|ui| {
ui.set_min_width(ui.available_width());
ui.vertical(|ui| {
ui.horizontal(|ui| {
ui.label(egui::RichText::new("📋").size(14.0));
ui.label(egui::RichText::new(format!("Selected {} servers", selected_count))
.size(14.0)
.strong()
.color(egui::Color32::LIGHT_BLUE));
ui.label(
egui::RichText::new(format!("Selected {} servers", selected_count))
.size(14.0)
.strong()
.color(egui::Color32::LIGHT_BLUE),
);
});
ui.add_space(5.0);
ui.horizontal(|ui| {
if ui.add(egui::Button::new(egui::RichText::new("❗ Deprecate Selected").color(egui::Color32::BLACK))
.fill(egui::Color32::from_rgb(255, 200, 0))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(102, 94, 72)))
.rounding(egui::Rounding::same(6.0))
.min_size(egui::vec2(130.0, 28.0))
).clicked() {
if ui
.add(
egui::Button::new(
egui::RichText::new("❗ Deprecate Selected")
.color(egui::Color32::BLACK),
)
.fill(egui::Color32::from_rgb(255, 200, 0))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(102, 94, 72)))
.rounding(egui::Rounding::same(6.0))
.min_size(egui::vec2(130.0, 28.0)),
)
.clicked()
{
action = BulkAction::DeprecateSelected;
}
if ui.add(egui::Button::new(egui::RichText::new("✅ Restore Selected").color(egui::Color32::WHITE))
.fill(egui::Color32::from_rgb(101, 199, 40))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(94, 105, 25)))
.rounding(egui::Rounding::same(6.0))
.min_size(egui::vec2(120.0, 28.0))
).clicked() {
if ui
.add(
egui::Button::new(
egui::RichText::new("✅ Restore Selected").color(egui::Color32::WHITE),
)
.fill(egui::Color32::from_rgb(101, 199, 40))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(94, 105, 25)))
.rounding(egui::Rounding::same(6.0))
.min_size(egui::vec2(120.0, 28.0)),
)
.clicked()
{
action = BulkAction::RestoreSelected;
}
if ui.add(egui::Button::new(egui::RichText::new("X Clear Selection").color(egui::Color32::WHITE))
.fill(egui::Color32::from_rgb(170, 170, 170))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(89, 89, 89)))
.rounding(egui::Rounding::same(6.0))
.min_size(egui::vec2(110.0, 28.0))
).clicked() {
if ui
.add(
egui::Button::new(
egui::RichText::new("X Clear Selection").color(egui::Color32::WHITE),
)
.fill(egui::Color32::from_rgb(170, 170, 170))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(89, 89, 89)))
.rounding(egui::Rounding::same(6.0))
.min_size(egui::vec2(110.0, 28.0)),
)
.clicked()
{
admin_state.clear_selection();
action = BulkAction::ClearSelection;
}
});
});
});
action
}
@@ -177,86 +254,136 @@ pub fn render_keys_table(ui: &mut egui::Ui, admin_state: &mut AdminState) -> Key
render_empty_state(ui, admin_state);
return KeyAction::None;
}
let mut action = KeyAction::None;
// Group keys by server
let mut servers: BTreeMap<String, Vec<SshKey>> = BTreeMap::new();
for key in &admin_state.filtered_keys {
servers.entry(key.server.clone()).or_insert_with(Vec::new).push(key.clone());
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 = admin_state.expanded_servers.get(&server_name).copied().unwrap_or(false);
let is_expanded = admin_state
.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;
// Server header
ui.group(|ui| {
ui.horizontal(|ui| {
// Server selection checkbox
let mut selected = admin_state.selected_servers.get(&server_name).copied().unwrap_or(false);
if ui.add(egui::Checkbox::new(&mut selected, "")
.indeterminate(false)
).changed() {
admin_state.selected_servers.insert(server_name.clone(), selected);
let mut selected = admin_state
.selected_servers
.get(&server_name)
.copied()
.unwrap_or(false);
if ui
.add(egui::Checkbox::new(&mut selected, "").indeterminate(false))
.changed()
{
admin_state
.selected_servers
.insert(server_name.clone(), selected);
}
// 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() {
admin_state.expanded_servers.insert(server_name.clone(), !is_expanded);
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()
{
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));
ui.label(
egui::RichText::new(&server_name)
.size(15.0)
.strong()
.color(egui::Color32::WHITE),
);
// Keys count badge
render_badge(ui, &format!("{} keys", server_keys.len()), egui::Color32::from_rgb(52, 152, 219), egui::Color32::WHITE);
render_badge(
ui,
&format!("{} keys", server_keys.len()),
egui::Color32::from_rgb(52, 152, 219),
egui::Color32::WHITE,
);
ui.add_space(5.0);
// Deprecated count badge
if deprecated_count > 0 {
render_badge(ui, &format!("{} depr", deprecated_count), egui::Color32::from_rgb(231, 76, 60), egui::Color32::WHITE);
render_badge(
ui,
&format!("{} depr", deprecated_count),
egui::Color32::from_rgb(231, 76, 60),
egui::Color32::WHITE,
);
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
// Server action buttons
if deprecated_count > 0 {
if ui.add(egui::Button::new(egui::RichText::new("✅ Restore").color(egui::Color32::WHITE))
.fill(egui::Color32::from_rgb(101, 199, 40))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(94, 105, 25)))
.rounding(egui::Rounding::same(4.0))
.min_size(egui::vec2(70.0, 24.0))
).clicked() {
if ui
.add(
egui::Button::new(
egui::RichText::new("✅ Restore").color(egui::Color32::WHITE),
)
.fill(egui::Color32::from_rgb(101, 199, 40))
.stroke(egui::Stroke::new(
1.0,
egui::Color32::from_rgb(94, 105, 25),
))
.rounding(egui::Rounding::same(4.0))
.min_size(egui::vec2(70.0, 24.0)),
)
.clicked()
{
action = KeyAction::RestoreServer(server_name.clone());
}
}
if active_count > 0 {
if ui.add(egui::Button::new(egui::RichText::new("❗ Deprecate").color(egui::Color32::BLACK))
.fill(egui::Color32::from_rgb(255, 200, 0))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(102, 94, 72)))
.rounding(egui::Rounding::same(4.0))
.min_size(egui::vec2(85.0, 24.0))
).clicked() {
if ui
.add(
egui::Button::new(
egui::RichText::new("❗ Deprecate").color(egui::Color32::BLACK),
)
.fill(egui::Color32::from_rgb(255, 200, 0))
.stroke(egui::Stroke::new(
1.0,
egui::Color32::from_rgb(102, 94, 72),
))
.rounding(egui::Rounding::same(4.0))
.min_size(egui::vec2(85.0, 24.0)),
)
.clicked()
{
action = KeyAction::DeprecateServer(server_name.clone());
}
}
});
});
});
// Expanded key details
if is_expanded {
ui.indent("server_keys", |ui| {
@@ -267,10 +394,10 @@ pub fn render_keys_table(ui: &mut egui::Ui, admin_state: &mut AdminState) -> Key
}
});
}
ui.add_space(5.0);
}
action
}
@@ -279,29 +406,56 @@ fn render_empty_state(ui: &mut egui::Ui, admin_state: &AdminState) {
ui.vertical_centered(|ui| {
ui.add_space(60.0);
if 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));
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 if !admin_state.search_term.is_empty() {
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: '{}'", admin_state.search_term))
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: '{}'",
admin_state.search_term
))
.size(14.0)
.color(egui::Color32::DARK_GRAY));
.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));
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),
);
}
});
}
@@ -309,7 +463,7 @@ fn render_empty_state(ui: &mut egui::Ui, admin_state: &AdminState) {
/// Render individual key item
fn render_key_item(ui: &mut egui::Ui, key: &SshKey, server_name: &str) -> Option<KeyAction> {
let mut action = None;
ui.group(|ui| {
ui.horizontal(|ui| {
// Key type badge
@@ -321,86 +475,112 @@ fn render_key_item(ui: &mut egui::Ui, key: &SshKey, server_name: &str) -> Option
"DSA" => (egui::Color32::from_rgb(230, 126, 34), egui::Color32::WHITE),
_ => (egui::Color32::GRAY, egui::Color32::WHITE),
};
render_small_badge(ui, &key_type, badge_color, text_color);
ui.add_space(5.0);
// Status badge
if key.deprecated {
ui.label(egui::RichText::new("❗ DEPR")
.size(10.0)
.color(egui::Color32::from_rgb(231, 76, 60))
.strong());
ui.label(
egui::RichText::new("❗ DEPR")
.size(10.0)
.color(egui::Color32::from_rgb(231, 76, 60))
.strong(),
);
} else {
ui.label(egui::RichText::new("[OK] ACTIVE")
.size(10.0)
.color(egui::Color32::from_rgb(46, 204, 113))
.strong());
ui.label(
egui::RichText::new("")
.size(10.0)
.color(egui::Color32::from_rgb(46, 204, 113))
.strong(),
);
}
ui.add_space(5.0);
// Key preview
ui.label(egui::RichText::new(get_key_preview(&key.public_key))
.font(egui::FontId::monospace(10.0))
.color(egui::Color32::LIGHT_GRAY));
ui.label(
egui::RichText::new(get_key_preview(&key.public_key))
.font(egui::FontId::monospace(10.0))
.color(egui::Color32::LIGHT_GRAY),
);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
// Key action buttons
if key.deprecated {
if ui.add(egui::Button::new(egui::RichText::new("[R]").color(egui::Color32::WHITE))
.fill(egui::Color32::from_rgb(101, 199, 40))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(94, 105, 25)))
.rounding(egui::Rounding::same(3.0))
.min_size(egui::vec2(22.0, 18.0))
).on_hover_text("Restore key").clicked() {
if ui
.add(
egui::Button::new(
egui::RichText::new("[R]").color(egui::Color32::WHITE),
)
.fill(egui::Color32::from_rgb(101, 199, 40))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(94, 105, 25)))
.rounding(egui::Rounding::same(3.0))
.min_size(egui::vec2(22.0, 18.0)),
)
.on_hover_text("Restore key")
.clicked()
{
action = Some(KeyAction::RestoreKey(server_name.to_string()));
}
if ui.add(egui::Button::new(egui::RichText::new("Del").color(egui::Color32::WHITE))
.fill(egui::Color32::from_rgb(246, 36, 71))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(129, 18, 17)))
.rounding(egui::Rounding::same(3.0))
.min_size(egui::vec2(26.0, 18.0))
).on_hover_text("Delete key").clicked() {
if ui
.add(
egui::Button::new(
egui::RichText::new("Del").color(egui::Color32::WHITE),
)
.fill(egui::Color32::from_rgb(246, 36, 71))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(129, 18, 17)))
.rounding(egui::Rounding::same(3.0))
.min_size(egui::vec2(26.0, 18.0)),
)
.on_hover_text("Delete key")
.clicked()
{
action = Some(KeyAction::DeleteKey(server_name.to_string()));
}
} else {
if ui.add(egui::Button::new(egui::RichText::new("").color(egui::Color32::BLACK))
.fill(egui::Color32::from_rgb(255, 200, 0))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(102, 94, 72)))
.rounding(egui::Rounding::same(3.0))
.min_size(egui::vec2(22.0, 18.0))
).on_hover_text("Deprecate key").clicked() {
if ui
.add(
egui::Button::new(
egui::RichText::new("").color(egui::Color32::BLACK),
)
.fill(egui::Color32::from_rgb(255, 200, 0))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(102, 94, 72)))
.rounding(egui::Rounding::same(3.0))
.min_size(egui::vec2(22.0, 18.0)),
)
.on_hover_text("Deprecate key")
.clicked()
{
action = Some(KeyAction::DeprecateKey(server_name.to_string()));
}
}
if ui.add(egui::Button::new(egui::RichText::new("Copy").color(egui::Color32::WHITE))
.fill(egui::Color32::from_rgb(0, 111, 230))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(35, 84, 97)))
.rounding(egui::Rounding::same(3.0))
.min_size(egui::vec2(30.0, 18.0))
).on_hover_text("Copy to clipboard").clicked() {
if ui
.add(
egui::Button::new(egui::RichText::new("Copy").color(egui::Color32::WHITE))
.fill(egui::Color32::from_rgb(0, 111, 230))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(35, 84, 97)))
.rounding(egui::Rounding::same(3.0))
.min_size(egui::vec2(30.0, 18.0)),
)
.on_hover_text("Copy to clipboard")
.clicked()
{
ui.output_mut(|o| o.copied_text = key.public_key.clone());
}
});
});
});
action
}
/// Render a badge with text
fn render_badge(ui: &mut egui::Ui, text: &str, bg_color: egui::Color32, text_color: egui::Color32) {
let (rect, _) = ui.allocate_exact_size(
egui::vec2(50.0, 18.0),
egui::Sense::hover()
);
ui.painter().rect_filled(
rect,
egui::Rounding::same(8.0),
bg_color
);
let (rect, _) = ui.allocate_exact_size(egui::vec2(50.0, 18.0), egui::Sense::hover());
ui.painter()
.rect_filled(rect, egui::Rounding::same(8.0), bg_color);
ui.painter().text(
rect.center(),
egui::Align2::CENTER_CENTER,
@@ -411,16 +591,15 @@ fn render_badge(ui: &mut egui::Ui, text: &str, bg_color: egui::Color32, text_col
}
/// Render a small badge with text
fn render_small_badge(ui: &mut egui::Ui, text: &str, bg_color: egui::Color32, text_color: egui::Color32) {
let (rect, _) = ui.allocate_exact_size(
egui::vec2(40.0, 16.0),
egui::Sense::hover()
);
ui.painter().rect_filled(
rect,
egui::Rounding::same(3.0),
bg_color
);
fn render_small_badge(
ui: &mut egui::Ui,
text: &str,
bg_color: egui::Color32,
text_color: egui::Color32,
) {
let (rect, _) = ui.allocate_exact_size(egui::vec2(40.0, 16.0), egui::Sense::hover());
ui.painter()
.rect_filled(rect, egui::Rounding::same(3.0), bg_color);
ui.painter().text(
rect.center(),
egui::Align2::CENTER_CENTER,