Wire Forge-native release infrastructure
Some checks failed
Cache: Publish Nix / Publish Nix Cache (push) Waiting to run
Build Rust / Cargo Test (push) Successful in 4m14s
Build Site / Next.js Build (push) Failing after 6s
Infra: OpenTofu / OpenTofu (grafana) (push) Successful in 5s
Lint Governance / BEP Metadata (push) Successful in 0s
Build: Android / Android Rust Core Stub (push) Failing after 23s
Some checks failed
Cache: Publish Nix / Publish Nix Cache (push) Waiting to run
Build Rust / Cargo Test (push) Successful in 4m14s
Build Site / Next.js Build (push) Failing after 6s
Infra: OpenTofu / OpenTofu (grafana) (push) Successful in 5s
Lint Governance / BEP Metadata (push) Successful in 0s
Build: Android / Android Rust Core Stub (push) Failing after 23s
This commit is contained in:
parent
97c569fb35
commit
002bd382e9
199 changed files with 14268 additions and 185 deletions
515
burrow-gtk/src/components/networks_screen.rs
Normal file
515
burrow-gtk/src/components/networks_screen.rs
Normal file
|
|
@ -0,0 +1,515 @@
|
|||
use super::*;
|
||||
use anyhow::{Context, Result};
|
||||
use tokio::time::Duration;
|
||||
|
||||
pub struct NetworksScreen {
|
||||
list_box: gtk::ListBox,
|
||||
network_status: gtk::Label,
|
||||
wireguard_text: gtk::TextView,
|
||||
tailnet_email_entry: gtk::Entry,
|
||||
tailnet_authority_entry: gtk::Entry,
|
||||
tailnet_account_entry: gtk::Entry,
|
||||
tailnet_identity_entry: gtk::Entry,
|
||||
tailnet_hostname_entry: gtk::Entry,
|
||||
tailnet_name_entry: gtk::Entry,
|
||||
custom_authority_switch: gtk::Switch,
|
||||
tailnet_status: gtk::Label,
|
||||
tailnet_session_id: Option<String>,
|
||||
tailnet_running: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NetworksScreenMsg {
|
||||
Refresh,
|
||||
AddWireGuard,
|
||||
DiscoverTailnet,
|
||||
ProbeTailnet,
|
||||
StartTailnetLogin,
|
||||
PollTailnetLogin,
|
||||
CancelTailnetLogin,
|
||||
AddTailnet,
|
||||
}
|
||||
|
||||
#[relm4::component(pub, async)]
|
||||
impl AsyncComponent for NetworksScreen {
|
||||
type Init = ();
|
||||
type Input = NetworksScreenMsg;
|
||||
type Output = ();
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
gtk::ScrolledWindow {
|
||||
set_vexpand: true,
|
||||
|
||||
adw::Clamp {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 12,
|
||||
set_margin_all: 12,
|
||||
|
||||
adw::PreferencesGroup {
|
||||
set_title: "Stored Networks",
|
||||
set_description: Some("Networks saved in the Burrow daemon"),
|
||||
|
||||
#[name(network_status)]
|
||||
gtk::Label {
|
||||
set_xalign: 0.0,
|
||||
set_wrap: true,
|
||||
set_label: "Loading networks",
|
||||
},
|
||||
|
||||
#[name(list_box)]
|
||||
gtk::ListBox {
|
||||
add_css_class: "boxed-list",
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "Refresh",
|
||||
connect_clicked => NetworksScreenMsg::Refresh
|
||||
},
|
||||
},
|
||||
|
||||
adw::PreferencesGroup {
|
||||
set_title: "Add WireGuard",
|
||||
set_description: Some("Import a WireGuard configuration into the daemon"),
|
||||
|
||||
gtk::ScrolledWindow {
|
||||
set_min_content_height: 150,
|
||||
set_vexpand: false,
|
||||
|
||||
#[name(wireguard_text)]
|
||||
gtk::TextView {
|
||||
set_monospace: true,
|
||||
set_wrap_mode: gtk::WrapMode::WordChar,
|
||||
}
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "Add WireGuard Network",
|
||||
connect_clicked => NetworksScreenMsg::AddWireGuard
|
||||
},
|
||||
},
|
||||
|
||||
adw::PreferencesGroup {
|
||||
set_title: "Tailnet",
|
||||
set_description: Some("Discover, probe, sign in, and save Tailnet authorities through the daemon"),
|
||||
|
||||
#[name(tailnet_email_entry)]
|
||||
gtk::Entry {
|
||||
set_placeholder_text: Some("Email address"),
|
||||
},
|
||||
|
||||
#[name(custom_authority_switch)]
|
||||
gtk::Switch {
|
||||
set_halign: Align::Start,
|
||||
set_active: false,
|
||||
},
|
||||
|
||||
#[name(tailnet_authority_entry)]
|
||||
gtk::Entry {
|
||||
set_placeholder_text: Some("Tailnet server URL"),
|
||||
},
|
||||
|
||||
#[name(tailnet_account_entry)]
|
||||
gtk::Entry {
|
||||
set_placeholder_text: Some("Account name"),
|
||||
},
|
||||
|
||||
#[name(tailnet_identity_entry)]
|
||||
gtk::Entry {
|
||||
set_placeholder_text: Some("Identity name"),
|
||||
},
|
||||
|
||||
#[name(tailnet_hostname_entry)]
|
||||
gtk::Entry {
|
||||
set_placeholder_text: Some("Device hostname"),
|
||||
},
|
||||
|
||||
#[name(tailnet_name_entry)]
|
||||
gtk::Entry {
|
||||
set_placeholder_text: Some("Tailnet name"),
|
||||
},
|
||||
|
||||
#[name(tailnet_status)]
|
||||
gtk::Label {
|
||||
set_xalign: 0.0,
|
||||
set_wrap: true,
|
||||
set_selectable: true,
|
||||
set_label: "Use discovery for the managed Tailscale authority or enter a custom Tailnet server.",
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 8,
|
||||
|
||||
gtk::Button {
|
||||
set_label: "Discover",
|
||||
connect_clicked => NetworksScreenMsg::DiscoverTailnet
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "Probe",
|
||||
connect_clicked => NetworksScreenMsg::ProbeTailnet
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "Sign In",
|
||||
connect_clicked => NetworksScreenMsg::StartTailnetLogin
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "Cancel",
|
||||
connect_clicked => NetworksScreenMsg::CancelTailnetLogin
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: "Save Tailnet Network",
|
||||
connect_clicked => NetworksScreenMsg::AddTailnet
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn init(
|
||||
_: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
let widgets = view_output!();
|
||||
widgets
|
||||
.tailnet_authority_entry
|
||||
.set_text(daemon_api::default_tailnet_authority());
|
||||
widgets.tailnet_account_entry.set_text("default");
|
||||
widgets.tailnet_identity_entry.set_text("linux");
|
||||
|
||||
let mut model = NetworksScreen {
|
||||
list_box: widgets.list_box.clone(),
|
||||
network_status: widgets.network_status.clone(),
|
||||
wireguard_text: widgets.wireguard_text.clone(),
|
||||
tailnet_email_entry: widgets.tailnet_email_entry.clone(),
|
||||
tailnet_authority_entry: widgets.tailnet_authority_entry.clone(),
|
||||
tailnet_account_entry: widgets.tailnet_account_entry.clone(),
|
||||
tailnet_identity_entry: widgets.tailnet_identity_entry.clone(),
|
||||
tailnet_hostname_entry: widgets.tailnet_hostname_entry.clone(),
|
||||
tailnet_name_entry: widgets.tailnet_name_entry.clone(),
|
||||
custom_authority_switch: widgets.custom_authority_switch.clone(),
|
||||
tailnet_status: widgets.tailnet_status.clone(),
|
||||
tailnet_session_id: None,
|
||||
tailnet_running: false,
|
||||
};
|
||||
|
||||
model.refresh_networks().await;
|
||||
|
||||
AsyncComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
async fn update(
|
||||
&mut self,
|
||||
msg: Self::Input,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
match msg {
|
||||
NetworksScreenMsg::Refresh => self.refresh_networks().await,
|
||||
NetworksScreenMsg::AddWireGuard => self.add_wireguard().await,
|
||||
NetworksScreenMsg::DiscoverTailnet => self.discover_tailnet().await,
|
||||
NetworksScreenMsg::ProbeTailnet => self.probe_tailnet().await,
|
||||
NetworksScreenMsg::StartTailnetLogin => self.start_tailnet_login(sender).await,
|
||||
NetworksScreenMsg::PollTailnetLogin => self.poll_tailnet_login(sender).await,
|
||||
NetworksScreenMsg::CancelTailnetLogin => self.cancel_tailnet_login().await,
|
||||
NetworksScreenMsg::AddTailnet => self.add_tailnet().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworksScreen {
|
||||
async fn refresh_networks(&mut self) {
|
||||
match daemon_api::list_networks().await {
|
||||
Ok(networks) => {
|
||||
self.render_networks(&networks);
|
||||
self.network_status.set_label(if networks.is_empty() {
|
||||
"No daemon networks saved yet"
|
||||
} else {
|
||||
"Daemon networks are up to date"
|
||||
});
|
||||
}
|
||||
Err(error) => {
|
||||
self.clear_networks();
|
||||
self.network_status
|
||||
.set_label(&format!("Unable to read daemon networks: {error}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn add_wireguard(&mut self) {
|
||||
let config = text_view_text(&self.wireguard_text);
|
||||
if config.trim().is_empty() {
|
||||
self.network_status
|
||||
.set_label("Paste a WireGuard configuration before adding a network");
|
||||
return;
|
||||
}
|
||||
|
||||
match daemon_api::add_wireguard(config).await {
|
||||
Ok(id) => {
|
||||
self.network_status
|
||||
.set_label(&format!("Added WireGuard network #{id}"));
|
||||
self.wireguard_text.buffer().set_text("");
|
||||
self.refresh_networks().await;
|
||||
}
|
||||
Err(error) => self
|
||||
.network_status
|
||||
.set_label(&format!("Unable to add WireGuard network: {error}")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn discover_tailnet(&mut self) {
|
||||
let email = self.tailnet_email_entry.text().to_string();
|
||||
let Ok(email) = daemon_api::require_value(&email, "Email address") else {
|
||||
self.tailnet_status
|
||||
.set_label("Enter an email address before discovery");
|
||||
return;
|
||||
};
|
||||
|
||||
self.tailnet_status.set_label("Discovering Tailnet server");
|
||||
match daemon_api::discover_tailnet(email).await {
|
||||
Ok(discovery) => {
|
||||
self.tailnet_authority_entry.set_text(&discovery.authority);
|
||||
self.custom_authority_switch.set_active(!discovery.managed);
|
||||
let issuer = discovery
|
||||
.oidc_issuer
|
||||
.map(|issuer| format!(" OIDC issuer: {issuer}."))
|
||||
.unwrap_or_default();
|
||||
let kind = if discovery.managed {
|
||||
"managed authority"
|
||||
} else {
|
||||
"custom authority"
|
||||
};
|
||||
self.tailnet_status.set_label(&format!(
|
||||
"Discovered {kind}: {}.{issuer}",
|
||||
discovery.authority
|
||||
));
|
||||
}
|
||||
Err(error) => self
|
||||
.tailnet_status
|
||||
.set_label(&format!("Tailnet discovery failed: {error}")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn probe_tailnet(&mut self) {
|
||||
let authority = self.tailnet_authority();
|
||||
let Ok(authority) = daemon_api::require_value(&authority, "Tailnet server URL") else {
|
||||
self.tailnet_status
|
||||
.set_label("Enter a Tailnet server URL before probing");
|
||||
return;
|
||||
};
|
||||
|
||||
self.tailnet_status.set_label("Checking Tailnet server");
|
||||
match daemon_api::probe_tailnet(authority).await {
|
||||
Ok(probe) => {
|
||||
let detail = probe
|
||||
.detail
|
||||
.unwrap_or_else(|| format!("HTTP {}", probe.status_code));
|
||||
self.tailnet_status
|
||||
.set_label(&format!("{}: {detail}", probe.summary));
|
||||
}
|
||||
Err(error) => self
|
||||
.tailnet_status
|
||||
.set_label(&format!("Tailnet probe failed: {error}")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_tailnet_login(&mut self, sender: AsyncComponentSender<Self>) {
|
||||
let authority = self.tailnet_authority();
|
||||
let Ok(authority) = daemon_api::require_value(&authority, "Tailnet server URL") else {
|
||||
self.tailnet_status
|
||||
.set_label("Enter a Tailnet server URL before sign-in");
|
||||
return;
|
||||
};
|
||||
let account = daemon_api::normalized(&self.tailnet_account_entry.text(), "default");
|
||||
let identity = daemon_api::normalized(&self.tailnet_identity_entry.text(), "linux");
|
||||
let hostname = daemon_api::normalized_optional(&self.tailnet_hostname_entry.text());
|
||||
|
||||
self.tailnet_status.set_label("Starting Tailnet sign-in");
|
||||
match daemon_api::start_tailnet_login(authority, account, identity, hostname).await {
|
||||
Ok(status) => {
|
||||
self.apply_login_status(&status);
|
||||
if let Some(auth_url) = status.auth_url.as_deref() {
|
||||
if let Err(error) = open_auth_url(auth_url) {
|
||||
self.tailnet_status.set_label(&format!(
|
||||
"{} Open this URL manually: {auth_url}. Browser launch failed: {error}",
|
||||
self.tailnet_status.text()
|
||||
));
|
||||
}
|
||||
}
|
||||
if !status.running {
|
||||
sender.input(NetworksScreenMsg::PollTailnetLogin);
|
||||
}
|
||||
}
|
||||
Err(error) => self
|
||||
.tailnet_status
|
||||
.set_label(&format!("Tailnet sign-in failed: {error}")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn poll_tailnet_login(&mut self, sender: AsyncComponentSender<Self>) {
|
||||
let Some(session_id) = self.tailnet_session_id.clone() else {
|
||||
return;
|
||||
};
|
||||
if self.tailnet_running {
|
||||
return;
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
match daemon_api::tailnet_login_status(session_id).await {
|
||||
Ok(status) => {
|
||||
self.apply_login_status(&status);
|
||||
if !status.running {
|
||||
sender.input(NetworksScreenMsg::PollTailnetLogin);
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
self.tailnet_status
|
||||
.set_label(&format!("Tailnet sign-in status failed: {error}"));
|
||||
self.tailnet_session_id = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn cancel_tailnet_login(&mut self) {
|
||||
let Some(session_id) = self.tailnet_session_id.clone() else {
|
||||
self.tailnet_status
|
||||
.set_label("No Tailnet sign-in is active");
|
||||
return;
|
||||
};
|
||||
|
||||
match daemon_api::cancel_tailnet_login(session_id).await {
|
||||
Ok(()) => {
|
||||
self.tailnet_session_id = None;
|
||||
self.tailnet_running = false;
|
||||
self.tailnet_status.set_label("Tailnet sign-in cancelled");
|
||||
}
|
||||
Err(error) => self
|
||||
.tailnet_status
|
||||
.set_label(&format!("Unable to cancel Tailnet sign-in: {error}")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn add_tailnet(&mut self) {
|
||||
let authority = self.tailnet_authority();
|
||||
let Ok(authority) = daemon_api::require_value(&authority, "Tailnet server URL") else {
|
||||
self.tailnet_status
|
||||
.set_label("Enter a Tailnet server URL before saving");
|
||||
return;
|
||||
};
|
||||
if self.tailnet_session_id.is_some() && !self.tailnet_running {
|
||||
self.tailnet_status
|
||||
.set_label("Finish browser sign-in before saving this Tailnet network");
|
||||
return;
|
||||
}
|
||||
|
||||
let account = daemon_api::normalized(&self.tailnet_account_entry.text(), "default");
|
||||
let identity = daemon_api::normalized(&self.tailnet_identity_entry.text(), "linux");
|
||||
let hostname = daemon_api::normalized_optional(&self.tailnet_hostname_entry.text());
|
||||
let tailnet = daemon_api::normalized_optional(&self.tailnet_name_entry.text());
|
||||
|
||||
match daemon_api::add_tailnet(authority, account, identity, hostname, tailnet).await {
|
||||
Ok(id) => {
|
||||
self.tailnet_status
|
||||
.set_label(&format!("Saved Tailnet network #{id}"));
|
||||
self.refresh_networks().await;
|
||||
}
|
||||
Err(error) => self
|
||||
.tailnet_status
|
||||
.set_label(&format!("Unable to save Tailnet network: {error}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_login_status(&mut self, status: &daemon_api::TailnetLoginStatus) {
|
||||
self.tailnet_session_id = Some(status.session_id.clone());
|
||||
self.tailnet_running = status.running;
|
||||
|
||||
let mut parts = Vec::new();
|
||||
if status.running {
|
||||
parts.push("Signed in".to_owned());
|
||||
} else if status.needs_login {
|
||||
parts.push("Browser sign-in required".to_owned());
|
||||
} else {
|
||||
parts.push("Checking sign-in".to_owned());
|
||||
}
|
||||
if !status.backend_state.is_empty() {
|
||||
parts.push(format!("State: {}", status.backend_state));
|
||||
}
|
||||
if let Some(tailnet_name) = &status.tailnet_name {
|
||||
parts.push(format!("Tailnet: {tailnet_name}"));
|
||||
}
|
||||
if let Some(self_dns_name) = &status.self_dns_name {
|
||||
parts.push(self_dns_name.clone());
|
||||
}
|
||||
if !status.tailnet_ips.is_empty() {
|
||||
parts.push(status.tailnet_ips.join(", "));
|
||||
}
|
||||
if !status.health.is_empty() {
|
||||
parts.push(status.health.join(" / "));
|
||||
}
|
||||
if let Some(auth_url) = &status.auth_url {
|
||||
parts.push(format!("Auth URL: {auth_url}"));
|
||||
}
|
||||
|
||||
self.tailnet_status.set_label(&parts.join("\n"));
|
||||
}
|
||||
|
||||
fn tailnet_authority(&self) -> String {
|
||||
if self.custom_authority_switch.is_active() {
|
||||
self.tailnet_authority_entry.text().to_string()
|
||||
} else {
|
||||
let authority = self.tailnet_authority_entry.text();
|
||||
if authority.trim().is_empty() {
|
||||
daemon_api::default_tailnet_authority().to_owned()
|
||||
} else {
|
||||
authority.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_networks(&self) {
|
||||
while let Some(child) = self.list_box.first_child() {
|
||||
self.list_box.remove(&child);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_networks(&self, networks: &[daemon_api::NetworkSummary]) {
|
||||
self.clear_networks();
|
||||
if networks.is_empty() {
|
||||
let row = adw::ActionRow::builder()
|
||||
.title("No networks")
|
||||
.subtitle("Save a WireGuard or Tailnet network to use it here.")
|
||||
.build();
|
||||
self.list_box.append(&row);
|
||||
return;
|
||||
}
|
||||
|
||||
for network in networks {
|
||||
let title = format!("{} (#{})", network.title, network.id);
|
||||
let row = adw::ActionRow::builder()
|
||||
.title(&title)
|
||||
.subtitle(&network.detail)
|
||||
.build();
|
||||
self.list_box.append(&row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn text_view_text(text_view: >k::TextView) -> String {
|
||||
let buffer = text_view.buffer();
|
||||
buffer
|
||||
.text(&buffer.start_iter(), &buffer.end_iter(), true)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn open_auth_url(url: &str) -> Result<()> {
|
||||
gtk::gio::AppInfo::launch_default_for_uri(url, None::<>k::gio::AppLaunchContext>)
|
||||
.with_context(|| format!("failed to open {url}"))
|
||||
}
|
||||
|
|
@ -4,12 +4,10 @@ use std::process::Command;
|
|||
#[derive(Debug)]
|
||||
pub struct DaemonGroup {
|
||||
system_setup: SystemSetup,
|
||||
daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
already_running: bool,
|
||||
}
|
||||
|
||||
pub struct DaemonGroupInit {
|
||||
pub daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
pub system_setup: SystemSetup,
|
||||
}
|
||||
|
||||
|
|
@ -30,8 +28,8 @@ impl AsyncComponent for DaemonGroup {
|
|||
#[name(group)]
|
||||
adw::PreferencesGroup {
|
||||
#[watch]
|
||||
set_sensitive:
|
||||
(model.system_setup == SystemSetup::AppImage || model.system_setup == SystemSetup::Other) &&
|
||||
set_sensitive:
|
||||
(model.system_setup == SystemSetup::AppImage || model.system_setup == SystemSetup::Other) &&
|
||||
!model.already_running,
|
||||
set_title: "Local Daemon",
|
||||
set_description: Some("Run Local Daemon"),
|
||||
|
|
@ -51,8 +49,7 @@ impl AsyncComponent for DaemonGroup {
|
|||
// Should be impossible to panic here
|
||||
let model = DaemonGroup {
|
||||
system_setup: init.system_setup,
|
||||
daemon_client: init.daemon_client.clone(),
|
||||
already_running: init.daemon_client.lock().await.is_some(),
|
||||
already_running: daemon_api::daemon_available().await,
|
||||
};
|
||||
|
||||
let widgets = view_output!();
|
||||
|
|
@ -104,7 +101,7 @@ mv $TEMP /tmp/burrow-detached-daemon"#,
|
|||
.unwrap();
|
||||
}
|
||||
DaemonGroupMsg::DaemonStateChange => {
|
||||
self.already_running = self.daemon_client.lock().await.is_some();
|
||||
self.already_running = daemon_api::daemon_available().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ use super::*;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct DiagGroup {
|
||||
daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
|
||||
system_setup: SystemSetup,
|
||||
service_installed: StatusTernary,
|
||||
socket_installed: StatusTernary,
|
||||
|
|
@ -12,14 +10,13 @@ pub struct DiagGroup {
|
|||
}
|
||||
|
||||
pub struct DiagGroupInit {
|
||||
pub daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
pub system_setup: SystemSetup,
|
||||
}
|
||||
|
||||
impl DiagGroup {
|
||||
async fn new(daemon_client: Arc<Mutex<Option<DaemonClient>>>) -> Result<Self> {
|
||||
async fn new() -> Result<Self> {
|
||||
let system_setup = SystemSetup::new();
|
||||
let daemon_running = daemon_client.lock().await.is_some();
|
||||
let daemon_running = daemon_api::daemon_available().await;
|
||||
|
||||
Ok(Self {
|
||||
service_installed: system_setup.is_service_installed()?,
|
||||
|
|
@ -27,7 +24,6 @@ impl DiagGroup {
|
|||
socket_enabled: system_setup.is_socket_enabled()?,
|
||||
daemon_running,
|
||||
system_setup,
|
||||
daemon_client,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -95,7 +91,8 @@ impl AsyncComponent for DiagGroup {
|
|||
sender: AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
// Should be impossible to panic here
|
||||
let model = DiagGroup::new(init.daemon_client).await.unwrap();
|
||||
let mut model = DiagGroup::new().await.unwrap();
|
||||
model.system_setup = init.system_setup;
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
|
|
@ -111,7 +108,7 @@ impl AsyncComponent for DiagGroup {
|
|||
match msg {
|
||||
DiagGroupMsg::Refresh => {
|
||||
// Should be impossible to panic here
|
||||
*self = Self::new(Arc::clone(&self.daemon_client)).await.unwrap();
|
||||
*self = Self::new().await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,6 @@ pub struct SettingsScreen {
|
|||
daemon_group: AsyncController<settings::DaemonGroup>,
|
||||
}
|
||||
|
||||
pub struct SettingsScreenInit {
|
||||
pub daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SettingsScreenMsg {
|
||||
DaemonStateChange,
|
||||
|
|
@ -17,7 +13,7 @@ pub enum SettingsScreenMsg {
|
|||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for SettingsScreen {
|
||||
type Init = SettingsScreenInit;
|
||||
type Init = ();
|
||||
type Input = SettingsScreenMsg;
|
||||
type Output = ();
|
||||
|
||||
|
|
@ -27,26 +23,20 @@ impl SimpleComponent for SettingsScreen {
|
|||
}
|
||||
|
||||
fn init(
|
||||
init: Self::Init,
|
||||
_init: Self::Init,
|
||||
root: &Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let system_setup = SystemSetup::new();
|
||||
|
||||
let diag_group = settings::DiagGroup::builder()
|
||||
.launch(settings::DiagGroupInit {
|
||||
system_setup,
|
||||
daemon_client: Arc::clone(&init.daemon_client),
|
||||
})
|
||||
.launch(settings::DiagGroupInit { system_setup })
|
||||
.forward(sender.input_sender(), |_| {
|
||||
SettingsScreenMsg::DaemonStateChange
|
||||
});
|
||||
|
||||
let daemon_group = settings::DaemonGroup::builder()
|
||||
.launch(settings::DaemonGroupInit {
|
||||
system_setup,
|
||||
daemon_client: Arc::clone(&init.daemon_client),
|
||||
})
|
||||
.launch(settings::DaemonGroupInit { system_setup })
|
||||
.forward(sender.input_sender(), |_| {
|
||||
SettingsScreenMsg::DaemonStateChange
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,27 +1,22 @@
|
|||
use super::*;
|
||||
|
||||
pub struct SwitchScreen {
|
||||
daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
switch: gtk::Switch,
|
||||
switch_screen: gtk::Box,
|
||||
disconnected_banner: adw::Banner,
|
||||
}
|
||||
|
||||
pub struct SwitchScreenInit {
|
||||
pub daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
||||
status_label: gtk::Label,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SwitchScreenMsg {
|
||||
DaemonReconnect,
|
||||
DaemonDisconnect,
|
||||
Refresh,
|
||||
Start,
|
||||
Stop,
|
||||
}
|
||||
|
||||
#[relm4::component(pub, async)]
|
||||
impl AsyncComponent for SwitchScreen {
|
||||
type Init = SwitchScreenInit;
|
||||
type Init = ();
|
||||
type Input = SwitchScreenMsg;
|
||||
type Output = ();
|
||||
type CommandOutput = ();
|
||||
|
|
@ -39,7 +34,7 @@ impl AsyncComponent for SwitchScreen {
|
|||
|
||||
#[name(setup_banner)]
|
||||
adw::Banner {
|
||||
set_title: "Burrow is not running!",
|
||||
set_title: "Burrow daemon is not reachable",
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -52,7 +47,12 @@ impl AsyncComponent for SwitchScreen {
|
|||
set_vexpand: true,
|
||||
|
||||
gtk::Label {
|
||||
set_label: "Burrow Switch",
|
||||
set_label: "Burrow Tunnel",
|
||||
},
|
||||
|
||||
#[name(status_label)]
|
||||
gtk::Label {
|
||||
set_label: "Checking daemon status",
|
||||
},
|
||||
|
||||
#[name(switch)]
|
||||
|
|
@ -68,91 +68,76 @@ impl AsyncComponent for SwitchScreen {
|
|||
}
|
||||
|
||||
async fn init(
|
||||
init: Self::Init,
|
||||
_: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
let mut initial_switch_status = false;
|
||||
let mut initial_daemon_server_down = false;
|
||||
|
||||
if let Some(daemon_client) = init.daemon_client.lock().await.as_mut() {
|
||||
if let Ok(res) = daemon_client
|
||||
.send_command(DaemonCommand::ServerInfo)
|
||||
.await
|
||||
.as_ref()
|
||||
{
|
||||
initial_switch_status = match res.result.as_ref() {
|
||||
Ok(DaemonResponseData::None) => false,
|
||||
Ok(DaemonResponseData::ServerInfo(_)) => true,
|
||||
_ => false,
|
||||
};
|
||||
} else {
|
||||
initial_daemon_server_down = true;
|
||||
}
|
||||
} else {
|
||||
initial_daemon_server_down = true;
|
||||
}
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
widgets.switch.set_active(initial_switch_status);
|
||||
|
||||
if initial_daemon_server_down {
|
||||
*init.daemon_client.lock().await = None;
|
||||
widgets.switch.set_active(false);
|
||||
widgets.switch_screen.set_sensitive(false);
|
||||
widgets.setup_banner.set_revealed(true);
|
||||
}
|
||||
|
||||
let model = SwitchScreen {
|
||||
daemon_client: init.daemon_client,
|
||||
let mut model = SwitchScreen {
|
||||
switch: widgets.switch.clone(),
|
||||
switch_screen: widgets.switch_screen.clone(),
|
||||
disconnected_banner: widgets.setup_banner.clone(),
|
||||
status_label: widgets.status_label.clone(),
|
||||
};
|
||||
|
||||
model.refresh().await;
|
||||
|
||||
AsyncComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
async fn update(
|
||||
&mut self,
|
||||
msg: Self::Input,
|
||||
_: AsyncComponentSender<Self>,
|
||||
_sender: AsyncComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
let mut disconnected_daemon_client = false;
|
||||
|
||||
if let Some(daemon_client) = self.daemon_client.lock().await.as_mut() {
|
||||
match msg {
|
||||
Self::Input::Start => {
|
||||
if let Err(_e) = daemon_client
|
||||
.send_command(DaemonCommand::Start(Default::default()))
|
||||
.await
|
||||
{
|
||||
disconnected_daemon_client = true;
|
||||
}
|
||||
match msg {
|
||||
SwitchScreenMsg::Refresh => self.refresh().await,
|
||||
SwitchScreenMsg::Start => {
|
||||
match daemon_api::start_tunnel().await {
|
||||
Ok(()) => self.status_label.set_label("Tunnel running"),
|
||||
Err(error) => self
|
||||
.status_label
|
||||
.set_label(&format!("Start failed: {error}")),
|
||||
}
|
||||
Self::Input::Stop => {
|
||||
if let Err(_e) = daemon_client.send_command(DaemonCommand::Stop).await {
|
||||
disconnected_daemon_client = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
self.refresh().await;
|
||||
}
|
||||
SwitchScreenMsg::Stop => {
|
||||
match daemon_api::stop_tunnel().await {
|
||||
Ok(()) => self.status_label.set_label("Tunnel stopped"),
|
||||
Err(error) => self
|
||||
.status_label
|
||||
.set_label(&format!("Stop failed: {error}")),
|
||||
}
|
||||
self.refresh().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SwitchScreen {
|
||||
async fn refresh(&mut self) {
|
||||
match daemon_api::tunnel_state().await {
|
||||
Ok(daemon_api::TunnelState::Running) => {
|
||||
self.disconnected_banner.set_revealed(false);
|
||||
self.switch_screen.set_sensitive(true);
|
||||
self.status_label.set_label("Tunnel running");
|
||||
self.switch.set_active(true);
|
||||
}
|
||||
Ok(daemon_api::TunnelState::Stopped) => {
|
||||
self.disconnected_banner.set_revealed(false);
|
||||
self.switch_screen.set_sensitive(true);
|
||||
self.status_label.set_label("Tunnel stopped");
|
||||
self.switch.set_active(false);
|
||||
}
|
||||
Err(error) => {
|
||||
self.disconnected_banner.set_revealed(true);
|
||||
self.switch_screen.set_sensitive(false);
|
||||
self.status_label
|
||||
.set_label(&format!("Daemon unavailable: {error}"));
|
||||
self.switch.set_active(false);
|
||||
}
|
||||
} else {
|
||||
disconnected_daemon_client = true;
|
||||
}
|
||||
|
||||
if msg == Self::Input::DaemonReconnect {
|
||||
self.disconnected_banner.set_revealed(false);
|
||||
self.switch_screen.set_sensitive(true);
|
||||
}
|
||||
|
||||
if disconnected_daemon_client || msg == Self::Input::DaemonDisconnect {
|
||||
*self.daemon_client.lock().await = None;
|
||||
self.switch.set_active(false);
|
||||
self.switch_screen.set_sensitive(false);
|
||||
self.disconnected_banner.set_revealed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue