diff --git a/burrow-gtk/src/components/app.rs b/burrow-gtk/src/components/app.rs index 333b6f1..0fa587c 100644 --- a/burrow-gtk/src/components/app.rs +++ b/burrow-gtk/src/components/app.rs @@ -7,7 +7,7 @@ const RECONNECT_POLL_TIME: Duration = Duration::from_secs(5); pub struct App { daemon_client: Arc>>, settings_screen: Controller, - switch_screen: AsyncController, + main_screen: AsyncController, } #[derive(Debug)] @@ -60,8 +60,8 @@ impl AsyncComponent for App { ) -> AsyncComponentParts { let daemon_client = Arc::new(Mutex::new(daemon::daemon_connect().await.ok())); - let switch_screen = switch_screen::SwitchScreen::builder() - .launch(switch_screen::SwitchScreenInit { + let main_screen = main_screen::MainScreen::builder() + .launch(main_screen::MainScreenInit { daemon_client: Arc::clone(&daemon_client), }) .forward(sender.input_sender(), |_| AppMsg::None); @@ -75,7 +75,7 @@ impl AsyncComponent for App { let widgets = view_output!(); let view_stack = adw::ViewStack::new(); - view_stack.add_titled(switch_screen.widget(), None, "Switch"); + view_stack.add_titled(main_screen.widget(), None, "Burrow"); view_stack.add_titled(settings_screen.widget(), None, "Settings"); let view_switcher_bar = adw::ViewSwitcherBar::builder().stack(&view_stack).build(); @@ -108,7 +108,7 @@ impl AsyncComponent for App { let model = App { daemon_client, - switch_screen, + main_screen, settings_screen, }; @@ -130,8 +130,8 @@ impl AsyncComponent for App { let mut client = tunnel_client::TunnelClient::new(daemon_client); if let Err(_e) = client.tunnel_status(burrow_rpc::Empty {}).await { disconnected_daemon_client = true; - self.switch_screen - .emit(switch_screen::SwitchScreenMsg::DaemonDisconnect); + self.main_screen + .emit(main_screen::MainScreenMsg::DaemonDisconnect); self.settings_screen .emit(settings_screen::SettingsScreenMsg::DaemonStateChange) } @@ -141,8 +141,8 @@ impl AsyncComponent for App { match daemon::daemon_connect().await { Ok(new_daemon_client) => { *daemon_client = Some(new_daemon_client); - self.switch_screen - .emit(switch_screen::SwitchScreenMsg::DaemonReconnect); + self.main_screen + .emit(main_screen::MainScreenMsg::DaemonReconnect); self.settings_screen .emit(settings_screen::SettingsScreenMsg::DaemonStateChange) } diff --git a/burrow-gtk/src/components/main/mod.rs b/burrow-gtk/src/components/main/mod.rs new file mode 100644 index 0000000..8e03b41 --- /dev/null +++ b/burrow-gtk/src/components/main/mod.rs @@ -0,0 +1,9 @@ +use super::*; + +mod network_card; +mod networks; +mod switch; + +pub use network_card::{NetworkCard, NetworkCardInit}; +pub use networks::{Networks, NetworksInit}; +pub use switch::{Switch, SwitchInit, SwitchMsg}; diff --git a/burrow-gtk/src/components/main/network_card.rs b/burrow-gtk/src/components/main/network_card.rs new file mode 100644 index 0000000..be75066 --- /dev/null +++ b/burrow-gtk/src/components/main/network_card.rs @@ -0,0 +1,49 @@ +use super::*; + +pub struct NetworkCard {} + +pub struct NetworkCardInit { + pub name: String, + pub enabled: bool, +} + +#[derive(Debug)] +pub enum NetworkCardMsg {} + +#[relm4::component(pub, async)] +impl AsyncComponent for NetworkCard { + type Init = NetworkCardInit; + type Input = NetworkCardMsg; + type Output = (); + type CommandOutput = (); + + view! { + gtk::ListBoxRow { + set_hexpand: true, + set_halign: Align::Center, + gtk::Box { + gtk::Label { + set_label: &init.name + }, + gtk::Switch { + set_halign: Align::Center, + set_hexpand: false, + set_vexpand: false, + set_state: init.enabled, + }, + } + } + } + + async fn init( + init: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + let widgets = view_output!(); + + let model = NetworkCard {}; + + AsyncComponentParts { model, widgets } + } +} diff --git a/burrow-gtk/src/components/main/networks.rs b/burrow-gtk/src/components/main/networks.rs new file mode 100644 index 0000000..6372e65 --- /dev/null +++ b/burrow-gtk/src/components/main/networks.rs @@ -0,0 +1,83 @@ +use super::*; + +pub struct Networks { + daemon_client: Arc>>, + network_widgets: Vec>, +} + +pub struct NetworksInit { + pub daemon_client: Arc>>, +} + +#[derive(Debug)] +pub enum NetworksMsg { + None, +} + +#[relm4::component(pub, async)] +impl AsyncComponent for Networks { + type Init = NetworksInit; + type Input = NetworksMsg; + type Output = (); + type CommandOutput = (); + + view! { + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 5, + set_margin_all: 5, + set_valign: Align::Start, + + #[name = "networks"] + gtk::ListBox {} + } + } + + async fn init( + init: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + let widgets = view_output!(); + + let network_widgets = vec![ + NetworkCard::builder() + .launch(NetworkCardInit { + name: "Hello".to_owned(), + enabled: true, + }) + .forward(sender.input_sender(), |_| NetworksMsg::None), + NetworkCard::builder() + .launch(NetworkCardInit { + name: "World".to_owned(), + enabled: false, + }) + .forward(sender.input_sender(), |_| NetworksMsg::None), + NetworkCard::builder() + .launch(NetworkCardInit { + name: "Yay".to_owned(), + enabled: false, + }) + .forward(sender.input_sender(), |_| NetworksMsg::None), + ]; + + widgets.networks.append(network_widgets[0].widget()); + widgets.networks.append(network_widgets[1].widget()); + widgets.networks.append(network_widgets[2].widget()); + + let model = Networks { + daemon_client: init.daemon_client, + network_widgets, + }; + + AsyncComponentParts { model, widgets } + } + + async fn update( + &mut self, + msg: Self::Input, + _: AsyncComponentSender, + _root: &Self::Root, + ) { + } +} diff --git a/burrow-gtk/src/components/main/switch.rs b/burrow-gtk/src/components/main/switch.rs new file mode 100644 index 0000000..5958e83 --- /dev/null +++ b/burrow-gtk/src/components/main/switch.rs @@ -0,0 +1,205 @@ +use super::*; +use std::time::Duration; + +const RECONNECT_POLL_TIME: Duration = Duration::from_secs(3); + +pub struct Switch { + daemon_client: Arc>>, + switch: gtk::Switch, + switch_screen: gtk::Box, + disconnected_banner: adw::Banner, + + _tunnel_state_worker: WorkerController, +} + +pub struct SwitchInit { + pub daemon_client: Arc>>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum SwitchMsg { + None, + DaemonReconnect, + DaemonDisconnect, + Start, + Stop, + SwitchSetStart, + SwitchSetStop, +} + +#[relm4::component(pub, async)] +impl AsyncComponent for Switch { + type Init = SwitchInit; + type Input = SwitchMsg; + type Output = (); + type CommandOutput = (); + + view! { + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_valign: Align::Fill, + + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 5, + set_margin_all: 5, + set_valign: Align::Start, + + #[name(setup_banner)] + adw::Banner { + set_title: "Burrow is not running!", + }, + }, + + #[name(switch_screen)] + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 10, + set_margin_all: 5, + set_valign: Align::Center, + set_vexpand: true, + + gtk::Label { + set_label: "Burrow Switch", + }, + + #[name(switch)] + gtk::Switch { + set_halign: Align::Center, + set_hexpand: false, + set_vexpand: false, + connect_active_notify => move |switch| + switch_sender.input(if switch.is_active() { SwitchMsg::Start } else { SwitchMsg::Stop }) + }, + } + } + } + + async fn init( + init: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + let mut initial_daemon_server_down = false; + + if let Some(daemon_client) = init.daemon_client.lock().await.as_mut() { + let mut client = tunnel_client::TunnelClient::new(daemon_client); + if client + .tunnel_status(burrow_rpc::Empty {}) + .await + .as_mut() + .is_err() + { + initial_daemon_server_down = true; + } + } else { + initial_daemon_server_down = true; + } + + let switch_sender = sender.clone(); + let widgets = view_output!(); + + 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 = Switch { + daemon_client: init.daemon_client, + switch: widgets.switch.clone(), + switch_screen: widgets.switch_screen.clone(), + disconnected_banner: widgets.setup_banner.clone(), + _tunnel_state_worker: AsyncTunnelStateHandler::builder() + .detach_worker(()) + .forward(sender.input_sender(), |_| SwitchMsg::None), + }; + + widgets.switch.set_active(false); + + AsyncComponentParts { model, widgets } + } + + async fn update( + &mut self, + msg: Self::Input, + _: AsyncComponentSender, + _root: &Self::Root, + ) { + let mut disconnected_daemon_client = false; + + if let Some(daemon_client) = self.daemon_client.lock().await.as_mut() { + let mut client = tunnel_client::TunnelClient::new(daemon_client); + match msg { + Self::Input::Start => { + if let Err(_e) = client.tunnel_start(burrow_rpc::Empty {}).await { + disconnected_daemon_client = true; + } + } + Self::Input::Stop => { + if let Err(_e) = client.tunnel_stop(burrow_rpc::Empty {}).await { + disconnected_daemon_client = true; + } + } + Self::Input::SwitchSetStart => { + self.switch.set_active(true); + } + Self::Input::SwitchSetStop => { + 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_screen.set_sensitive(false); + self.disconnected_banner.set_revealed(true); + } + } +} + +struct AsyncTunnelStateHandler; + +impl Worker for AsyncTunnelStateHandler { + type Init = (); + type Input = (); + type Output = SwitchMsg; + + fn init(_: Self::Init, _sender: ComponentSender) -> Self { + Self + } + + fn update(&mut self, _: (), sender: ComponentSender) { + let rt = tokio::runtime::Runtime::new().unwrap(); + let task = rt.spawn(async move { + loop { + let conn = daemon::daemon_connect().await; + if let Ok(conn) = conn { + let mut client = tunnel_client::TunnelClient::new(conn); + if let Ok(mut res) = client.tunnel_status(burrow_rpc::Empty {}).await { + let stream = res.get_mut(); + while let Ok(Some(msg)) = stream.message().await { + sender + .output(match msg.state() { + burrow_rpc::State::Running => SwitchMsg::SwitchSetStart, + burrow_rpc::State::Stopped => SwitchMsg::SwitchSetStop, + }) + .unwrap(); + } + } + } + tokio::time::sleep(RECONNECT_POLL_TIME).await; + } + }); + rt.block_on(task).unwrap(); + } +} diff --git a/burrow-gtk/src/components/main_screen.rs b/burrow-gtk/src/components/main_screen.rs new file mode 100644 index 0000000..52dd98a --- /dev/null +++ b/burrow-gtk/src/components/main_screen.rs @@ -0,0 +1,109 @@ +use super::*; + +pub struct MainScreen { + switch: AsyncController, +} + +pub struct MainScreenInit { + pub daemon_client: Arc>>, +} + +#[derive(Debug)] +pub enum MainScreenMsg { + None, + DaemonDisconnect, + DaemonReconnect, +} + +#[relm4::component(pub, async)] +impl AsyncComponent for MainScreen { + type Init = MainScreenInit; + type Input = MainScreenMsg; + type Output = (); + type CommandOutput = (); + + view! { + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_valign: Align::Fill, + set_valign: Align::Center, + + // gtk::Box { + // set_orientation: gtk::Orientation::Vertical, + // set_spacing: 5, + // set_margin_all: 5, + // set_valign: Align::Start, + + // #[name(setup_banner)] + // adw::Banner { + // set_title: "Burrow is not running!", + // }, + // }, + + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 10, + set_margin_all: 5, + set_valign: Align::Center, + set_vexpand: true, + }, + + #[name(content)] + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 10, + set_margin_all: 5, + set_valign: Align::Center, + set_vexpand: true, + + gtk::Label { + set_label: "Main Screen", + }, + } + } + } + + async fn init( + init: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + let switch = main::Switch::builder() + .launch(main::SwitchInit { + daemon_client: Arc::clone(&init.daemon_client), + }) + .forward(sender.input_sender(), |_| MainScreenMsg::None); + + let networks = main::Networks::builder() + .launch(main::NetworksInit { + daemon_client: Arc::clone(&init.daemon_client), + }) + .forward(sender.input_sender(), |_| MainScreenMsg::None); + + let widgets = view_output!(); + + widgets.content.append(networks.widget()); + widgets.content.append(switch.widget()); + + let model = MainScreen { switch }; + + AsyncComponentParts { model, widgets } + } + + async fn update( + &mut self, + msg: Self::Input, + _: AsyncComponentSender, + _root: &Self::Root, + ) { + match msg { + MainScreenMsg::DaemonDisconnect => { + self.switch.emit(main::SwitchMsg::DaemonDisconnect); + } + MainScreenMsg::DaemonReconnect => { + self.switch.emit(main::SwitchMsg::DaemonReconnect); + } + _ => {} + } + } +} diff --git a/burrow-gtk/src/components/mod.rs b/burrow-gtk/src/components/mod.rs index 21c51e5..39608de 100644 --- a/burrow-gtk/src/components/mod.rs +++ b/burrow-gtk/src/components/mod.rs @@ -18,9 +18,11 @@ use burrow_rpc::tunnel_client; use tonic::transport::Channel; mod app; +mod main; +mod main_screen; mod settings; mod settings_screen; -mod switch_screen; +// mod switch_screen; pub use app::*; pub use settings::{DaemonGroupMsg, DiagGroupMsg}; diff --git a/burrow-gtk/src/components/template.rs b/burrow-gtk/src/components/template.rs new file mode 100644 index 0000000..800a5c9 --- /dev/null +++ b/burrow-gtk/src/components/template.rs @@ -0,0 +1,39 @@ +pub struct Template {} + +pub struct TemplateInit {} + +#[derive(Debug)] +pub enum TemplateMsg {} + +#[relm4::component(pub, async)] +impl AsyncComponent for Template { + type Init = TemplateInit; + type Input = TemplateMsg; + type Output = (); + type CommandOutput = (); + + view! { + gtk::Box { + } + } + + async fn init( + init: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + let widgets = view_output!(); + + let model = Template {}; + + AsyncComponentParts { model, widgets } + } + + async fn update( + &mut self, + msg: Self::Input, + _: AsyncComponentSender, + _root: &Self::Root, + ) { + } +}