Compare commits
18 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b973481539 | ||
|
|
648909a906 | ||
|
|
aaabca9957 | ||
|
|
dd8cd03036 | ||
|
|
21e0df60a8 | ||
|
|
5088ab9a0e | ||
|
|
0ba1ea9237 | ||
|
|
c455c1fbbe | ||
|
|
fec725bc52 | ||
|
|
54e9e0bc43 | ||
|
|
dbacf93418 | ||
|
|
37fb4f4974 | ||
|
|
9a262a1243 | ||
|
|
87cea6bf64 | ||
|
|
753e50f79d | ||
|
|
74325059a7 | ||
|
|
12f595011a | ||
|
|
90468d5518 |
21 changed files with 1306 additions and 1784 deletions
1898
burrow-gtk/Cargo.lock
generated
1898
burrow-gtk/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -8,10 +8,17 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
relm4 = { version = "0.6", features = ["libadwaita", "gnome_44"]}
|
relm4 = { version = "0.6", features = ["libadwaita", "gnome_44"]}
|
||||||
burrow = { version = "*", path = "../burrow/" }
|
|
||||||
tokio = { version = "1.35.0", features = ["time", "sync"] }
|
tokio = { version = "1.35.0", features = ["time", "sync"] }
|
||||||
gettext-rs = { version = "0.7.0", features = ["gettext-system"] }
|
gettext-rs = { version = "0.7.0", features = ["gettext-system"] }
|
||||||
|
tonic = "0.12"
|
||||||
|
prost = "0.13"
|
||||||
|
prost-types = "0.13"
|
||||||
|
hyper-util = "0.1.6"
|
||||||
|
tower = "0.4.13"
|
||||||
|
log = "0.4.22"
|
||||||
|
colog = "1.3.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
glib-build-tools = "0.18.0"
|
glib-build-tools = "0.18.0"
|
||||||
|
tonic-build = "0.12"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
RUN set -eux && \
|
RUN set -eux && \
|
||||||
dnf update -y && \
|
dnf update -y && \
|
||||||
dnf install -y clang ninja-build cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib util-linux wget fuse fuse-libs file sqlite sqlite-devel
|
dnf install -y clang ninja-build cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib util-linux wget fuse fuse-libs file sqlite-devel protobuf-compiler protobuf-devel
|
||||||
|
|
||||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
|
||||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use anyhow::Result;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
compile_gresources()?;
|
compile_gresources()?;
|
||||||
|
tonic_build::compile_protos("proto/burrow.proto")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
73
burrow-gtk/proto/burrow.proto
Normal file
73
burrow-gtk/proto/burrow.proto
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
package burrow;
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
service Tunnel {
|
||||||
|
rpc TunnelConfiguration (Empty) returns (TunnelConfigurationResponse);
|
||||||
|
rpc TunnelStart (Empty) returns (Empty);
|
||||||
|
rpc TunnelStop (Empty) returns (Empty);
|
||||||
|
rpc TunnelStatus (Empty) returns (stream TunnelStatusResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
service Networks {
|
||||||
|
rpc NetworkAdd (Empty) returns (Empty);
|
||||||
|
rpc NetworkList (Empty) returns (stream NetworkListResponse);
|
||||||
|
rpc NetworkReorder (NetworkReorderRequest) returns (Empty);
|
||||||
|
rpc NetworkDelete (NetworkDeleteRequest) returns (Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
message NetworkReorderRequest {
|
||||||
|
int32 id = 1;
|
||||||
|
int32 index = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WireGuardPeer {
|
||||||
|
string endpoint = 1;
|
||||||
|
repeated string subnet = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WireGuardNetwork {
|
||||||
|
string address = 1;
|
||||||
|
string dns = 2;
|
||||||
|
repeated WireGuardPeer peer = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NetworkDeleteRequest {
|
||||||
|
int32 id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Network {
|
||||||
|
int32 id = 1;
|
||||||
|
NetworkType type = 2;
|
||||||
|
bytes payload = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NetworkType {
|
||||||
|
WireGuard = 0;
|
||||||
|
HackClub = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NetworkListResponse {
|
||||||
|
repeated Network network = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Empty {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
Stopped = 0;
|
||||||
|
Running = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TunnelStatusResponse {
|
||||||
|
State state = 1;
|
||||||
|
optional google.protobuf.Timestamp start = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TunnelConfigurationResponse {
|
||||||
|
repeated string addresses = 1;
|
||||||
|
int32 mtu = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -2,12 +2,12 @@ use super::*;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
const RECONNECT_POLL_TIME: Duration = Duration::from_secs(5);
|
const RECONNECT_POLL_TIME: Duration = Duration::from_secs(3);
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
settings_screen: Controller<settings_screen::SettingsScreen>,
|
settings_screen: Controller<settings_screen::SettingsScreen>,
|
||||||
switch_screen: AsyncController<switch_screen::SwitchScreen>,
|
main_screen: AsyncController<main_screen::MainScreen>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -49,7 +49,7 @@ impl AsyncComponent for App {
|
||||||
view! {
|
view! {
|
||||||
adw::Window {
|
adw::Window {
|
||||||
set_title: Some("Burrow"),
|
set_title: Some("Burrow"),
|
||||||
set_default_size: (640, 480),
|
set_default_size: (640, 800),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,10 +58,19 @@ impl AsyncComponent for App {
|
||||||
root: Self::Root,
|
root: Self::Root,
|
||||||
sender: AsyncComponentSender<Self>,
|
sender: AsyncComponentSender<Self>,
|
||||||
) -> AsyncComponentParts<Self> {
|
) -> AsyncComponentParts<Self> {
|
||||||
let daemon_client = Arc::new(Mutex::new(DaemonClient::new().await.ok()));
|
// TODO: RPC REFACTOR (Handle Error)
|
||||||
|
let mut daemon_client_connected = false;
|
||||||
|
let daemon_client = Arc::new(Mutex::new(
|
||||||
|
daemon::daemon_connect()
|
||||||
|
.await
|
||||||
|
.inspect(|_| {
|
||||||
|
daemon_client_connected = true;
|
||||||
|
})
|
||||||
|
.ok(),
|
||||||
|
));
|
||||||
|
|
||||||
let switch_screen = switch_screen::SwitchScreen::builder()
|
let main_screen = main_screen::MainScreen::builder()
|
||||||
.launch(switch_screen::SwitchScreenInit {
|
.launch(main_screen::MainScreenInit {
|
||||||
daemon_client: Arc::clone(&daemon_client),
|
daemon_client: Arc::clone(&daemon_client),
|
||||||
})
|
})
|
||||||
.forward(sender.input_sender(), |_| AppMsg::None);
|
.forward(sender.input_sender(), |_| AppMsg::None);
|
||||||
|
|
@ -72,10 +81,21 @@ impl AsyncComponent for App {
|
||||||
})
|
})
|
||||||
.forward(sender.input_sender(), |_| AppMsg::None);
|
.forward(sender.input_sender(), |_| AppMsg::None);
|
||||||
|
|
||||||
|
if !daemon_client_connected {
|
||||||
|
main_screen
|
||||||
|
.sender()
|
||||||
|
.send(main_screen::MainScreenMsg::DaemonDisconnect)
|
||||||
|
.unwrap();
|
||||||
|
settings_screen
|
||||||
|
.sender()
|
||||||
|
.send(settings_screen::SettingsScreenMsg::DaemonStateChange)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
|
||||||
let view_stack = adw::ViewStack::new();
|
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");
|
view_stack.add_titled(settings_screen.widget(), None, "Settings");
|
||||||
|
|
||||||
let view_switcher_bar = adw::ViewSwitcherBar::builder().stack(&view_stack).build();
|
let view_switcher_bar = adw::ViewSwitcherBar::builder().stack(&view_stack).build();
|
||||||
|
|
@ -108,7 +128,7 @@ impl AsyncComponent for App {
|
||||||
|
|
||||||
let model = App {
|
let model = App {
|
||||||
daemon_client,
|
daemon_client,
|
||||||
switch_screen,
|
main_screen,
|
||||||
settings_screen,
|
settings_screen,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -122,27 +142,32 @@ impl AsyncComponent for App {
|
||||||
_root: &Self::Root,
|
_root: &Self::Root,
|
||||||
) {
|
) {
|
||||||
loop {
|
loop {
|
||||||
tokio::time::sleep(RECONNECT_POLL_TIME).await;
|
|
||||||
{
|
{
|
||||||
let mut daemon_client = self.daemon_client.lock().await;
|
let mut daemon_client = self.daemon_client.lock().await;
|
||||||
let mut disconnected_daemon_client = false;
|
let mut disconnected_daemon_client = false;
|
||||||
|
|
||||||
if let Some(daemon_client) = daemon_client.as_mut() {
|
if let Some(client) = daemon_client.as_mut() {
|
||||||
if let Err(_e) = daemon_client.send_command(DaemonCommand::ServerInfo).await {
|
let mut client = tunnel_client::TunnelClient::new(client);
|
||||||
disconnected_daemon_client = true;
|
|
||||||
self.switch_screen
|
if let Ok(mut res) = client.tunnel_status(burrow_rpc::Empty {}).await {
|
||||||
.emit(switch_screen::SwitchScreenMsg::DaemonDisconnect);
|
let stream = res.get_mut();
|
||||||
self.settings_screen
|
while let Ok(Some(_)) = stream.message().await {}
|
||||||
.emit(settings_screen::SettingsScreenMsg::DaemonStateChange)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*daemon_client = None;
|
||||||
|
disconnected_daemon_client = true;
|
||||||
|
self.main_screen
|
||||||
|
.emit(main_screen::MainScreenMsg::DaemonDisconnect);
|
||||||
|
self.settings_screen
|
||||||
|
.emit(settings_screen::SettingsScreenMsg::DaemonStateChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
if disconnected_daemon_client || daemon_client.is_none() {
|
if disconnected_daemon_client || daemon_client.is_none() {
|
||||||
match DaemonClient::new().await {
|
match daemon::daemon_connect().await {
|
||||||
Ok(new_daemon_client) => {
|
Ok(new_daemon_client) => {
|
||||||
*daemon_client = Some(new_daemon_client);
|
*daemon_client = Some(new_daemon_client);
|
||||||
self.switch_screen
|
self.main_screen
|
||||||
.emit(switch_screen::SwitchScreenMsg::DaemonReconnect);
|
.emit(main_screen::MainScreenMsg::DaemonReconnect);
|
||||||
self.settings_screen
|
self.settings_screen
|
||||||
.emit(settings_screen::SettingsScreenMsg::DaemonStateChange)
|
.emit(settings_screen::SettingsScreenMsg::DaemonStateChange)
|
||||||
}
|
}
|
||||||
|
|
@ -152,6 +177,7 @@ impl AsyncComponent for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tokio::time::sleep(RECONNECT_POLL_TIME).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
burrow-gtk/src/components/main/mod.rs
Normal file
9
burrow-gtk/src/components/main/mod.rs
Normal file
|
|
@ -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};
|
||||||
147
burrow-gtk/src/components/main/network_card.rs
Normal file
147
burrow-gtk/src/components/main/network_card.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub struct NetworkCard {
|
||||||
|
id: i32,
|
||||||
|
index: usize,
|
||||||
|
index_max: usize,
|
||||||
|
daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NetworkCardInit {
|
||||||
|
pub id: i32,
|
||||||
|
pub index: usize,
|
||||||
|
pub index_max: usize,
|
||||||
|
pub name: String,
|
||||||
|
pub enabled: bool,
|
||||||
|
pub daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum NetworkCardMsg {
|
||||||
|
NetworkDelete,
|
||||||
|
MoveUp,
|
||||||
|
MoveDown,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component(pub, async)]
|
||||||
|
impl AsyncComponent for NetworkCard {
|
||||||
|
type Init = NetworkCardInit;
|
||||||
|
type Input = NetworkCardMsg;
|
||||||
|
type Output = ();
|
||||||
|
type CommandOutput = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::ListBoxRow {
|
||||||
|
set_halign: Align::Fill,
|
||||||
|
set_margin_vertical: 5,
|
||||||
|
set_margin_horizontal: 25,
|
||||||
|
set_hexpand: true,
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_halign: Align::Fill,
|
||||||
|
set_hexpand: true,
|
||||||
|
set_spacing: 10,
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_halign: Align::Start,
|
||||||
|
|
||||||
|
gtk::Switch {
|
||||||
|
set_active: init.enabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_halign: Align::Center,
|
||||||
|
set_hexpand: true,
|
||||||
|
|
||||||
|
gtk::Label {
|
||||||
|
set_label: &init.name
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_halign: Align::End,
|
||||||
|
set_spacing: 5,
|
||||||
|
|
||||||
|
gtk::Button {
|
||||||
|
set_icon_name: "list-remove",
|
||||||
|
|
||||||
|
connect_clicked => NetworkCardMsg::NetworkDelete,
|
||||||
|
},
|
||||||
|
gtk::Button {
|
||||||
|
set_icon_name: "pan-up-symbolic",
|
||||||
|
|
||||||
|
connect_clicked => NetworkCardMsg::MoveUp,
|
||||||
|
},
|
||||||
|
gtk::Button {
|
||||||
|
set_icon_name: "pan-down-symbolic",
|
||||||
|
|
||||||
|
connect_clicked => NetworkCardMsg::MoveDown,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init(
|
||||||
|
init: Self::Init,
|
||||||
|
root: Self::Root,
|
||||||
|
sender: AsyncComponentSender<Self>,
|
||||||
|
) -> AsyncComponentParts<Self> {
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
let model = NetworkCard {
|
||||||
|
id: init.id,
|
||||||
|
index: init.index,
|
||||||
|
index_max: init.index_max,
|
||||||
|
daemon_client: init.daemon_client,
|
||||||
|
};
|
||||||
|
|
||||||
|
AsyncComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
&mut self,
|
||||||
|
msg: Self::Input,
|
||||||
|
_sender: AsyncComponentSender<Self>,
|
||||||
|
_root: &Self::Root,
|
||||||
|
) {
|
||||||
|
match msg {
|
||||||
|
NetworkCardMsg::NetworkDelete => {
|
||||||
|
if let Some(daemon_client) = self.daemon_client.lock().await.as_mut() {
|
||||||
|
let mut client = networks_client::NetworksClient::new(daemon_client);
|
||||||
|
let _ = client
|
||||||
|
.network_delete(burrow_rpc::NetworkDeleteRequest { id: self.id })
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NetworkCardMsg::MoveUp => {
|
||||||
|
if self.index.checked_sub(1).is_some() {
|
||||||
|
if let Some(daemon_client) = self.daemon_client.lock().await.as_mut() {
|
||||||
|
let mut client = networks_client::NetworksClient::new(daemon_client);
|
||||||
|
let _ = client
|
||||||
|
.network_reorder(burrow_rpc::NetworkReorderRequest {
|
||||||
|
id: self.id,
|
||||||
|
index: self.index as i32 - 1,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NetworkCardMsg::MoveDown => {
|
||||||
|
if self.index + 1 < self.index_max {
|
||||||
|
if let Some(daemon_client) = self.daemon_client.lock().await.as_mut() {
|
||||||
|
let mut client = networks_client::NetworksClient::new(daemon_client);
|
||||||
|
let _ = client
|
||||||
|
.network_reorder(burrow_rpc::NetworkReorderRequest {
|
||||||
|
id: self.id,
|
||||||
|
index: self.index as i32 + 1,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
226
burrow-gtk/src/components/main/networks.rs
Normal file
226
burrow-gtk/src/components/main/networks.rs
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
use super::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
const RECONNECT_POLL_TIME: Duration = Duration::from_secs(3);
|
||||||
|
|
||||||
|
pub struct Networks {
|
||||||
|
daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
|
network_cards: Vec<AsyncController<NetworkCard>>,
|
||||||
|
networks_list_box: gtk::ListBox,
|
||||||
|
|
||||||
|
_network_state_worker: WorkerController<AsyncNetworkStateHandler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NetworksInit {
|
||||||
|
pub daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum NetworksMsg {
|
||||||
|
None,
|
||||||
|
NetworkList(Vec<burrow_rpc::Network>),
|
||||||
|
NetworkAdd,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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: 20,
|
||||||
|
set_margin_all: 5,
|
||||||
|
set_valign: Align::Fill,
|
||||||
|
set_vexpand: true,
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
|
set_spacing: 10,
|
||||||
|
set_margin_all: 5,
|
||||||
|
set_valign: Align::Start,
|
||||||
|
set_halign: Align::Center,
|
||||||
|
|
||||||
|
gtk::Label {
|
||||||
|
set_label: "Add Network",
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Button {
|
||||||
|
set_icon_name: "list-add",
|
||||||
|
set_margin_all: 12,
|
||||||
|
|
||||||
|
connect_clicked => NetworksMsg::NetworkAdd,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
gtk::ScrolledWindow {
|
||||||
|
set_valign: Align::Fill,
|
||||||
|
set_vexpand: true,
|
||||||
|
set_margin_bottom: 50,
|
||||||
|
set_margin_start: 50,
|
||||||
|
set_margin_end: 50,
|
||||||
|
|
||||||
|
#[name = "networks"]
|
||||||
|
gtk::ListBox {
|
||||||
|
set_vexpand: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init(
|
||||||
|
init: Self::Init,
|
||||||
|
root: Self::Root,
|
||||||
|
sender: AsyncComponentSender<Self>,
|
||||||
|
) -> AsyncComponentParts<Self> {
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
let network_cards = vec![
|
||||||
|
NetworkCard::builder()
|
||||||
|
.launch(NetworkCardInit {
|
||||||
|
id: 0,
|
||||||
|
index: 0,
|
||||||
|
index_max: 3,
|
||||||
|
daemon_client: Arc::clone(&init.daemon_client),
|
||||||
|
name: "Hello".to_owned(),
|
||||||
|
enabled: true,
|
||||||
|
})
|
||||||
|
.forward(sender.input_sender(), |_| NetworksMsg::None),
|
||||||
|
NetworkCard::builder()
|
||||||
|
.launch(NetworkCardInit {
|
||||||
|
id: 1,
|
||||||
|
index: 1,
|
||||||
|
index_max: 3,
|
||||||
|
daemon_client: Arc::clone(&init.daemon_client),
|
||||||
|
name: "World".to_owned(),
|
||||||
|
enabled: false,
|
||||||
|
})
|
||||||
|
.forward(sender.input_sender(), |_| NetworksMsg::None),
|
||||||
|
NetworkCard::builder()
|
||||||
|
.launch(NetworkCardInit {
|
||||||
|
id: 2,
|
||||||
|
index: 2,
|
||||||
|
index_max: 3,
|
||||||
|
daemon_client: Arc::clone(&init.daemon_client),
|
||||||
|
name: "Yay".to_owned(),
|
||||||
|
enabled: false,
|
||||||
|
})
|
||||||
|
.forward(sender.input_sender(), |_| NetworksMsg::None),
|
||||||
|
NetworkCard::builder()
|
||||||
|
.launch(NetworkCardInit {
|
||||||
|
id: 2,
|
||||||
|
index: 2,
|
||||||
|
index_max: 3,
|
||||||
|
daemon_client: Arc::clone(&init.daemon_client),
|
||||||
|
name: "Yay".to_owned(),
|
||||||
|
enabled: false,
|
||||||
|
})
|
||||||
|
.forward(sender.input_sender(), |_| NetworksMsg::None),
|
||||||
|
NetworkCard::builder()
|
||||||
|
.launch(NetworkCardInit {
|
||||||
|
id: 2,
|
||||||
|
index: 2,
|
||||||
|
index_max: 3,
|
||||||
|
daemon_client: Arc::clone(&init.daemon_client),
|
||||||
|
name: "Yay".to_owned(),
|
||||||
|
enabled: false,
|
||||||
|
})
|
||||||
|
.forward(sender.input_sender(), |_| NetworksMsg::None),
|
||||||
|
];
|
||||||
|
for network_card in network_cards.iter() {
|
||||||
|
widgets.networks.append(network_card.widget());
|
||||||
|
}
|
||||||
|
// let network_cards = vec![];
|
||||||
|
|
||||||
|
let model = Networks {
|
||||||
|
daemon_client: init.daemon_client,
|
||||||
|
network_cards,
|
||||||
|
networks_list_box: widgets.networks.clone(),
|
||||||
|
|
||||||
|
_network_state_worker: AsyncNetworkStateHandler::builder()
|
||||||
|
.detach_worker(())
|
||||||
|
.forward(sender.input_sender(), |msg| msg),
|
||||||
|
};
|
||||||
|
|
||||||
|
AsyncComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
&mut self,
|
||||||
|
msg: Self::Input,
|
||||||
|
sender: AsyncComponentSender<Self>,
|
||||||
|
_root: &Self::Root,
|
||||||
|
) {
|
||||||
|
match msg {
|
||||||
|
NetworksMsg::NetworkList(networks) => {
|
||||||
|
for network_card in self.network_cards.iter() {
|
||||||
|
self.networks_list_box
|
||||||
|
.remove(&network_card.widget().clone());
|
||||||
|
}
|
||||||
|
self.network_cards.clear();
|
||||||
|
|
||||||
|
let index_max = networks.len();
|
||||||
|
for (index, network) in networks.iter().enumerate() {
|
||||||
|
let network_card = NetworkCard::builder()
|
||||||
|
.launch(NetworkCardInit {
|
||||||
|
id: network.id,
|
||||||
|
index,
|
||||||
|
index_max,
|
||||||
|
daemon_client: Arc::clone(&self.daemon_client),
|
||||||
|
name: format!("ID: {}, TYPE: {}", network.id, network.r#type),
|
||||||
|
enabled: false,
|
||||||
|
})
|
||||||
|
.forward(sender.input_sender(), |_| NetworksMsg::None);
|
||||||
|
self.networks_list_box.append(network_card.widget());
|
||||||
|
self.network_cards.push(network_card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NetworksMsg::NetworkAdd => {
|
||||||
|
if let Some(daemon_client) = self.daemon_client.lock().await.as_mut() {
|
||||||
|
let mut client = networks_client::NetworksClient::new(daemon_client);
|
||||||
|
let _ = client.network_add(burrow_rpc::Empty {}).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AsyncNetworkStateHandler;
|
||||||
|
|
||||||
|
impl Worker for AsyncNetworkStateHandler {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = NetworksMsg;
|
||||||
|
|
||||||
|
fn init(_: Self::Init, sender: ComponentSender<Self>) -> Self {
|
||||||
|
sender.input(());
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _: (), sender: ComponentSender<Self>) {
|
||||||
|
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 = networks_client::NetworksClient::new(conn);
|
||||||
|
if let Ok(mut res) = client.network_list(burrow_rpc::Empty {}).await {
|
||||||
|
let stream = res.get_mut();
|
||||||
|
while let Ok(Some(msg)) = stream.message().await {
|
||||||
|
sender
|
||||||
|
.output(NetworksMsg::NetworkList(msg.network))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokio::time::sleep(RECONNECT_POLL_TIME).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rt.block_on(task).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
158
burrow-gtk/src/components/main/switch.rs
Normal file
158
burrow-gtk/src/components/main/switch.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
use super::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
const RECONNECT_POLL_TIME: Duration = Duration::from_secs(3);
|
||||||
|
|
||||||
|
pub struct Switch {
|
||||||
|
daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
|
switch: gtk::Switch,
|
||||||
|
|
||||||
|
_tunnel_state_worker: WorkerController<AsyncTunnelStateHandler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SwitchInit {
|
||||||
|
pub daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum SwitchMsg {
|
||||||
|
None,
|
||||||
|
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!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
|
set_spacing: 10,
|
||||||
|
set_margin_all: 5,
|
||||||
|
set_halign: Align::Center,
|
||||||
|
|
||||||
|
gtk::Label {
|
||||||
|
set_label: "Burrow Tunnel",
|
||||||
|
},
|
||||||
|
|
||||||
|
#[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<Self>,
|
||||||
|
) -> AsyncComponentParts<Self> {
|
||||||
|
let switch_sender = sender.clone();
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
let model = Switch {
|
||||||
|
daemon_client: init.daemon_client,
|
||||||
|
switch: widgets.switch.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<Self>,
|
||||||
|
_root: &Self::Root,
|
||||||
|
) {
|
||||||
|
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 => {
|
||||||
|
// TODO: Figure out best way for error handling.
|
||||||
|
let _ = client.tunnel_start(burrow_rpc::Empty {}).await;
|
||||||
|
}
|
||||||
|
Self::Input::Stop => {
|
||||||
|
// TODO: Figure out best way for error handling.
|
||||||
|
let _ = client.tunnel_stop(burrow_rpc::Empty {}).await;
|
||||||
|
}
|
||||||
|
Self::Input::SwitchSetStart => {
|
||||||
|
self.switch.set_active(true);
|
||||||
|
}
|
||||||
|
Self::Input::SwitchSetStop => {
|
||||||
|
self.switch.set_active(false);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AsyncTunnelStateHandler;
|
||||||
|
|
||||||
|
impl Worker for AsyncTunnelStateHandler {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = SwitchMsg;
|
||||||
|
|
||||||
|
fn init(_: Self::Init, sender: ComponentSender<Self>) -> Self {
|
||||||
|
sender.input(());
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _: (), sender: ComponentSender<Self>) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
107
burrow-gtk/src/components/main_screen.rs
Normal file
107
burrow-gtk/src/components/main_screen.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub struct MainScreen {
|
||||||
|
_switch: AsyncController<main::Switch>,
|
||||||
|
_networks: AsyncController<main::Networks>,
|
||||||
|
content_box: gtk::Box,
|
||||||
|
daemon_status_banner: adw::Banner,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MainScreenInit {
|
||||||
|
pub daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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_vexpand: true,
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
set_spacing: 5,
|
||||||
|
set_margin_all: 5,
|
||||||
|
set_valign: Align::Start,
|
||||||
|
|
||||||
|
#[name(daemon_status_banner)]
|
||||||
|
adw::Banner {
|
||||||
|
set_title: "Burrow is not running!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
#[name(content)]
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
set_spacing: 10,
|
||||||
|
set_margin_all: 5,
|
||||||
|
set_valign: Align::Fill,
|
||||||
|
set_vexpand: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init(
|
||||||
|
init: Self::Init,
|
||||||
|
root: Self::Root,
|
||||||
|
sender: AsyncComponentSender<Self>,
|
||||||
|
) -> AsyncComponentParts<Self> {
|
||||||
|
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(switch.widget());
|
||||||
|
widgets.content.append(networks.widget());
|
||||||
|
|
||||||
|
let model = MainScreen {
|
||||||
|
_switch: switch,
|
||||||
|
_networks: networks,
|
||||||
|
content_box: widgets.content.clone(),
|
||||||
|
daemon_status_banner: widgets.daemon_status_banner.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
AsyncComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
&mut self,
|
||||||
|
msg: Self::Input,
|
||||||
|
_: AsyncComponentSender<Self>,
|
||||||
|
_root: &Self::Root,
|
||||||
|
) {
|
||||||
|
match msg {
|
||||||
|
MainScreenMsg::DaemonDisconnect => {
|
||||||
|
self.daemon_status_banner.set_revealed(true);
|
||||||
|
self.content_box.set_sensitive(false);
|
||||||
|
}
|
||||||
|
MainScreenMsg::DaemonReconnect => {
|
||||||
|
self.daemon_status_banner.set_revealed(false);
|
||||||
|
self.content_box.set_sensitive(true);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use burrow::{DaemonClient, DaemonCommand, DaemonResponseData};
|
|
||||||
use gtk::Align;
|
use gtk::Align;
|
||||||
use relm4::{
|
use relm4::{
|
||||||
component::{
|
component::{
|
||||||
|
|
@ -8,14 +7,23 @@ use relm4::{
|
||||||
AsyncController,
|
AsyncController,
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
Worker, WorkerController,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
pub mod burrow_rpc {
|
||||||
|
tonic::include_proto!("burrow");
|
||||||
|
}
|
||||||
|
use burrow_rpc::{networks_client, tunnel_client};
|
||||||
|
use tonic::transport::Channel;
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
|
mod main;
|
||||||
|
mod main_screen;
|
||||||
mod settings;
|
mod settings;
|
||||||
mod settings_screen;
|
mod settings_screen;
|
||||||
mod switch_screen;
|
// mod switch_screen;
|
||||||
|
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
pub use settings::{DaemonGroupMsg, DiagGroupMsg};
|
pub use settings::{DaemonGroupMsg, DiagGroupMsg};
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ use std::process::Command;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DaemonGroup {
|
pub struct DaemonGroup {
|
||||||
system_setup: SystemSetup,
|
system_setup: SystemSetup,
|
||||||
daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
already_running: bool,
|
already_running: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DaemonGroupInit {
|
pub struct DaemonGroupInit {
|
||||||
pub daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
pub daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
pub system_setup: SystemSetup,
|
pub system_setup: SystemSetup,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,7 +48,6 @@ impl AsyncComponent for DaemonGroup {
|
||||||
root: Self::Root,
|
root: Self::Root,
|
||||||
sender: AsyncComponentSender<Self>,
|
sender: AsyncComponentSender<Self>,
|
||||||
) -> AsyncComponentParts<Self> {
|
) -> AsyncComponentParts<Self> {
|
||||||
// Should be impossible to panic here
|
|
||||||
let model = DaemonGroup {
|
let model = DaemonGroup {
|
||||||
system_setup: init.system_setup,
|
system_setup: init.system_setup,
|
||||||
daemon_client: init.daemon_client.clone(),
|
daemon_client: init.daemon_client.clone(),
|
||||||
|
|
@ -68,40 +67,9 @@ impl AsyncComponent for DaemonGroup {
|
||||||
) {
|
) {
|
||||||
match msg {
|
match msg {
|
||||||
DaemonGroupMsg::LaunchLocal => {
|
DaemonGroupMsg::LaunchLocal => {
|
||||||
let burrow_original_bin = std::env::vars()
|
if let Err(e) = launch_local() {
|
||||||
.find(|(k, _)| k == "APPDIR")
|
error!("Failed to launch local daemon at: {}", e);
|
||||||
.map(|(_, v)| v + "/usr/bin/burrow")
|
};
|
||||||
.unwrap_or("/usr/bin/burrow".to_owned());
|
|
||||||
|
|
||||||
let mut burrow_bin =
|
|
||||||
String::from_utf8(Command::new("mktemp").output().unwrap().stdout).unwrap();
|
|
||||||
burrow_bin.pop();
|
|
||||||
|
|
||||||
let privileged_spawn_script = format!(
|
|
||||||
r#"TEMP=$(mktemp -p /root)
|
|
||||||
cp {} $TEMP
|
|
||||||
chmod +x $TEMP
|
|
||||||
setcap CAP_NET_BIND_SERVICE,CAP_NET_ADMIN+eip $TEMP
|
|
||||||
mv $TEMP /tmp/burrow-detached-daemon"#,
|
|
||||||
burrow_original_bin
|
|
||||||
)
|
|
||||||
.replace('\n', "&&");
|
|
||||||
|
|
||||||
// TODO: Handle error condition
|
|
||||||
|
|
||||||
Command::new("pkexec")
|
|
||||||
.arg("sh")
|
|
||||||
.arg("-c")
|
|
||||||
.arg(privileged_spawn_script)
|
|
||||||
.arg(&burrow_bin)
|
|
||||||
.output()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Command::new("/tmp/burrow-detached-daemon")
|
|
||||||
.env("RUST_LOG", "debug")
|
|
||||||
.arg("daemon")
|
|
||||||
.spawn()
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
DaemonGroupMsg::DaemonStateChange => {
|
DaemonGroupMsg::DaemonStateChange => {
|
||||||
self.already_running = self.daemon_client.lock().await.is_some();
|
self.already_running = self.daemon_client.lock().await.is_some();
|
||||||
|
|
@ -109,3 +77,49 @@ mv $TEMP /tmp/burrow-detached-daemon"#,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn launch_local() -> Result<()> {
|
||||||
|
const BURROW_LOCAL_DAEMON_PATH: &str = "/tmp/burrow-detached-daemon";
|
||||||
|
|
||||||
|
let burrow_original_bin = std::env::vars()
|
||||||
|
.find(|(k, _)| k == "APPDIR")
|
||||||
|
.map(|(_, v)| v + "/usr/bin/burrow")
|
||||||
|
.unwrap_or("/usr/bin/burrow".to_owned());
|
||||||
|
|
||||||
|
Command::new("cp")
|
||||||
|
.arg(&burrow_original_bin)
|
||||||
|
.arg(BURROW_LOCAL_DAEMON_PATH)
|
||||||
|
.output()
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Copying {} to {}",
|
||||||
|
burrow_original_bin, BURROW_LOCAL_DAEMON_PATH
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut burrow_bin = String::from_utf8(Command::new("mktemp").output()?.stdout)?;
|
||||||
|
burrow_bin.pop();
|
||||||
|
|
||||||
|
let privileged_spawn_script = format!(
|
||||||
|
r#"chmod +x {}
|
||||||
|
setcap CAP_NET_BIND_SERVICE,CAP_NET_ADMIN+eip {}"#,
|
||||||
|
BURROW_LOCAL_DAEMON_PATH, BURROW_LOCAL_DAEMON_PATH
|
||||||
|
)
|
||||||
|
.replace('\n', "&&");
|
||||||
|
|
||||||
|
// Need to be more careful here.
|
||||||
|
Command::new("pkexec")
|
||||||
|
.arg("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(privileged_spawn_script)
|
||||||
|
.arg(&burrow_bin)
|
||||||
|
.output()
|
||||||
|
.with_context(|| format!("Priviledged call to {}", burrow_bin))?;
|
||||||
|
|
||||||
|
Command::new(BURROW_LOCAL_DAEMON_PATH)
|
||||||
|
.env("RUST_LOG", "debug")
|
||||||
|
.arg("daemon")
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use super::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DiagGroup {
|
pub struct DiagGroup {
|
||||||
daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
|
|
||||||
system_setup: SystemSetup,
|
system_setup: SystemSetup,
|
||||||
service_installed: StatusTernary,
|
service_installed: StatusTernary,
|
||||||
|
|
@ -12,23 +12,23 @@ pub struct DiagGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DiagGroupInit {
|
pub struct DiagGroupInit {
|
||||||
pub daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
pub daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
pub system_setup: SystemSetup,
|
pub system_setup: SystemSetup,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagGroup {
|
impl DiagGroup {
|
||||||
async fn new(daemon_client: Arc<Mutex<Option<DaemonClient>>>) -> Result<Self> {
|
async fn new(daemon_client: Arc<Mutex<Option<Channel>>>) -> Self {
|
||||||
let system_setup = SystemSetup::new();
|
let system_setup = SystemSetup::new();
|
||||||
let daemon_running = daemon_client.lock().await.is_some();
|
let daemon_running = daemon_client.lock().await.is_some();
|
||||||
|
|
||||||
Ok(Self {
|
Self {
|
||||||
service_installed: system_setup.is_service_installed()?,
|
service_installed: system_setup.is_service_installed(),
|
||||||
socket_installed: system_setup.is_socket_installed()?,
|
socket_installed: system_setup.is_socket_installed(),
|
||||||
socket_enabled: system_setup.is_socket_enabled()?,
|
socket_enabled: system_setup.is_socket_enabled(),
|
||||||
daemon_running,
|
daemon_running,
|
||||||
system_setup,
|
system_setup,
|
||||||
daemon_client,
|
daemon_client,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,7 +95,7 @@ impl AsyncComponent for DiagGroup {
|
||||||
sender: AsyncComponentSender<Self>,
|
sender: AsyncComponentSender<Self>,
|
||||||
) -> AsyncComponentParts<Self> {
|
) -> AsyncComponentParts<Self> {
|
||||||
// Should be impossible to panic here
|
// Should be impossible to panic here
|
||||||
let model = DiagGroup::new(init.daemon_client).await.unwrap();
|
let model = DiagGroup::new(init.daemon_client).await;
|
||||||
|
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
|
@ -111,7 +111,7 @@ impl AsyncComponent for DiagGroup {
|
||||||
match msg {
|
match msg {
|
||||||
DiagGroupMsg::Refresh => {
|
DiagGroupMsg::Refresh => {
|
||||||
// Should be impossible to panic here
|
// Should be impossible to panic here
|
||||||
*self = Self::new(Arc::clone(&self.daemon_client)).await.unwrap();
|
*self = Self::new(Arc::clone(&self.daemon_client)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ pub struct SettingsScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SettingsScreenInit {
|
pub struct SettingsScreenInit {
|
||||||
pub daemon_client: Arc<Mutex<Option<DaemonClient>>>,
|
pub daemon_client: Arc<Mutex<Option<Channel>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
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>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub enum SwitchScreenMsg {
|
|
||||||
DaemonReconnect,
|
|
||||||
DaemonDisconnect,
|
|
||||||
Start,
|
|
||||||
Stop,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[relm4::component(pub, async)]
|
|
||||||
impl AsyncComponent for SwitchScreen {
|
|
||||||
type Init = SwitchScreenInit;
|
|
||||||
type Input = SwitchScreenMsg;
|
|
||||||
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|
|
|
||||||
sender.input(if switch.is_active() { SwitchScreenMsg::Start } else { SwitchScreenMsg::Stop })
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn init(
|
|
||||||
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,
|
|
||||||
switch: widgets.switch.clone(),
|
|
||||||
switch_screen: widgets.switch_screen.clone(),
|
|
||||||
disconnected_banner: widgets.setup_banner.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
AsyncComponentParts { model, widgets }
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update(
|
|
||||||
&mut self,
|
|
||||||
msg: Self::Input,
|
|
||||||
_: 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::Input::Stop => {
|
|
||||||
if let Err(_e) = daemon_client.send_command(DaemonCommand::Stop).await {
|
|
||||||
disconnected_daemon_client = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
39
burrow-gtk/src/components/template.rs
Normal file
39
burrow-gtk/src/components/template.rs
Normal file
|
|
@ -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<Self>,
|
||||||
|
) -> AsyncComponentParts<Self> {
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
let model = Template {};
|
||||||
|
|
||||||
|
AsyncComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
&mut self,
|
||||||
|
msg: Self::Input,
|
||||||
|
_: AsyncComponentSender<Self>,
|
||||||
|
_root: &Self::Root,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
17
burrow-gtk/src/daemon.rs
Normal file
17
burrow-gtk/src/daemon.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use hyper_util::rt::TokioIo;
|
||||||
|
use tokio::net::UnixStream;
|
||||||
|
use tonic::transport::{Channel, Endpoint, Uri};
|
||||||
|
use tower::service_fn;
|
||||||
|
|
||||||
|
const BURROW_RPC_SOCKET_PATH: &str = "/run/burrow.sock";
|
||||||
|
|
||||||
|
pub async fn daemon_connect() -> Result<Channel> {
|
||||||
|
Ok(Endpoint::try_from("http://[::]:50051")?
|
||||||
|
.connect_with_connector(service_fn(|_: Uri| async {
|
||||||
|
Ok::<_, std::io::Error>(TokioIo::new(
|
||||||
|
UnixStream::connect(BURROW_RPC_SOCKET_PATH).await?,
|
||||||
|
))
|
||||||
|
}))
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ use std::{fmt::Display, fs, process::Command};
|
||||||
const SYSTEMD_SOCKET_LOC: &str = "/etc/systemd/system/burrow.socket";
|
const SYSTEMD_SOCKET_LOC: &str = "/etc/systemd/system/burrow.socket";
|
||||||
const SYSTEMD_SERVICE_LOC: &str = "/etc/systemd/system/burrow.service";
|
const SYSTEMD_SERVICE_LOC: &str = "/etc/systemd/system/burrow.service";
|
||||||
|
|
||||||
// I don't like this type very much.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum StatusTernary {
|
pub enum StatusTernary {
|
||||||
True,
|
True,
|
||||||
|
|
@ -33,35 +32,41 @@ impl SystemSetup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_service_installed(&self) -> Result<StatusTernary> {
|
pub fn is_service_installed(&self) -> StatusTernary {
|
||||||
match self {
|
match self {
|
||||||
SystemSetup::Systemd => Ok(fs::metadata(SYSTEMD_SERVICE_LOC).is_ok().into()),
|
SystemSetup::Systemd => fs::metadata(SYSTEMD_SERVICE_LOC).is_ok().into(),
|
||||||
SystemSetup::AppImage => Ok(StatusTernary::NA),
|
SystemSetup::AppImage => StatusTernary::NA,
|
||||||
SystemSetup::Other => Ok(StatusTernary::NA),
|
SystemSetup::Other => StatusTernary::NA,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_socket_installed(&self) -> Result<StatusTernary> {
|
pub fn is_socket_installed(&self) -> StatusTernary {
|
||||||
match self {
|
match self {
|
||||||
SystemSetup::Systemd => Ok(fs::metadata(SYSTEMD_SOCKET_LOC).is_ok().into()),
|
SystemSetup::Systemd => fs::metadata(SYSTEMD_SOCKET_LOC).is_ok().into(),
|
||||||
SystemSetup::AppImage => Ok(StatusTernary::NA),
|
SystemSetup::AppImage => StatusTernary::NA,
|
||||||
SystemSetup::Other => Ok(StatusTernary::NA),
|
SystemSetup::Other => StatusTernary::NA,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_socket_enabled(&self) -> Result<StatusTernary> {
|
pub fn is_socket_enabled(&self) -> StatusTernary {
|
||||||
match self {
|
match self {
|
||||||
SystemSetup::Systemd => {
|
SystemSetup::Systemd => {
|
||||||
let output = Command::new("systemctl")
|
let Ok(output) = Command::new("systemctl")
|
||||||
.arg("is-enabled")
|
.arg("is-enabled")
|
||||||
.arg("burrow.socket")
|
.arg("burrow.socket")
|
||||||
.output()?
|
.output()
|
||||||
.stdout;
|
.map(|o| o.stdout)
|
||||||
let output = String::from_utf8(output)?;
|
.inspect_err(|e| {
|
||||||
Ok((output == "enabled\n").into())
|
error!("Failed to run `systemctl is-enabled burrow.socket` {}", e)
|
||||||
|
})
|
||||||
|
else {
|
||||||
|
return StatusTernary::NA;
|
||||||
|
};
|
||||||
|
let output = String::from_utf8(output).unwrap();
|
||||||
|
(output == "enabled\n").into()
|
||||||
}
|
}
|
||||||
SystemSetup::AppImage => Ok(StatusTernary::NA),
|
SystemSetup::AppImage => StatusTernary::NA,
|
||||||
SystemSetup::Other => Ok(StatusTernary::NA),
|
SystemSetup::Other => StatusTernary::NA,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
|
use log::error;
|
||||||
|
|
||||||
pub mod components;
|
pub mod components;
|
||||||
|
mod daemon;
|
||||||
mod diag;
|
mod diag;
|
||||||
|
|
||||||
// Generated using meson
|
// Generated using meson
|
||||||
mod config;
|
mod config;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
colog::default_builder()
|
||||||
|
.filter(None, log::LevelFilter::Error)
|
||||||
|
.init();
|
||||||
components::App::run();
|
components::App::run();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ Note that the flatpak version can compile but will not run properly!
|
||||||
1. Install build dependencies
|
1. Install build dependencies
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo apt install -y clang meson cmake pkg-config libgtk-4-dev libadwaita-1-dev gettext desktop-file-utils
|
sudo apt install -y clang meson cmake pkg-config libgtk-4-dev libadwaita-1-dev gettext desktop-file-utils libsqlite3-dev protobuf-compiler libprotobuf-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install flatpak builder (Optional)
|
2. Install flatpak builder (Optional)
|
||||||
|
|
@ -38,7 +38,7 @@ Note that the flatpak version can compile but will not run properly!
|
||||||
1. Install build dependencies
|
1. Install build dependencies
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo dnf install -y clang ninja-build cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib
|
sudo dnf install -y clang ninja-build cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib sqlite-devel protobuf-compiler protobuf-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install flatpak builder (Optional)
|
2. Install flatpak builder (Optional)
|
||||||
|
|
@ -61,7 +61,7 @@ Note that the flatpak version can compile but will not run properly!
|
||||||
1. Install build dependencies
|
1. Install build dependencies
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo xbps-install -Sy gcc clang meson cmake pkg-config gtk4-devel gettext desktop-file-utils gtk4-update-icon-cache appstream-glib
|
sudo xbps-install -Sy gcc clang meson cmake pkg-config gtk4-devel gettext desktop-file-utils gtk4-update-icon-cache appstream-glib sqlite-devel protobuf protobuf-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install flatpak builder (Optional)
|
2. Install flatpak builder (Optional)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue