UI(system tray menu): added pause/resume menu items + replaced subscription to obsolete "runtime:subsystems" by "runtime:system/status"
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod spn;
|
pub mod spn;
|
||||||
pub mod notification;
|
pub mod notification;
|
||||||
pub mod subsystem;
|
pub mod system_status_types;
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
use serde::*;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
|
||||||
pub struct ModuleStatus {
|
|
||||||
#[serde(rename = "Name")]
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
#[serde(rename = "Enabled")]
|
|
||||||
pub enabled: bool,
|
|
||||||
|
|
||||||
#[serde(rename = "Status")]
|
|
||||||
pub status: u8,
|
|
||||||
|
|
||||||
#[serde(rename = "FailureStatus")]
|
|
||||||
pub failure_status: u8,
|
|
||||||
|
|
||||||
#[serde(rename = "FailureID")]
|
|
||||||
pub failure_id: String,
|
|
||||||
|
|
||||||
#[serde(rename = "FailureMsg")]
|
|
||||||
pub failure_msg: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
|
||||||
pub struct Subsystem {
|
|
||||||
#[serde(rename = "ID")]
|
|
||||||
pub id: String,
|
|
||||||
|
|
||||||
#[serde(rename = "Name")]
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
#[serde(rename = "Description")]
|
|
||||||
pub description: String,
|
|
||||||
|
|
||||||
#[serde(rename = "Modules")]
|
|
||||||
pub module_status: Vec<ModuleStatus>,
|
|
||||||
|
|
||||||
#[serde(rename = "FailureStatus")]
|
|
||||||
pub failure_status: u8,
|
|
||||||
}
|
|
||||||
pub const FAILURE_NONE: u8 = 0;
|
|
||||||
pub const FAILURE_HINT: u8 = 1;
|
|
||||||
pub const FAILURE_WARNING: u8 = 2;
|
|
||||||
pub const FAILURE_ERROR: u8 = 3;
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum StateType {
|
||||||
|
#[serde(rename = "")]
|
||||||
|
Undefined,
|
||||||
|
#[serde(rename = "hint")]
|
||||||
|
Hint,
|
||||||
|
#[serde(rename = "warning")]
|
||||||
|
Warning,
|
||||||
|
#[serde(rename = "error")]
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct State {
|
||||||
|
#[serde(rename = "ID")]
|
||||||
|
pub id: String,
|
||||||
|
#[serde(rename = "Name")]
|
||||||
|
pub name: String,
|
||||||
|
#[serde(rename = "Message")]
|
||||||
|
pub message: Option<String>,
|
||||||
|
#[serde(rename = "Type")]
|
||||||
|
pub state_type: Option<StateType>,
|
||||||
|
#[serde(rename = "Time")]
|
||||||
|
pub time: Option<String>, // time.Time serialized by GoLang
|
||||||
|
#[serde(rename = "Data")]
|
||||||
|
pub data: Option<serde_json::Value>, // any type
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct StateUpdate {
|
||||||
|
#[serde(rename = "Module")]
|
||||||
|
pub module: String,
|
||||||
|
#[serde(rename = "States")]
|
||||||
|
pub states: Option<Vec<State>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct WorstState {
|
||||||
|
#[serde(rename = "Module")]
|
||||||
|
pub module: String,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SystemStatus {
|
||||||
|
#[serde(rename = "Modules")]
|
||||||
|
pub modules: Vec<StateUpdate>,
|
||||||
|
#[serde(rename = "WorstState")]
|
||||||
|
pub worst_state: Option<WorstState>,
|
||||||
|
|
||||||
|
// add more fields when needed
|
||||||
|
// ...
|
||||||
|
}
|
||||||
@@ -217,6 +217,47 @@ impl<R: Runtime> PortmasterInterface<R> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_resume(&self) {
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
match client
|
||||||
|
.post(format!("{}control/resume", PORTMASTER_BASE_URL))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(v) => {
|
||||||
|
debug!("resume request sent {:?}", v);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("failed to send resume request {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn set_pause(&self, duration_seconds: u64, spn_only: bool) {
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
match client
|
||||||
|
.post(format!("{}control/pause", PORTMASTER_BASE_URL))
|
||||||
|
.json(&serde_json::json!({
|
||||||
|
"duration": duration_seconds,
|
||||||
|
"onlySPN": spn_only
|
||||||
|
}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(v) => {
|
||||||
|
debug!("pause request sent {:?}", v);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("failed to send pause request {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//// Internal functions
|
//// Internal functions
|
||||||
fn start_notification_handler(&self) {
|
fn start_notification_handler(&self) {
|
||||||
if let Some(api) = self.get_api() {
|
if let Some(api) = self.get_api() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use std::{collections::HashMap, sync::atomic::Ordering};
|
use std::{sync::atomic::Ordering};
|
||||||
|
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use tauri::{
|
use tauri::{
|
||||||
@@ -16,11 +16,11 @@ use crate::config;
|
|||||||
use crate::{
|
use crate::{
|
||||||
portapi::{
|
portapi::{
|
||||||
client::PortAPI,
|
client::PortAPI,
|
||||||
message::ParseError,
|
message::{ParseError},
|
||||||
models::{
|
models::{
|
||||||
config::BooleanValue,
|
config::BooleanValue,
|
||||||
spn::SPNStatus,
|
spn::SPNStatus,
|
||||||
subsystem::{self, Subsystem},
|
system_status_types::{self, SystemStatus},
|
||||||
},
|
},
|
||||||
types::{Request, Response},
|
types::{Request, Response},
|
||||||
},
|
},
|
||||||
@@ -59,6 +59,14 @@ const FORCE_SHOW_KEY: &str = "force-show";
|
|||||||
const PM_TRAY_ICON_ID: &str = "pm_icon";
|
const PM_TRAY_ICON_ID: &str = "pm_icon";
|
||||||
const PM_TRAY_MENU_ID: &str = "pm_tray_menu";
|
const PM_TRAY_MENU_ID: &str = "pm_tray_menu";
|
||||||
|
|
||||||
|
const PAUSE_SPN_5_KEY: &str = "pause_spn_5";
|
||||||
|
const PAUSE_SPN_15_KEY: &str = "pause_spn_15";
|
||||||
|
const PAUSE_SPN_60_KEY: &str = "pause_spn_60";
|
||||||
|
const PAUSE_PM_5_KEY: &str = "pause_pm_5";
|
||||||
|
const PAUSE_PM_15_KEY: &str = "pause_pm_15";
|
||||||
|
const PAUSE_PM_60_KEY: &str = "pause_pm_60";
|
||||||
|
const RESUME_KEY: &str = "resume_all";
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
|
|
||||||
fn get_theme_mode() -> dark_light::Mode {
|
fn get_theme_mode() -> dark_light::Mode {
|
||||||
@@ -142,7 +150,7 @@ fn build_tray_menu(
|
|||||||
.enabled(false)
|
.enabled(false)
|
||||||
.build(app)
|
.build(app)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Setup SPN button
|
// Setup SPN button
|
||||||
let spn_button_text = match spn_status_text {
|
let spn_button_text = match spn_status_text {
|
||||||
"disabled" => "Enable SPN",
|
"disabled" => "Enable SPN",
|
||||||
@@ -152,6 +160,7 @@ fn build_tray_menu(
|
|||||||
.build(app)
|
.build(app)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
// Setup Icon theme submenu
|
||||||
let system_theme = MenuItemBuilder::with_id(SYSTEM_THEME_KEY, "System")
|
let system_theme = MenuItemBuilder::with_id(SYSTEM_THEME_KEY, "System")
|
||||||
.build(app)
|
.build(app)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -165,11 +174,38 @@ fn build_tray_menu(
|
|||||||
.items(&[&system_theme, &light_theme, &dark_theme])
|
.items(&[&system_theme, &light_theme, &dark_theme])
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
|
|
||||||
|
// Setup Pause/Resume menu items
|
||||||
|
let disabled_spn = spn_status_text == "disabled";
|
||||||
|
let pause_spn_5min_item = MenuItemBuilder::with_id(PAUSE_SPN_5_KEY, "Pause SPN for 5 minutes").enabled(!disabled_spn).build(app)?;
|
||||||
|
let pause_spn_15min_item = MenuItemBuilder::with_id(PAUSE_SPN_15_KEY, "Pause SPN for 15 minutes").enabled(!disabled_spn).build(app)?;
|
||||||
|
let pause_spn_1hour_item = MenuItemBuilder::with_id(PAUSE_SPN_60_KEY, "Pause SPN for 1 hour").enabled(!disabled_spn).build(app)?;
|
||||||
|
|
||||||
|
let pause_pm_5min_item = MenuItemBuilder::with_id(PAUSE_PM_5_KEY, "Pause for 5 minutes").build(app)?;
|
||||||
|
let pause_pm_15min_item = MenuItemBuilder::with_id(PAUSE_PM_15_KEY, "Pause for 15 minutes").build(app)?;
|
||||||
|
let pause_pm_1hour_item = MenuItemBuilder::with_id(PAUSE_PM_60_KEY, "Pause for 1 hour").build(app)?;
|
||||||
|
|
||||||
|
let pause_menu = SubmenuBuilder::new(app, "Pause")
|
||||||
|
.items(&[
|
||||||
|
&pause_spn_5min_item,
|
||||||
|
&pause_spn_15min_item,
|
||||||
|
&pause_spn_1hour_item,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&pause_pm_5min_item,
|
||||||
|
&pause_pm_15min_item,
|
||||||
|
&pause_pm_1hour_item,
|
||||||
|
])
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let resume_item = MenuItemBuilder::with_id(RESUME_KEY, "Resume now").build(app)?;
|
||||||
|
|
||||||
|
/* DEV MENU
|
||||||
let force_show_window = MenuItemBuilder::with_id(FORCE_SHOW_KEY, "Force Show UI").build(app)?;
|
let force_show_window = MenuItemBuilder::with_id(FORCE_SHOW_KEY, "Force Show UI").build(app)?;
|
||||||
let reload_btn = MenuItemBuilder::with_id(RELOAD_KEY, "Reload User Interface").build(app)?;
|
let reload_btn = MenuItemBuilder::with_id(RELOAD_KEY, "Reload User Interface").build(app)?;
|
||||||
let developer_menu = SubmenuBuilder::new(app, "Developer")
|
let developer_menu = SubmenuBuilder::new(app, "Developer")
|
||||||
.items(&[&reload_btn, &force_show_window])
|
.items(&[&reload_btn, &force_show_window])
|
||||||
.build()?;
|
.build()?;
|
||||||
|
*/
|
||||||
|
|
||||||
let menu = MenuBuilder::with_id(app, PM_TRAY_MENU_ID)
|
let menu = MenuBuilder::with_id(app, PM_TRAY_MENU_ID)
|
||||||
.items(&[
|
.items(&[
|
||||||
@@ -177,6 +213,9 @@ fn build_tray_menu(
|
|||||||
&PredefinedMenuItem::separator(app)?,
|
&PredefinedMenuItem::separator(app)?,
|
||||||
&global_status,
|
&global_status,
|
||||||
&PredefinedMenuItem::separator(app)?,
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&pause_menu,
|
||||||
|
&resume_item,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
&spn_status,
|
&spn_status,
|
||||||
&spn_button,
|
&spn_button,
|
||||||
&PredefinedMenuItem::separator(app)?,
|
&PredefinedMenuItem::separator(app)?,
|
||||||
@@ -184,7 +223,7 @@ fn build_tray_menu(
|
|||||||
&PredefinedMenuItem::separator(app)?,
|
&PredefinedMenuItem::separator(app)?,
|
||||||
&exit_ui_btn,
|
&exit_ui_btn,
|
||||||
&shutdown_btn,
|
&shutdown_btn,
|
||||||
&developer_menu,
|
//&developer_menu,
|
||||||
])
|
])
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
@@ -250,6 +289,15 @@ pub fn setup_tray_menu(
|
|||||||
SYSTEM_THEME_KEY => update_icon_theme(app, dark_light::Mode::Default),
|
SYSTEM_THEME_KEY => update_icon_theme(app, dark_light::Mode::Default),
|
||||||
DARK_THEME_KEY => update_icon_theme(app, dark_light::Mode::Dark),
|
DARK_THEME_KEY => update_icon_theme(app, dark_light::Mode::Dark),
|
||||||
LIGHT_THEME_KEY => update_icon_theme(app, dark_light::Mode::Light),
|
LIGHT_THEME_KEY => update_icon_theme(app, dark_light::Mode::Light),
|
||||||
|
|
||||||
|
PAUSE_SPN_5_KEY => app.portmaster().set_pause(60*5, true),
|
||||||
|
PAUSE_SPN_15_KEY => app.portmaster().set_pause(60*15, true),
|
||||||
|
PAUSE_SPN_60_KEY => app.portmaster().set_pause(60*60, true),
|
||||||
|
PAUSE_PM_5_KEY => app.portmaster().set_pause(60*5, false),
|
||||||
|
PAUSE_PM_15_KEY => app.portmaster().set_pause(60*15, false),
|
||||||
|
PAUSE_PM_60_KEY => app.portmaster().set_pause(60*60, false),
|
||||||
|
RESUME_KEY => app.portmaster().set_resume(),
|
||||||
|
|
||||||
other => {
|
other => {
|
||||||
error!("unknown menu event id: {}", other);
|
error!("unknown menu event id: {}", other);
|
||||||
}
|
}
|
||||||
@@ -275,33 +323,29 @@ pub fn setup_tray_menu(
|
|||||||
Ok(icon)
|
Ok(icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_icon(icon: AppIcon, subsystems: HashMap<String, Subsystem>, spn_status: String) {
|
pub fn update_icon(icon: AppIcon, system_status: SystemStatus, spn_status: String) {
|
||||||
// iterate over the subsystems and check if there's a module failure
|
// Extract the worst state type
|
||||||
let failure = subsystems.values().map(|s| &s.module_status).fold(
|
let worst_state_type = match system_status.worst_state {
|
||||||
(subsystem::FAILURE_NONE, "".to_string()),
|
Some(ws) => {
|
||||||
|mut acc, s| {
|
match ws.state.state_type {
|
||||||
for m in s {
|
Some(s) => s,
|
||||||
if m.failure_status > acc.0 {
|
None => system_status_types::StateType::Undefined
|
||||||
acc = (m.failure_status, m.failure_msg.clone())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
acc
|
}
|
||||||
},
|
None => system_status_types::StateType::Undefined
|
||||||
);
|
};
|
||||||
|
|
||||||
let mut status = "Secured".to_owned();
|
// Determine status and icon color in a single match expression
|
||||||
|
let (status, icon_color) = match worst_state_type {
|
||||||
if failure.0 != subsystem::FAILURE_NONE {
|
system_status_types::StateType::Error => ("Insecure", IconColor::Red),
|
||||||
status = failure.1;
|
system_status_types::StateType::Warning => ("Insecure", IconColor::Yellow),
|
||||||
}
|
_ => {
|
||||||
|
let color = match spn_status.as_str() {
|
||||||
let icon_color = match failure.0 {
|
"connected" | "connecting" => IconColor::Blue,
|
||||||
subsystem::FAILURE_WARNING => IconColor::Yellow,
|
_ => IconColor::Green,
|
||||||
subsystem::FAILURE_ERROR => IconColor::Red,
|
};
|
||||||
_ => match spn_status.as_str() {
|
("Secured", color)
|
||||||
"connected" | "connecting" => IconColor::Blue,
|
}
|
||||||
_ => IconColor::Green,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(menu) = build_tray_menu(icon.app_handle(), status.as_ref(), spn_status.as_str()) {
|
if let Ok(menu) = build_tray_menu(icon.app_handle(), status.as_ref(), spn_status.as_str()) {
|
||||||
@@ -322,9 +366,9 @@ pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut subsystem_subscription = match cli
|
let mut system_status_subscription = match cli
|
||||||
.request(Request::QuerySubscribe(
|
.request(Request::QuerySubscribe(
|
||||||
"query runtime:subsystems/".to_string(),
|
"query runtime:system/status".to_string(),
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -388,12 +432,16 @@ pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
|
|||||||
|
|
||||||
update_icon_color(&icon, IconColor::Blue);
|
update_icon_color(&icon, IconColor::Blue);
|
||||||
|
|
||||||
let mut subsystems: HashMap<String, Subsystem> = HashMap::new();
|
let mut system_status = SystemStatus {
|
||||||
|
modules: Vec::new(),
|
||||||
|
worst_state: None,
|
||||||
|
};
|
||||||
|
|
||||||
let mut spn_status: String = "".to_string();
|
let mut spn_status: String = "".to_string();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
msg = subsystem_subscription.recv() => {
|
msg = system_status_subscription.recv() => {
|
||||||
let msg = match msg {
|
let msg = match msg {
|
||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
None => { break }
|
None => { break }
|
||||||
@@ -407,17 +455,17 @@ pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some((_, payload)) = res {
|
if let Some((_, payload)) = res {
|
||||||
match payload.parse::<Subsystem>() {
|
match payload.parse::<SystemStatus>() {
|
||||||
Ok(n) => {
|
Ok(system_status_update) => {
|
||||||
subsystems.insert(n.id.clone(), n);
|
system_status.clone_from(&system_status_update);
|
||||||
update_icon(icon.clone(), subsystems.clone(), spn_status.clone());
|
update_icon(icon.clone(), system_status.clone(), spn_status.clone());
|
||||||
},
|
},
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
ParseError::Json(err) => {
|
ParseError::Json(err) => {
|
||||||
error!("failed to parse subsystem: {}", err);
|
error!("failed to parse SystemStatus: {}", err);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("unknown error when parsing notifications payload");
|
error!("unknown error when parsing SystemStatus payload");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -441,7 +489,7 @@ pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
|
|||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
debug!("SPN status update: {}", value.status);
|
debug!("SPN status update: {}", value.status);
|
||||||
spn_status.clone_from(&value.status);
|
spn_status.clone_from(&value.status);
|
||||||
update_icon(icon.clone(), subsystems.clone(), spn_status.clone());
|
update_icon(icon.clone(), system_status.clone(), spn_status.clone());
|
||||||
},
|
},
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
ParseError::Json(err) => {
|
ParseError::Json(err) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user