rust: profile editor and quick connect

This commit is contained in:
Andy Dixon
2026-10-11 17:23:47 +01:00
parent 43acc57c51
commit 7f4f9376fb
3 changed files with 1297 additions and 22 deletions
+1 -1
View File
@@ -611,7 +611,7 @@ pub fn palette(app: &mut App, ctx: &egui::Context) {
// --- prompts -------------------------------------------------------------------------------- // --- prompts --------------------------------------------------------------------------------
fn modal(ctx: &egui::Context, id: &str, width: f32, body: impl FnOnce(&mut Ui)) { pub fn modal(ctx: &egui::Context, id: &str, width: f32, body: impl FnOnce(&mut Ui)) {
modal_backdrop(ctx); modal_backdrop(ctx);
egui::Area::new(egui::Id::new(id)).order(egui::Order::Foreground).anchor(Align2::CENTER_CENTER, vec2(0.0, -40.0)).show(ctx, |ui| { egui::Area::new(egui::Id::new(id)).order(egui::Order::Foreground).anchor(Align2::CENTER_CENTER, vec2(0.0, -40.0)).show(ctx, |ui| {
egui::Frame::popup(ui.style()).corner_radius(10).inner_margin(16).show(ui, |ui| { egui::Frame::popup(ui.style()).corner_radius(10).inner_margin(16).show(ui, |ui| {
File diff suppressed because it is too large Load Diff
+160 -9
View File
@@ -1,26 +1,177 @@
//! Quick Connect dialog: an ad-hoc session without saving a profile. //! Quick Connect dialog: an ad-hoc session without saving a profile.
//! STUB — interface fixed; implementation pending.
use crate::state::Shared; use crate::chrome::modal;
use crate::state::{Request, Shared};
use crate::theme::{self, Tokens};
use crate::widgets;
use egui::{ComboBox, DragValue, Key, RichText, TextEdit};
use egui_phosphor::regular as icon;
use nexus_core::clients::serial;
use nexus_core::models::{Profile, Protocol};
const PROTOCOLS: [Protocol; 5] = [Protocol::Ssh, Protocol::Telnet, Protocol::Raw, Protocol::Irc, Protocol::Serial];
#[derive(Default)]
pub struct QuickConnect { pub struct QuickConnect {
open: bool, open: bool,
focus: bool,
protocol: Protocol,
host: String,
port: i64,
username: String,
nick: String,
ports: Vec<String>,
}
impl Default for QuickConnect {
fn default() -> Self {
QuickConnect { open: false, focus: false, protocol: Protocol::Ssh, host: String::new(), port: 22, username: String::new(), nick: "nexus".into(), ports: vec![] }
}
} }
impl QuickConnect { impl QuickConnect {
pub fn open(&mut self) { pub fn open(&mut self) {
self.open = true; self.open = true;
self.focus = true;
self.host.clear();
if self.protocol == Protocol::Serial {
self.ports = serial::list_ports();
}
} }
pub fn is_open(&self) -> bool { fn set_protocol(&mut self, p: Protocol) {
self.open self.protocol = p;
self.port = Profile::new(p).port;
if p == Protocol::Serial {
self.ports = serial::list_ports();
}
}
/// The profile to connect, or None when the host is empty.
fn profile(&self) -> Option<Profile> {
let host = self.host.trim();
if host.is_empty() {
return None;
}
let mut p = Profile::new(self.protocol);
p.host = host.into();
p.display_name = host.into();
if self.protocol != Protocol::Serial {
p.port = self.port;
}
if matches!(self.protocol, Protocol::Ssh | Protocol::Irc) {
p.username = self.username.trim().into();
}
if let Some(c) = &mut p.irc
&& !self.nick.trim().is_empty()
{
c.nick = self.nick.trim().into();
}
Some(p)
} }
/// Called every frame; draws its window(s) while open. /// Called every frame; draws its window(s) while open.
pub fn ui(&mut self, ctx: &egui::Context, _s: &mut Shared) { pub fn ui(&mut self, ctx: &egui::Context, s: &mut Shared) {
let mut open = self.open; if !self.open {
egui::Window::new("QuickConnect").open(&mut open).show(ctx, |ui| ui.label("todo")); return;
self.open = open; }
let t = Tokens::get(ctx);
let (enter, esc) = ctx.input(|i| (i.key_pressed(Key::Enter), i.key_pressed(Key::Escape)));
let mut connect = enter;
let mut close = esc;
let label = |ui: &mut egui::Ui, l: &str| ui.label(RichText::new(l).color(t.text_dim).size(12.0));
modal(ctx, "quick-connect", 360.0, |ui| {
ui.label(RichText::new(format!("{} Quick Connect", icon::LIGHTNING)).font(theme::semibold(15.0)));
ui.add_space(4.0);
label(ui, "Protocol");
let mut proto = self.protocol;
ComboBox::from_id_salt("qc-protocol").width(ui.available_width()).selected_text(proto.label()).show_ui(ui, |ui| {
for p in PROTOCOLS {
ui.selectable_value(&mut proto, p, p.label());
}
});
if proto != self.protocol {
self.set_protocol(proto);
}
let serial = self.protocol == Protocol::Serial;
label(ui, if serial { "Device" } else { "Host" });
let r = ui
.horizontal(|ui| {
let w = if serial { ui.available_width() - 34.0 } else { ui.available_width() };
let r = ui.add(TextEdit::singleline(&mut self.host).hint_text(if serial { "/dev/ttyUSB0 or COM3" } else { "e.g. 192.168.1.1" }).desired_width(w));
if serial {
ui.menu_button(icon::CARET_DOWN, |ui| {
if self.ports.is_empty() {
widgets::hint(ui, "No serial devices found.");
}
for p in &self.ports {
if ui.selectable_label(self.host == *p, p).clicked() {
self.host = p.clone();
ui.close();
}
}
if ui.button(format!("{} Refresh", icon::ARROWS_CLOCKWISE)).clicked() {
self.ports = serial::list_ports();
}
});
}
r
})
.inner;
if std::mem::take(&mut self.focus) {
r.request_focus();
}
if !serial {
label(ui, "Port");
ui.add(DragValue::new(&mut self.port).range(1..=65535).speed(0.25));
}
if matches!(self.protocol, Protocol::Ssh | Protocol::Irc) {
label(ui, "Username");
ui.add(TextEdit::singleline(&mut self.username).hint_text("root").desired_width(f32::INFINITY));
}
if self.protocol == Protocol::Irc {
label(ui, "Nick");
ui.add(TextEdit::singleline(&mut self.nick).desired_width(f32::INFINITY));
}
ui.add_space(8.0);
ui.horizontal(|ui| {
if widgets::primary(ui, icon::PLUGS_CONNECTED, "Connect").clicked() {
connect = true;
}
if ui.button("Cancel").clicked() {
close = true;
}
});
});
if close {
self.open = false;
} else if connect {
match self.profile() {
Some(p) => {
self.open = false;
s.request(Request::ConnectQuick(p));
}
None => self.focus = true,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builds_profiles() {
let mut q = QuickConnect::default();
assert!(q.profile().is_none());
q.host = " box ".into();
q.username = "me".into();
q.set_protocol(Protocol::Irc);
q.nick = "andy".into();
let p = q.profile().unwrap();
assert_eq!((p.host.as_str(), p.port, p.username.as_str(), p.irc.unwrap().nick.as_str()), ("box", 6667, "me", "andy"));
q.set_protocol(Protocol::Serial);
let p = q.profile().unwrap();
assert_eq!((p.port, p.username.as_str()), (0, ""));
} }
} }