Tray: separate tray and window processes; device list without self
Release / tsunagi-linux-aarch64 (push) Successful in 2m44s
Release / tsunagi-linux-x86_64 (push) Successful in 4m37s
Release / tsunagi-macos-aarch64 (push) Canceled after 0s
Release / tsunagi-windows-x86_64 (push) Canceled after 0s
Release / Publish release (push) Canceled after 0s
Release / tsunagi-linux-aarch64 (push) Successful in 2m44s
Release / tsunagi-linux-x86_64 (push) Successful in 4m37s
Release / tsunagi-macos-aarch64 (push) Canceled after 0s
Release / tsunagi-windows-x86_64 (push) Canceled after 0s
Release / Publish release (push) Canceled after 0s
The tray process holds only the icon and starts the same binary with --window on Open; the window ends when closed, which works on Wayland where a window cannot be hidden. The devices table now names devices as hostname (id5), leaves out this device and counts the others. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
69310108f4
commit
f4dcdec662
6 files changed
+247
-172
No files matched your search
@@ -23,22 +23,51 @@ pub(crate) fn title(network: &NetworkReport) -> String {
|
||||
/// One row of the device table, built from a tunnel and/or a signed member.
|
||||
struct Device<'a> {
|
||||
online: bool,
|
||||
name: String,
|
||||
/// What clicking the name copies: `hostname.network` so it resolves, or
|
||||
/// the short id when there is no hostname.
|
||||
copy: String,
|
||||
/// The last hostname seen for it, kept while it is offline.
|
||||
hostname: Option<&'a str>,
|
||||
/// What clicking the hostname copies: `hostname.network`, so it resolves.
|
||||
copy: Option<String>,
|
||||
/// The full endpoint id, which clicking the short form copies.
|
||||
id: &'a str,
|
||||
proto: &'a str,
|
||||
address: Option<String>,
|
||||
handshake_secs: Option<u64>,
|
||||
overlay: Option<&'a OverlayPeerReport>,
|
||||
}
|
||||
|
||||
impl Device<'_> {
|
||||
fn sort_key(&self) -> String {
|
||||
self.hostname.unwrap_or(self.id).to_lowercase()
|
||||
}
|
||||
}
|
||||
|
||||
/// The first characters of an endpoint id, enough to tell devices apart.
|
||||
fn short_id(id: &str) -> String {
|
||||
id.chars().take(5).collect()
|
||||
}
|
||||
|
||||
/// The device cell: the hostname and, in brackets, the start of the id.
|
||||
/// Clicking either copies its own full value.
|
||||
fn name_cell(ui: &mut egui::Ui, device: &Device<'_>) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 4.0;
|
||||
match (device.hostname, &device.copy) {
|
||||
(Some(hostname), Some(copy)) => {
|
||||
format::copy_field(ui, hostname, copy);
|
||||
format::copy_field(ui, &format!("({})", short_id(device.id)), device.id);
|
||||
}
|
||||
_ => format::copy_field(ui, &short_id(device.id), device.id),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Draws the window body into its viewport.
|
||||
pub(crate) fn show(
|
||||
ctx: &egui::Context,
|
||||
_agent: &AgentClient,
|
||||
unit: &mut Unit,
|
||||
network: &NetworkReport,
|
||||
own_id: &str,
|
||||
traffic: &Traffic,
|
||||
) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
@@ -60,7 +89,7 @@ pub(crate) fn show(
|
||||
format::sparkline(ui, traffic.network(&network.network_id), unit, 48.0);
|
||||
ui.add_space(8.0);
|
||||
|
||||
let devices = devices(network);
|
||||
let devices = devices(network, own_id);
|
||||
let online = devices.iter().filter(|d| d.online).count();
|
||||
ui.label(
|
||||
egui::RichText::new(format!(
|
||||
@@ -123,31 +152,56 @@ fn summary(ui: &mut egui::Ui, network: &NetworkReport) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Every other device this network knows of, from a live tunnel or the signed
|
||||
/// membership. This device is in the roster too but is not one of the others.
|
||||
fn known_ids<'a>(network: &'a NetworkReport, own_id: &str) -> Vec<&'a str> {
|
||||
let overlay_peers = network
|
||||
.overlay
|
||||
.as_ref()
|
||||
.map(|o| o.peers.as_slice())
|
||||
.unwrap_or(&[]);
|
||||
let mut ids: Vec<&str> = Vec::new();
|
||||
let candidates = overlay_peers
|
||||
.iter()
|
||||
.map(|p| p.endpoint_id.as_str())
|
||||
.chain(network.peers.iter().map(|p| p.endpoint_id.as_str()))
|
||||
.chain(network.members.iter().map(|m| m.endpoint_id.as_str()));
|
||||
for id in candidates {
|
||||
if id != own_id && !ids.contains(&id) {
|
||||
ids.push(id);
|
||||
}
|
||||
}
|
||||
ids
|
||||
}
|
||||
|
||||
/// How many other devices this network knows of, connected or not.
|
||||
pub(crate) fn known_count(network: &NetworkReport, own_id: &str) -> usize {
|
||||
known_ids(network, own_id).len()
|
||||
}
|
||||
|
||||
/// Builds the device list: every endpoint known from a live tunnel or the
|
||||
/// signed membership, online ones first.
|
||||
fn devices(network: &NetworkReport) -> Vec<Device<'_>> {
|
||||
let hostnames: HashMap<&str, &str> = network
|
||||
.peers
|
||||
fn devices<'a>(network: &'a NetworkReport, own_id: &str) -> Vec<Device<'a>> {
|
||||
// The signed members carry the last name seen, so a device that is away
|
||||
// is still called by it; a live announcement is fresher and wins.
|
||||
let mut hostnames: HashMap<&str, &str> = network
|
||||
.members
|
||||
.iter()
|
||||
.filter_map(|p| p.hostname.as_deref().map(|h| (p.endpoint_id.as_str(), h)))
|
||||
.filter_map(|m| m.hostname.as_deref().map(|h| (m.endpoint_id.as_str(), h)))
|
||||
.collect();
|
||||
hostnames.extend(
|
||||
network
|
||||
.peers
|
||||
.iter()
|
||||
.filter_map(|p| p.hostname.as_deref().map(|h| (p.endpoint_id.as_str(), h))),
|
||||
);
|
||||
let overlay_peers = network
|
||||
.overlay
|
||||
.as_ref()
|
||||
.map(|o| o.peers.as_slice())
|
||||
.unwrap_or(&[]);
|
||||
|
||||
let mut ids: Vec<&str> = Vec::new();
|
||||
for peer in overlay_peers {
|
||||
if !ids.contains(&peer.endpoint_id.as_str()) {
|
||||
ids.push(&peer.endpoint_id);
|
||||
}
|
||||
}
|
||||
for member in &network.members {
|
||||
if !ids.contains(&member.endpoint_id.as_str()) {
|
||||
ids.push(&member.endpoint_id);
|
||||
}
|
||||
}
|
||||
let ids = known_ids(network, own_id);
|
||||
|
||||
let mut devices: Vec<Device> = ids
|
||||
.into_iter()
|
||||
@@ -156,15 +210,11 @@ fn devices(network: &NetworkReport) -> Vec<Device<'_>> {
|
||||
let member = network.members.iter().find(|m| m.endpoint_id == id);
|
||||
let online = overlay.is_some_and(|p| p.handshake_secs_ago.is_some());
|
||||
let hostname = hostnames.get(id).copied();
|
||||
let name = hostname.map_or_else(
|
||||
|| format::short(overlay.map_or(id, |p| p.public_key.as_str())),
|
||||
str::to_string,
|
||||
);
|
||||
let copy = hostname.map_or_else(|| name.clone(), |h| format!("{h}.{}", network.name));
|
||||
Device {
|
||||
online,
|
||||
name,
|
||||
copy,
|
||||
hostname,
|
||||
copy: hostname.map(|h| format!("{h}.{}", network.name)),
|
||||
id,
|
||||
proto: overlay.map_or("—", |p| p.protocol.as_str()),
|
||||
address: overlay
|
||||
.and_then(|p| p.address.clone())
|
||||
@@ -178,7 +228,7 @@ fn devices(network: &NetworkReport) -> Vec<Device<'_>> {
|
||||
devices.sort_by(|a, b| {
|
||||
b.online
|
||||
.cmp(&a.online)
|
||||
.then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase()))
|
||||
.then_with(|| a.sort_key().cmp(&b.sort_key()))
|
||||
});
|
||||
devices
|
||||
}
|
||||
@@ -216,7 +266,7 @@ fn device_table(
|
||||
ui.end_row();
|
||||
|
||||
for device in devices {
|
||||
format::copy_field(ui, &device.name, &device.copy);
|
||||
name_cell(ui, device);
|
||||
ui.label(device.proto);
|
||||
match &device.address {
|
||||
Some(address) => format::copy_field(ui, address, address),
|
||||
|
||||
Reference in new issue
Block a user