From d838ab4dae69620c802cb0a7d553ddbce31290d9 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 21 Jun 2025 11:28:11 +0200 Subject: [PATCH 01/19] don't use tooltips for notifications icons --- src/gui/pages/notifications_page.rs | 41 +++++++++-------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index b29f9e3e2..ecc9883a9 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -162,15 +162,10 @@ fn packets_notification_log<'a>( .height(Length::Fill) .spacing(30) .push( - Tooltip::new( - Icon::PacketsThreshold - .to_text() - .size(80) - .line_height(LineHeight::Relative(1.0)), - Text::new(packets_exceeded_translation(language)).font(font), - Position::FollowCursor, - ) - .class(ContainerType::Tooltip), + Icon::PacketsThreshold + .to_text() + .size(80) + .line_height(LineHeight::Relative(1.0)), ) .push( Column::new() @@ -243,15 +238,10 @@ fn bytes_notification_log<'a>( .align_y(Alignment::Center) .height(Length::Fill) .push( - Tooltip::new( - Icon::BytesThreshold - .to_text() - .size(80) - .line_height(LineHeight::Relative(1.0)), - Text::new(bytes_exceeded_translation(language)).font(font), - Position::FollowCursor, - ) - .class(ContainerType::Tooltip), + Icon::BytesThreshold + .to_text() + .size(80) + .line_height(LineHeight::Relative(1.0)), ) .push( Column::new() @@ -327,16 +317,11 @@ fn favorite_notification_log<'a>( .align_y(Alignment::Center) .height(Length::Fill) .push( - Tooltip::new( - Icon::Star - .to_text() - .size(80) - .class(TextType::Starred) - .line_height(LineHeight::Relative(1.0)), - Text::new(favorite_transmitted_translation(language)).font(font), - Position::FollowCursor, - ) - .class(ContainerType::Tooltip), + Icon::Star + .to_text() + .size(80) + .class(TextType::Starred) + .line_height(LineHeight::Relative(1.0)), ) .push( Column::new() From e3377a0e4f19c3c143b3f388f8472b18831b2ce4 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 21 Jun 2025 15:22:04 +0200 Subject: [PATCH 02/19] favorite notifications: show the amount of exchanged data --- src/gui/pages/notifications_page.rs | 42 ++++------- src/gui/pages/overview_page.rs | 98 ++++++++++++++++---------- src/gui/sniffer.rs | 22 +++--- src/networking/manage_packets.rs | 14 ---- src/networking/parse_packets.rs | 1 - src/networking/types/data_info.rs | 2 +- src/networking/types/data_info_host.rs | 2 +- src/networking/types/info_traffic.rs | 17 +++-- src/networking/types/traffic_type.rs | 2 +- src/notifications/notify_and_log.rs | 7 +- 10 files changed, 101 insertions(+), 106 deletions(-) diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index ecc9883a9..b2ea5e390 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -7,10 +7,11 @@ use iced::widget::{Space, button, vertical_space}; use iced::{Alignment, Font, Length}; use std::fmt::Write; -use crate::countries::country_utils::get_flag_tooltip; +use crate::chart::types::chart_type::ChartType; use crate::gui::components::header::get_button_settings; use crate::gui::components::tab::get_pages_tabs; use crate::gui::components::types::my_modal::MyModal; +use crate::gui::pages::overview_page::host_bar; use crate::gui::pages::types::settings_page::SettingsPage; use crate::gui::styles::container::ContainerType; use crate::gui::styles::scrollbar::ScrollbarType; @@ -289,28 +290,18 @@ fn bytes_notification_log<'a>( fn favorite_notification_log<'a>( logged_notification: FavoriteTransmitted, + chart_type: ChartType, language: Language, font: Font, ) -> Container<'a, Message, StyleType> { - let country = logged_notification.host.country; - let asn = &logged_notification.host.asn; - - let mut domain_asn_str = logged_notification.host.domain; - if !asn.name.is_empty() { - let _ = write!(domain_asn_str, " - {}", asn.name); - } - - let row_flag_details = Row::new() - .align_y(Alignment::Center) - .spacing(5) - .push(get_flag_tooltip( - country, - &logged_notification.data_info_host, - language, - font, - false, - )) - .push(Text::new(domain_asn_str).font(font)); + let host_bar = host_bar( + &logged_notification.host, + &logged_notification.data_info_host, + chart_type, + logged_notification.data_info_host.data_info, + font, + language, + ); let content = Row::new() .spacing(30) @@ -339,12 +330,8 @@ fn favorite_notification_log<'a>( .font(font), ), ) - .push( - Column::new() - .spacing(7) - .width(Length::Fill) - .push(row_flag_details), - ); + .push(Column::new().spacing(7).width(Length::Fill).push(host_bar)); + Container::new(content) .height(120) .width(800) @@ -378,6 +365,7 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> let ConfigSettings { style, language, .. } = sniffer.configs.settings; + let chart_type = sniffer.traffic_chart.chart_type; let font = style.get_extension().font; let mut ret_val = Column::new() .width(830) @@ -394,7 +382,7 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> bytes_notification_log(byte_threshold_exceeded.clone(), language, font) } LoggedNotification::FavoriteTransmitted(favorite_transmitted) => { - favorite_notification_log(favorite_transmitted.clone(), language, font) + favorite_notification_log(favorite_transmitted.clone(), chart_type, language, font) } }); } diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index f76c07ca5..44e161bf0 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -18,6 +18,7 @@ use crate::gui::styles::types::palette_extension::PaletteExtension; use crate::gui::types::message::Message; use crate::networking::types::capture_context::CaptureSource; use crate::networking::types::data_info::DataInfo; +use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::filters::Filters; use crate::networking::types::host::Host; use crate::report::get_report_entries::{get_host_entries, get_service_entries}; @@ -256,50 +257,21 @@ fn col_host<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { .unwrap_or_default(); for (host, data_info_host) in &entries { - let (incoming_bar_len, outgoing_bar_len) = get_bars_length( - chart_type, - &first_entry_data_info, - &data_info_host.data_info, - ); - let star_button = get_star_button(data_info_host.is_favorite, host.clone()); - let host_bar = Column::new() - .spacing(1) - .push( - Row::new() - .push(Text::new(host.domain.clone()).font(font)) - .push( - Text::new(if host.asn.name.is_empty() { - String::new() - } else { - format!(" - {}", host.asn.name) - }) - .font(font), - ) - .push(horizontal_space()) - .push( - Text::new(if chart_type.eq(&ChartType::Packets) { - data_info_host.data_info.tot_packets().to_string() - } else { - ByteMultiple::formatted_string(data_info_host.data_info.tot_bytes()) - }) - .font(font), - ), - ) - .push(get_bars(incoming_bar_len, outgoing_bar_len)); + let host_bar = host_bar( + host, + data_info_host, + chart_type, + first_entry_data_info, + font, + language, + ); let content = Row::new() .align_y(Alignment::Center) .spacing(5) .push(star_button) - .push(get_flag_tooltip( - host.country, - data_info_host, - language, - font, - false, - )) .push(host_bar); scroll_host = scroll_host.push( @@ -426,6 +398,58 @@ fn col_service<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { ) } +pub fn host_bar<'a>( + host: &Host, + data_info_host: &DataInfoHost, + chart_type: ChartType, + first_entry_data_info: DataInfo, + font: Font, + language: Language, +) -> Row<'a, Message, StyleType> { + let (incoming_bar_len, outgoing_bar_len) = get_bars_length( + chart_type, + &first_entry_data_info, + &data_info_host.data_info, + ); + + Row::new() + .align_y(Alignment::Center) + .spacing(5) + .push(get_flag_tooltip( + host.country, + data_info_host, + language, + font, + false, + )) + .push( + Column::new() + .spacing(1) + .push( + Row::new() + .push(Text::new(host.domain.clone()).font(font)) + .push( + Text::new(if host.asn.name.is_empty() { + String::new() + } else { + format!(" - {}", host.asn.name) + }) + .font(font), + ) + .push(horizontal_space()) + .push( + Text::new(if chart_type.eq(&ChartType::Packets) { + data_info_host.data_info.tot_packets().to_string() + } else { + ByteMultiple::formatted_string(data_info_host.data_info.tot_bytes()) + }) + .font(font), + ), + ) + .push(get_bars(incoming_bar_len, outgoing_bar_len)), + ) +} + fn col_info<'a>(sniffer: &Sniffer) -> Container<'a, Message, StyleType> { let ConfigSettings { style, language, .. diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index e40cd4d16..bac830b59 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -1109,6 +1109,15 @@ impl Sniffer { rdns, } = host_msg; + let data_info_host = DataInfoHost { + data_info: other_data, + is_favorite: false, + is_loopback, + is_local, + is_bogon, + traffic_type, + }; + self.info_traffic .hosts .entry(host.clone()) @@ -1119,14 +1128,7 @@ impl Sniffer { data_info_host.is_bogon = is_bogon; data_info_host.traffic_type = traffic_type; }) - .or_insert_with(|| DataInfoHost { - data_info: other_data, - is_favorite: false, - is_loopback, - is_local, - is_bogon, - traffic_type, - }); + .or_insert(data_info_host); self.addresses_resolved .insert(address_to_lookup, (rdns, host.clone())); @@ -1136,7 +1138,9 @@ impl Sniffer { // check if the newly resolved host was featured in the favorites (possible in case of already existing host) if self.favorite_hosts.contains(&host) { - self.info_traffic.favorites_last_interval.insert(host); + self.info_traffic + .favorites_last_interval + .insert((host, data_info_host)); } } diff --git a/src/networking/manage_packets.rs b/src/networking/manage_packets.rs index 685a333be..1b6c76bc3 100644 --- a/src/networking/manage_packets.rs +++ b/src/networking/manage_packets.rs @@ -1,11 +1,9 @@ use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use std::sync::{Arc, Mutex}; use etherparse::{EtherType, LaxPacketHeaders, LinkHeader, NetHeaders, TransportHeader}; use pcap::Address; -use crate::networking::parse_packets::AddressesResolutionState; use crate::networking::types::address_port_pair::AddressPortPair; use crate::networking::types::arp_type::ArpType; use crate::networking::types::bogon::is_bogon; @@ -250,7 +248,6 @@ pub fn get_service( } /// Function to insert the source and destination of a packet into the map containing the analyzed traffic -#[allow(clippy::too_many_arguments)] pub fn modify_or_insert_in_map( info_traffic_msg: &mut InfoTrafficMessage, key: &AddressPortPair, @@ -259,7 +256,6 @@ pub fn modify_or_insert_in_map( icmp_type: IcmpType, arp_type: ArpType, exchanged_bytes: u128, - resolutions_state: &Arc>, ) -> (TrafficDirection, Service) { let mut traffic_direction = TrafficDirection::default(); let mut service = Service::Unknown; @@ -280,16 +276,6 @@ pub fn modify_or_insert_in_map( ); // determine upper layer service service = get_service(key, traffic_direction, my_interface_addresses); - // consider all hosts as potential favorites, then they'll be filtered in InfoTraffic::refresh - if let Some(host) = resolutions_state - .lock() - .unwrap() - .addresses_resolved - .get(&get_address_to_lookup(key, traffic_direction)) - .cloned() - { - info_traffic_msg.potential_favorites.insert(host); - } } let timestamp = info_traffic_msg.last_packet_timestamp; diff --git a/src/networking/parse_packets.rs b/src/networking/parse_packets.rs index 43be9c4c6..963240b1f 100644 --- a/src/networking/parse_packets.rs +++ b/src/networking/parse_packets.rs @@ -152,7 +152,6 @@ pub fn parse_packets( icmp_type, arp_type, exchanged_bytes, - &resolutions_state, ); info_traffic_msg.add_packet(exchanged_bytes, traffic_direction); diff --git a/src/networking/types/data_info.rs b/src/networking/types/data_info.rs index 2b64d7342..598c710c3 100644 --- a/src/networking/types/data_info.rs +++ b/src/networking/types/data_info.rs @@ -8,7 +8,7 @@ use std::time::Instant; /// Amount of exchanged data (packets and bytes) incoming and outgoing, with the timestamp of the latest occurrence // data fields are private to make them only editable via the provided methods: needed to correctly refresh timestamps -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub struct DataInfo { /// Incoming packets incoming_packets: u128, diff --git a/src/networking/types/data_info_host.rs b/src/networking/types/data_info_host.rs index 0603206b6..4b63d8f93 100644 --- a/src/networking/types/data_info_host.rs +++ b/src/networking/types/data_info_host.rs @@ -4,7 +4,7 @@ use crate::networking::types::data_info::DataInfo; use crate::networking::types::traffic_type::TrafficType; /// Host-related information. -#[derive(Clone, Copy, Default, Debug)] +#[derive(Clone, Copy, Default, Debug, Eq, PartialEq, Hash)] pub struct DataInfoHost { /// Incoming and outgoing packets and bytes pub data_info: DataInfo, diff --git a/src/networking/types/info_traffic.rs b/src/networking/types/info_traffic.rs index e371a7b1e..ee738e422 100644 --- a/src/networking/types/info_traffic.rs +++ b/src/networking/types/info_traffic.rs @@ -43,7 +43,7 @@ pub struct InfoTraffic { /// Map of the hosts with their data info pub hosts: HashMap, /// Collection of favorite hosts that exchanged data in the last interval - pub favorites_last_interval: HashSet, + pub favorites_last_interval: HashSet<(Host, DataInfoHost)>, } impl InfoTraffic { @@ -82,18 +82,19 @@ impl InfoTraffic { .or_insert(value); } + self.favorites_last_interval = msg + .hosts + .iter() + .filter(|(h, _)| favorites.contains(h)) + .map(|(h, data)| (h.clone(), *data)) + .collect(); + for (key, value) in msg.hosts { self.hosts .entry(key) .and_modify(|x| x.refresh(&value)) .or_insert(value); } - - self.favorites_last_interval = msg - .potential_favorites - .into_iter() - .filter(|h| favorites.contains(h)) - .collect(); } pub fn get_thumbnail_data(&self, chart_type: ChartType) -> (u128, u128, u128, u128) { @@ -141,8 +142,6 @@ pub struct InfoTrafficMessage { pub services: HashMap, /// Map of the hosts with their data info pub hosts: HashMap, - /// Collection of potentially favorite hosts that exchanged data in the last interval - pub potential_favorites: HashSet, } impl InfoTrafficMessage { diff --git a/src/networking/types/traffic_type.rs b/src/networking/types/traffic_type.rs index 70dadc358..7fa53b5b7 100644 --- a/src/networking/types/traffic_type.rs +++ b/src/networking/types/traffic_type.rs @@ -1,5 +1,5 @@ /// Enum representing the possible traffic type (unicast, multicast or broadcast). -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum TrafficType { /// Unicast traffic Unicast, diff --git a/src/notifications/notify_and_log.rs b/src/notifications/notify_and_log.rs index d08cae34b..3e4f3a50f 100644 --- a/src/notifications/notify_and_log.rs +++ b/src/notifications/notify_and_log.rs @@ -1,6 +1,5 @@ use crate::InfoTraffic; use crate::networking::types::capture_context::CaptureSource; -use crate::networking::types::data_info_host::DataInfoHost; use crate::notifications::types::logged_notification::{ BytesThresholdExceeded, FavoriteTransmitted, LoggedNotification, PacketsThresholdExceeded, }; @@ -71,17 +70,13 @@ pub fn notify_and_log( if notifications.favorite_notification.notify_on_favorite && !info_traffic.favorites_last_interval.is_empty() { - for host in info_traffic.favorites_last_interval.clone() { + for (host, data_info_host) in info_traffic.favorites_last_interval.clone() { //log this notification emitted_notifications += 1; if logged_notifications.len() >= 30 { logged_notifications.pop_back(); } - let data_info_host = *info_traffic - .hosts - .get(&host) - .unwrap_or(&DataInfoHost::default()); logged_notifications.push_front(LoggedNotification::FavoriteTransmitted( FavoriteTransmitted { host, From d71fe0aec4b6d4cd079798d807f8b65bbd0d7809 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 21 Jun 2025 15:47:00 +0200 Subject: [PATCH 03/19] fix notifications page padding --- src/gui/pages/notifications_page.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index b2ea5e390..06513246e 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -4,7 +4,7 @@ use iced::widget::text::LineHeight; use iced::widget::tooltip::Position; use iced::widget::{Column, Container, Row, Scrollable, Text, Tooltip}; use iced::widget::{Space, button, vertical_space}; -use iced::{Alignment, Font, Length}; +use iced::{Alignment, Font, Length, Padding}; use std::fmt::Write; use crate::chart::types::chart_type::ChartType; @@ -54,7 +54,7 @@ pub fn notifications_page(sniffer: &Sniffer) -> Container { sniffer.unread_notifications, ); - tab_and_body = tab_and_body.push(tabs).push(Space::with_height(15)); + tab_and_body = tab_and_body.push(tabs); if notifications.packets_notification.threshold.is_none() && notifications.bytes_notification.threshold.is_none() @@ -69,6 +69,7 @@ pub fn notifications_page(sniffer: &Sniffer) -> Container { } else { let logged_notifications = logged_notifications(sniffer); let body_row = Row::new() + .padding(Padding::new(10.0).bottom(0)) .width(Length::Fill) .push( Container::new(if sniffer.logged_notifications.len() < 30 { @@ -76,7 +77,6 @@ pub fn notifications_page(sniffer: &Sniffer) -> Container { } else { Text::new(only_last_30_translation(language)).font(font) }) - .padding(10) .width(Length::Fill) .height(Length::Fill) .align_x(Alignment::Center) @@ -369,7 +369,6 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> let font = style.get_extension().font; let mut ret_val = Column::new() .width(830) - .padding(5) .spacing(10) .align_x(Alignment::Center); From 43c308072673dc3eb0b3a5267ee8cdf6326b9ba1 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 21 Jun 2025 15:54:08 +0200 Subject: [PATCH 04/19] minor fix to bytes notification settings text input --- src/gui/pages/settings_notifications_page.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/pages/settings_notifications_page.rs b/src/gui/pages/settings_notifications_page.rs index e7302926a..80b872130 100644 --- a/src/gui/pages/settings_notifications_page.rs +++ b/src/gui/pages/settings_notifications_page.rs @@ -324,7 +324,7 @@ fn input_group_bytes<'a>( .push( TextInput::new( "0", - if curr_threshold_str == "0" { + if curr_threshold_str.starts_with('0') { "" } else { &curr_threshold_str From fe1d527cdd884c8a87a02fd8787b692d8a4f4092 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 21 Jun 2025 16:40:18 +0200 Subject: [PATCH 05/19] notifications page: fill all the available horizontal space --- src/gui/pages/notifications_page.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index 06513246e..6004fef85 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -69,15 +69,15 @@ pub fn notifications_page(sniffer: &Sniffer) -> Container { } else { let logged_notifications = logged_notifications(sniffer); let body_row = Row::new() + .spacing(10) .padding(Padding::new(10.0).bottom(0)) - .width(Length::Fill) .push( Container::new(if sniffer.logged_notifications.len() < 30 { Text::new("") } else { Text::new(only_last_30_translation(language)).font(font) }) - .width(Length::Fill) + .width(150) .height(Length::Fill) .align_x(Alignment::Center) .align_y(Alignment::Center), @@ -88,7 +88,7 @@ pub fn notifications_page(sniffer: &Sniffer) -> Container { )) .push( Container::new(get_button_clear_all(font, language)) - .width(Length::Fill) + .width(150) .height(Length::Fill) .align_x(Alignment::Center) .align_y(Alignment::Center), @@ -205,7 +205,7 @@ fn packets_notification_log<'a>( ); Container::new(content) .height(120) - .width(800) + .width(Length::Fill) .padding(10) .class(ContainerType::BorderedRound) } @@ -283,7 +283,7 @@ fn bytes_notification_log<'a>( ); Container::new(content) .height(120) - .width(800) + .width(Length::Fill) .padding(10) .class(ContainerType::BorderedRound) } @@ -330,11 +330,11 @@ fn favorite_notification_log<'a>( .font(font), ), ) - .push(Column::new().spacing(7).width(Length::Fill).push(host_bar)); + .push(Column::new().spacing(7).push(host_bar)); Container::new(content) .height(120) - .width(800) + .width(Length::Fill) .padding(10) .class(ContainerType::BorderedRound) } @@ -368,7 +368,7 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> let chart_type = sniffer.traffic_chart.chart_type; let font = style.get_extension().font; let mut ret_val = Column::new() - .width(830) + .padding(Padding::ZERO.right(15)) .spacing(10) .align_x(Alignment::Center); From ad5a7d64f32fd04f075068ea5438370bc3b184aa Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 21 Jun 2025 17:02:14 +0200 Subject: [PATCH 06/19] improve HostMessage ergonomics --- src/gui/sniffer.rs | 24 +++--------------------- src/networking/parse_packets.rs | 13 +++++++++---- src/networking/types/host.rs | 9 ++------- 3 files changed, 14 insertions(+), 32 deletions(-) diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index bac830b59..4ef77810a 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -46,7 +46,6 @@ use crate::mmdb::types::mmdb_reader::{MmdbReader, MmdbReaders}; use crate::networking::parse_packets::BackendTrafficMessage; use crate::networking::parse_packets::parse_packets; use crate::networking::types::capture_context::{CaptureContext, CaptureSource, MyPcapImport}; -use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::filters::Filters; use crate::networking::types::host::{Host, HostMessage}; use crate::networking::types::host_data_states::HostDataStates; @@ -1100,33 +1099,16 @@ impl Sniffer { fn handle_new_host(&mut self, host_msg: HostMessage) { let HostMessage { host, - other_data, - is_loopback, - is_local, - is_bogon, - traffic_type, + data_info_host, address_to_lookup, rdns, } = host_msg; - let data_info_host = DataInfoHost { - data_info: other_data, - is_favorite: false, - is_loopback, - is_local, - is_bogon, - traffic_type, - }; - self.info_traffic .hosts .entry(host.clone()) - .and_modify(|data_info_host| { - data_info_host.data_info.refresh(other_data); - data_info_host.is_loopback = is_loopback; - data_info_host.is_local = is_local; - data_info_host.is_bogon = is_bogon; - data_info_host.traffic_type = traffic_type; + .and_modify(|d| { + d.refresh(&data_info_host); }) .or_insert(data_info_host); diff --git a/src/networking/parse_packets.rs b/src/networking/parse_packets.rs index 963240b1f..ea4c664c3 100644 --- a/src/networking/parse_packets.rs +++ b/src/networking/parse_packets.rs @@ -388,13 +388,18 @@ fn reverse_dns_lookup( .insert(address_to_lookup, new_host.clone()); drop(resolutions_lock); - let msg_data = HostMessage { - host: new_host, - other_data, - is_loopback, + let data_info_host = DataInfoHost { + data_info: other_data, + is_favorite: false, is_local, is_bogon, + is_loopback, traffic_type, + }; + + let msg_data = HostMessage { + host: new_host, + data_info_host, address_to_lookup, rdns, }; diff --git a/src/networking/types/host.rs b/src/networking/types/host.rs index 6684e8aeb..370d41e26 100644 --- a/src/networking/types/host.rs +++ b/src/networking/types/host.rs @@ -1,7 +1,6 @@ use crate::countries::types::country::Country; use crate::networking::types::asn::Asn; -use crate::networking::types::data_info::DataInfo; -use crate::networking::types::traffic_type::TrafficType; +use crate::networking::types::data_info_host::DataInfoHost; use std::net::IpAddr; /// Struct to represent a network host @@ -30,11 +29,7 @@ pub struct ThumbnailHost { #[derive(Clone, Debug)] pub struct HostMessage { pub host: Host, - pub other_data: DataInfo, - pub is_loopback: bool, - pub is_local: bool, - pub is_bogon: Option<&'static str>, - pub traffic_type: TrafficType, + pub data_info_host: DataInfoHost, pub address_to_lookup: IpAddr, pub rdns: String, } From bc6c5076395afe8d836ca8aa99926e2274fd1b17 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 22 Jun 2025 00:13:50 +0200 Subject: [PATCH 07/19] simplify InfoTraffic struct: use DataInfo --- src/chart/manage_chart_data.rs | 68 +++++++++++++++------------ src/gui/pages/overview_page.rs | 2 +- src/gui/pages/thumbnail_page.rs | 2 +- src/gui/sniffer.rs | 15 ++++-- src/networking/parse_packets.rs | 4 +- src/networking/types/info_traffic.rs | 70 ++++++++-------------------- src/notifications/notify_and_log.rs | 12 +++-- 7 files changed, 81 insertions(+), 92 deletions(-) diff --git a/src/chart/manage_chart_data.rs b/src/chart/manage_chart_data.rs index 391091bf6..a79bb2294 100644 --- a/src/chart/manage_chart_data.rs +++ b/src/chart/manage_chart_data.rs @@ -16,16 +16,20 @@ impl TrafficChart { self.ticks += 1; #[allow(clippy::cast_precision_loss)] - let out_bytes_entry = - -1.0 * (info_traffic.tot_out_bytes - info_traffic.tot_out_bytes_prev) as f32; + let out_bytes_entry = -1.0 + * (info_traffic.tot_data_info.outgoing_bytes() + - info_traffic.tot_data_info_prev.outgoing_bytes()) as f32; #[allow(clippy::cast_precision_loss)] - let in_bytes_entry = (info_traffic.tot_in_bytes - info_traffic.tot_in_bytes_prev) as f32; + let in_bytes_entry = (info_traffic.tot_data_info.incoming_bytes() + - info_traffic.tot_data_info_prev.incoming_bytes()) as f32; #[allow(clippy::cast_precision_loss)] - let out_packets_entry = - -1.0 * (info_traffic.tot_out_packets - info_traffic.tot_out_packets_prev) as f32; + let out_packets_entry = -1.0 + * (info_traffic.tot_data_info.outgoing_packets() + - info_traffic.tot_data_info_prev.outgoing_packets()) as f32; #[allow(clippy::cast_precision_loss)] - let in_packets_entry = - (info_traffic.tot_in_packets - info_traffic.tot_in_packets_prev) as f32; + let in_packets_entry = (info_traffic.tot_data_info.incoming_packets() + - info_traffic.tot_data_info_prev.incoming_packets()) + as f32; let out_bytes_point = (tot_seconds, out_bytes_entry); let in_bytes_point = (tot_seconds, in_bytes_entry); @@ -163,6 +167,8 @@ mod tests { use splines::{Interpolation, Key, Spline}; use crate::chart::manage_chart_data::{ChartSeries, get_max, get_min}; + use crate::networking::types::data_info::DataInfo; + use crate::networking::types::traffic_direction::TrafficDirection; use crate::utils::types::timestamp::Timestamp; use crate::{ChartType, InfoTraffic, Language, StyleType, TrafficChart}; @@ -250,6 +256,14 @@ mod tests { }; let tot_sent = 1000 * 28 + 500; let tot_received = 21000 * 28 + 1000; + let tot_data_info_prev = + DataInfo::new_for_tests(tot_received, tot_sent, tot_received, tot_sent); + let tot_data_info = DataInfo::new_for_tests( + tot_received + 4444, + tot_sent + 3333, + tot_received + 2222, + tot_sent + 1111, + ); let mut traffic_chart = TrafficChart { ticks: 29, out_bytes: sent.clone(), @@ -271,15 +285,9 @@ mod tests { let mut info_traffic = InfoTraffic { all_bytes: 0, all_packets: 0, - tot_out_bytes: tot_sent + 1111, - tot_in_bytes: tot_received + 2222, - tot_out_packets: tot_sent + 3333, - tot_in_packets: tot_received + 4444, + tot_data_info, dropped_packets: 0, - tot_out_bytes_prev: tot_sent, - tot_in_bytes_prev: tot_received, - tot_out_packets_prev: tot_sent, - tot_in_packets_prev: tot_received, + tot_data_info_prev, ..Default::default() }; @@ -292,10 +300,7 @@ mod tests { assert_eq!(get_max(&traffic_chart.in_bytes), 21000.0); // prev values aren't updated here anymore: manually set them - info_traffic.tot_out_bytes_prev = info_traffic.tot_out_bytes; - info_traffic.tot_in_bytes_prev = info_traffic.tot_in_bytes; - info_traffic.tot_out_packets_prev = info_traffic.tot_out_packets; - info_traffic.tot_in_packets_prev = info_traffic.tot_in_packets; + info_traffic.tot_data_info_prev = info_traffic.tot_data_info; let mut sent_bytes = sent.clone(); sent_bytes @@ -337,17 +342,20 @@ mod tests { received_bytes.spline.keys() ); - info_traffic.tot_out_bytes += 99; - info_traffic.tot_in_packets += 990; - info_traffic.tot_in_bytes += 2; + info_traffic + .tot_data_info + .add_packets(990, 2, TrafficDirection::Incoming); + info_traffic + .tot_data_info + .add_packet(99, TrafficDirection::Outgoing); traffic_chart.update_charts_data(&info_traffic, false); - info_traffic.tot_out_bytes_prev = info_traffic.tot_out_bytes; - info_traffic.tot_in_bytes_prev = info_traffic.tot_in_bytes; - info_traffic.tot_out_packets_prev = info_traffic.tot_out_packets; - info_traffic.tot_in_packets_prev = info_traffic.tot_in_packets; - info_traffic.tot_out_bytes += 77; - info_traffic.tot_in_packets += 1; - info_traffic.tot_out_packets += 220; + info_traffic.tot_data_info_prev = info_traffic.tot_data_info; + info_traffic + .tot_data_info + .add_packet(0, TrafficDirection::Incoming); + info_traffic + .tot_data_info + .add_packets(220, 77, TrafficDirection::Outgoing); traffic_chart.update_charts_data(&info_traffic, false); sent_bytes.spline.remove(0); @@ -370,7 +378,7 @@ mod tests { sent_packets.spline.remove(0); sent_packets .spline - .add(Key::new(30.0, 0.0, Interpolation::Cosine)); + .add(Key::new(30.0, -1.0, Interpolation::Cosine)); sent_packets .spline .add(Key::new(31.0, -220.0, Interpolation::Cosine)); diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 44e161bf0..79a7749e9 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -69,7 +69,7 @@ pub fn overview_page(sniffer: &Sniffer) -> Container { } else { // NO pcap error detected let observed = sniffer.info_traffic.all_packets; - let filtered = sniffer.info_traffic.tot_out_packets + sniffer.info_traffic.tot_in_packets; + let filtered = sniffer.info_traffic.tot_data_info.tot_packets(); match (observed, filtered) { (0, 0) => { diff --git a/src/gui/pages/thumbnail_page.rs b/src/gui/pages/thumbnail_page.rs index 7c308d106..86a3ae61b 100644 --- a/src/gui/pages/thumbnail_page.rs +++ b/src/gui/pages/thumbnail_page.rs @@ -27,7 +27,7 @@ pub fn thumbnail_page(sniffer: &Sniffer) -> Container { let ConfigSettings { style, .. } = sniffer.configs.settings; let font = style.get_extension().font; - let filtered = sniffer.info_traffic.tot_out_packets + sniffer.info_traffic.tot_in_packets; + let filtered = sniffer.info_traffic.tot_data_info.tot_packets(); if filtered == 0 { return Container::new( diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 4ef77810a..0b4467d5b 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -690,7 +690,7 @@ impl Sniffer { self.info_traffic.refresh(msg, &self.favorite_hosts); self.update_thresholds(); let info_traffic = &self.info_traffic; - if info_traffic.tot_in_packets + info_traffic.tot_out_packets == 0 { + if info_traffic.tot_data_info.tot_packets() == 0 { return; } let emitted_notifications = notify_and_log( @@ -988,7 +988,7 @@ impl Sniffer { true, ) => { // Running with no overlays - if self.info_traffic.tot_out_packets + self.info_traffic.tot_in_packets > 0 { + if self.info_traffic.tot_data_info.tot_packets() > 0 { // Running with no overlays and some packets filtered self.running_page = if next { self.running_page.next() @@ -1160,6 +1160,7 @@ mod tests { use crate::gui::types::timing_events::TimingEvents; use crate::networking::types::host::Host; use crate::networking::types::info_traffic::InfoTrafficMessage; + use crate::networking::types::traffic_direction::TrafficDirection; use crate::notifications::types::logged_notification::{ LoggedNotification, PacketsThresholdExceeded, }; @@ -1696,7 +1697,10 @@ mod tests { )); // Thresholds adjustments won't be updated if `info_traffic.tot_in_packets` // and `info_traffic.tot_out_packets` are both `0`. - sniffer.info_traffic.tot_in_packets = 1; + sniffer + .info_traffic + .tot_data_info + .add_packet(0, TrafficDirection::Outgoing); // Simulate a tick to apply the settings sniffer.update(Message::TickRun( @@ -1974,7 +1978,10 @@ mod tests { assert_eq!(sniffer.running_page, RunningPage::Overview); assert_eq!(sniffer.settings_page, None); // switch with closed setting and some packets received => change running page - sniffer.info_traffic.tot_in_packets += 1; + sniffer + .info_traffic + .tot_data_info + .add_packet(0, TrafficDirection::Outgoing); sniffer.update(Message::SwitchPage(true)); assert_eq!(sniffer.running_page, RunningPage::Inspect); assert_eq!(sniffer.settings_page, None); diff --git a/src/networking/parse_packets.rs b/src/networking/parse_packets.rs index ea4c664c3..198434d8c 100644 --- a/src/networking/parse_packets.rs +++ b/src/networking/parse_packets.rs @@ -154,7 +154,9 @@ pub fn parse_packets( exchanged_bytes, ); - info_traffic_msg.add_packet(exchanged_bytes, traffic_direction); + info_traffic_msg + .tot_data_info + .add_packet(exchanged_bytes, traffic_direction); // check the rDNS status of this address and act accordingly let address_to_lookup = get_address_to_lookup(&key, traffic_direction); diff --git a/src/networking/types/info_traffic.rs b/src/networking/types/info_traffic.rs index ee738e422..ca2cae4db 100644 --- a/src/networking/types/info_traffic.rs +++ b/src/networking/types/info_traffic.rs @@ -5,21 +5,14 @@ use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::host::Host; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; -use crate::networking::types::traffic_direction::TrafficDirection; use crate::utils::types::timestamp::Timestamp; use std::collections::{HashMap, HashSet}; /// Struct containing overall traffic statistics and data. #[derive(Debug, Default)] pub struct InfoTraffic { - /// Total amount of filtered bytes received. - pub tot_in_bytes: u128, - /// Total amount of filtered bytes sent. - pub tot_out_bytes: u128, - /// Total amount of filtered packets received. - pub tot_in_packets: u128, - /// Total amount of filtered packets sent. - pub tot_out_packets: u128, + /// Total amount of exchanged data + pub tot_data_info: DataInfo, /// Total packets including those not filtered pub all_packets: u128, /// Total bytes including those not filtered @@ -28,35 +21,24 @@ pub struct InfoTraffic { pub dropped_packets: u32, /// Timestamp of the latest parsed packet pub last_packet_timestamp: Timestamp, - /// Total sent bytes filtered before the current time interval - pub tot_out_bytes_prev: u128, - /// Total received bytes filtered before the current time interval - pub tot_in_bytes_prev: u128, - /// Total sent packets filtered before the current time interval - pub tot_out_packets_prev: u128, - /// Total received packets filtered before the current time interval - pub tot_in_packets_prev: u128, /// Map of the filtered traffic pub map: HashMap, /// Map of the upper layer services with their data info pub services: HashMap, /// Map of the hosts with their data info pub hosts: HashMap, + /// Total amount of exchanged data before the current time interval + pub tot_data_info_prev: DataInfo, /// Collection of favorite hosts that exchanged data in the last interval pub favorites_last_interval: HashSet<(Host, DataInfoHost)>, } impl InfoTraffic { pub fn refresh(&mut self, msg: InfoTrafficMessage, favorites: &HashSet) { - self.tot_out_bytes_prev = self.tot_out_bytes; - self.tot_in_bytes_prev = self.tot_in_bytes; - self.tot_out_packets_prev = self.tot_out_packets; - self.tot_in_packets_prev = self.tot_in_packets; + self.tot_data_info_prev = self.tot_data_info; + + self.tot_data_info.refresh(msg.tot_data_info); - self.tot_in_bytes += msg.tot_in_bytes; - self.tot_out_bytes += msg.tot_out_bytes; - self.tot_in_packets += msg.tot_in_packets; - self.tot_out_packets += msg.tot_out_packets; self.all_packets += msg.all_packets; self.all_bytes += msg.all_bytes; self.dropped_packets = msg.dropped_packets; @@ -100,17 +82,21 @@ impl InfoTraffic { pub fn get_thumbnail_data(&self, chart_type: ChartType) -> (u128, u128, u128, u128) { if chart_type.eq(&ChartType::Bytes) { ( - self.tot_in_bytes, - self.tot_out_bytes, - self.all_bytes - self.tot_out_bytes - self.tot_in_bytes, + self.tot_data_info.incoming_bytes(), + self.tot_data_info.outgoing_bytes(), + self.all_bytes + - self.tot_data_info.outgoing_bytes() + - self.tot_data_info.incoming_bytes(), // assume that the dropped packets have the same size as the average packet u128::from(self.dropped_packets) * self.all_bytes / self.all_packets, ) } else { ( - self.tot_in_packets, - self.tot_out_packets, - self.all_packets - self.tot_out_packets - self.tot_in_packets, + self.tot_data_info.incoming_packets(), + self.tot_data_info.outgoing_packets(), + self.all_packets + - self.tot_data_info.outgoing_packets() + - self.tot_data_info.incoming_packets(), u128::from(self.dropped_packets), ) } @@ -120,14 +106,8 @@ impl InfoTraffic { /// Struct containing traffic statistics and data related to the last time interval. #[derive(Debug, Clone, Default)] pub struct InfoTrafficMessage { - /// Total amount of filtered bytes received. - pub tot_in_bytes: u128, - /// Total amount of filtered bytes sent. - pub tot_out_bytes: u128, - /// Total amount of filtered packets received. - pub tot_in_packets: u128, - /// Total amount of filtered packets sent. - pub tot_out_packets: u128, + /// Total amount of exchanged data + pub tot_data_info: DataInfo, /// Total packets including those not filtered pub all_packets: u128, /// Total bytes including those not filtered @@ -145,18 +125,6 @@ pub struct InfoTrafficMessage { } impl InfoTrafficMessage { - pub fn add_packet(&mut self, bytes: u128, traffic_direction: TrafficDirection) { - if traffic_direction == TrafficDirection::Outgoing { - //increment number of sent packets and bytes - self.tot_out_packets += 1; - self.tot_out_bytes += bytes; - } else { - //increment number of received packets and bytes - self.tot_in_packets += 1; - self.tot_in_bytes += bytes; - } - } - pub fn take_but_leave_something(&mut self) -> Self { let info_traffic = Self { last_packet_timestamp: self.last_packet_timestamp, diff --git a/src/notifications/notify_and_log.rs b/src/notifications/notify_and_log.rs index 3e4f3a50f..6d046f2eb 100644 --- a/src/notifications/notify_and_log.rs +++ b/src/notifications/notify_and_log.rs @@ -22,8 +22,10 @@ pub fn notify_and_log( let timestamp = info_traffic.last_packet_timestamp; // packets threshold if let Some(threshold) = notifications.packets_notification.threshold { - let sent_packets_entry = info_traffic.tot_out_packets - info_traffic.tot_out_packets_prev; - let received_packets_entry = info_traffic.tot_in_packets - info_traffic.tot_in_packets_prev; + let sent_packets_entry = info_traffic.tot_data_info.outgoing_packets() + - info_traffic.tot_data_info_prev.outgoing_packets(); + let received_packets_entry = info_traffic.tot_data_info.incoming_packets() + - info_traffic.tot_data_info_prev.incoming_packets(); if received_packets_entry + sent_packets_entry > u128::from(threshold) { // log this notification emitted_notifications += 1; @@ -45,8 +47,10 @@ pub fn notify_and_log( } // bytes threshold if let Some(threshold) = notifications.bytes_notification.threshold { - let sent_bytes_entry = info_traffic.tot_out_bytes - info_traffic.tot_out_bytes_prev; - let received_bytes_entry = info_traffic.tot_in_bytes - info_traffic.tot_in_bytes_prev; + let sent_bytes_entry = info_traffic.tot_data_info.outgoing_bytes() + - info_traffic.tot_data_info_prev.outgoing_bytes(); + let received_bytes_entry = info_traffic.tot_data_info.incoming_bytes() + - info_traffic.tot_data_info_prev.incoming_bytes(); if received_bytes_entry + sent_bytes_entry > u128::from(threshold) { //log this notification emitted_notifications += 1; From 7233cc9c04ee6182ecca6e0caf3a22d67b963753 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 22 Jun 2025 01:32:41 +0200 Subject: [PATCH 08/19] data threshold notifications: represent amounts with bars --- src/gui/pages/notifications_page.rs | 123 +++++++------- src/gui/sniffer.rs | 8 +- src/networking/types/data_info.rs | 7 + src/notifications/notify_and_log.rs | 26 ++- .../types/logged_notification.rs | 18 +-- src/notifications/types/notifications.rs | 4 +- src/translations/translations.rs | 152 +++++++++--------- 7 files changed, 167 insertions(+), 171 deletions(-) diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index 6004fef85..fa18c222c 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -2,30 +2,32 @@ use iced::Length::FillPortion; use iced::widget::scrollable::Direction; use iced::widget::text::LineHeight; use iced::widget::tooltip::Position; -use iced::widget::{Column, Container, Row, Scrollable, Text, Tooltip}; +use iced::widget::{Column, Container, Row, Scrollable, Text, Tooltip, horizontal_space}; use iced::widget::{Space, button, vertical_space}; use iced::{Alignment, Font, Length, Padding}; use std::fmt::Write; use crate::chart::types::chart_type::ChartType; +use crate::countries::country_utils::get_computer_tooltip; use crate::gui::components::header::get_button_settings; use crate::gui::components::tab::get_pages_tabs; use crate::gui::components::types::my_modal::MyModal; -use crate::gui::pages::overview_page::host_bar; +use crate::gui::pages::overview_page::{get_bars, get_bars_length, host_bar}; use crate::gui::pages::types::settings_page::SettingsPage; use crate::gui::styles::container::ContainerType; use crate::gui::styles::scrollbar::ScrollbarType; use crate::gui::styles::style_constants::FONT_SIZE_FOOTER; use crate::gui::styles::text::TextType; use crate::gui::types::message::Message; +use crate::networking::types::data_info::DataInfo; +use crate::networking::types::traffic_type::TrafficType; use crate::notifications::types::logged_notification::{ - BytesThresholdExceeded, FavoriteTransmitted, LoggedNotification, PacketsThresholdExceeded, + DataThresholdExceeded, FavoriteTransmitted, LoggedNotification, }; use crate::translations::translations::{ - bytes_exceeded_translation, bytes_exceeded_value_translation, clear_all_translation, - favorite_transmitted_translation, incoming_translation, no_notifications_received_translation, - no_notifications_set_translation, only_last_30_translation, outgoing_translation, - packets_exceeded_translation, packets_exceeded_value_translation, per_second_translation, + bytes_exceeded_translation, clear_all_translation, favorite_transmitted_translation, + no_notifications_received_translation, no_notifications_set_translation, + only_last_30_translation, packets_exceeded_translation, per_second_translation, threshold_translation, }; use crate::utils::types::icon::Icon; @@ -140,7 +142,7 @@ fn body_no_notifications_received( } fn packets_notification_log<'a>( - logged_notification: PacketsThresholdExceeded, + logged_notification: DataThresholdExceeded, language: Language, font: Font, ) -> Container<'a, Message, StyleType> { @@ -150,14 +152,6 @@ fn packets_notification_log<'a>( logged_notification.threshold, per_second_translation(language) ); - let mut incoming_str = " - ".to_string(); - incoming_str.push_str(incoming_translation(language)); - incoming_str.push_str(": "); - incoming_str.push_str(&logged_notification.incoming.to_string()); - let mut outgoing_str = " - ".to_string(); - outgoing_str.push_str(outgoing_translation(language)); - outgoing_str.push_str(": "); - outgoing_str.push_str(&logged_notification.outgoing.to_string()); let content = Row::new() .align_y(Alignment::Center) .height(Length::Fill) @@ -190,19 +184,13 @@ fn packets_notification_log<'a>( .font(font), ), ) - .push( - Column::new() - .spacing(7) - .push( - Text::new(packets_exceeded_value_translation( - language, - logged_notification.incoming + logged_notification.outgoing, - )) - .font(font), - ) - .push(Text::new(incoming_str).font(font)) - .push(Text::new(outgoing_str).font(font)), - ); + .push(threshold_bar( + logged_notification.data_info, + ChartType::Packets, + logged_notification.data_info, + font, + language, + )); Container::new(content) .height(120) .width(Length::Fill) @@ -211,7 +199,7 @@ fn packets_notification_log<'a>( } fn bytes_notification_log<'a>( - logged_notification: BytesThresholdExceeded, + logged_notification: DataThresholdExceeded, language: Language, font: Font, ) -> Container<'a, Message, StyleType> { @@ -220,20 +208,7 @@ fn bytes_notification_log<'a>( threshold_str.push_str(&ByteMultiple::formatted_string( (logged_notification.threshold).into(), )); - let _ = write!(threshold_str, " {}", per_second_translation(language)); - let mut incoming_str = " - ".to_string(); - incoming_str.push_str(incoming_translation(language)); - incoming_str.push_str(": "); - incoming_str.push_str(&ByteMultiple::formatted_string(u128::from( - logged_notification.incoming, - ))); - let mut outgoing_str = " - ".to_string(); - outgoing_str.push_str(outgoing_translation(language)); - outgoing_str.push_str(": "); - outgoing_str.push_str(&ByteMultiple::formatted_string(u128::from( - logged_notification.outgoing, - ))); let content = Row::new() .spacing(30) .align_y(Alignment::Center) @@ -266,21 +241,13 @@ fn bytes_notification_log<'a>( .font(font), ), ) - .push( - Column::new() - .spacing(7) - .push( - Text::new(bytes_exceeded_value_translation( - language, - &ByteMultiple::formatted_string(u128::from( - logged_notification.incoming + logged_notification.outgoing, - )), - )) - .font(font), - ) - .push(Text::new(incoming_str).font(font)) - .push(Text::new(outgoing_str).font(font)), - ); + .push(threshold_bar( + logged_notification.data_info, + ChartType::Bytes, + logged_notification.data_info, + font, + language, + )); Container::new(content) .height(120) .width(Length::Fill) @@ -330,7 +297,7 @@ fn favorite_notification_log<'a>( .font(font), ), ) - .push(Column::new().spacing(7).push(host_bar)); + .push(host_bar); Container::new(content) .height(120) @@ -387,3 +354,41 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> } ret_val } + +fn threshold_bar<'a>( + data_info: DataInfo, + chart_type: ChartType, + first_entry_data_info: DataInfo, + font: Font, + language: Language, +) -> Row<'a, Message, StyleType> { + let (incoming_bar_len, outgoing_bar_len) = + get_bars_length(chart_type, &first_entry_data_info, &data_info); + + Row::new() + .align_y(Alignment::Center) + .spacing(5) + .push(get_computer_tooltip( + true, + true, + None, + TrafficType::Unicast, + language, + font, + )) + .push( + Column::new() + .spacing(1) + .push( + Row::new().push(horizontal_space()).push( + Text::new(if chart_type.eq(&ChartType::Packets) { + data_info.tot_packets().to_string() + } else { + ByteMultiple::formatted_string(data_info.tot_bytes()) + }) + .font(font), + ), + ) + .push(get_bars(incoming_bar_len, outgoing_bar_len)), + ) +} diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 0b4467d5b..4040f4094 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -1158,11 +1158,12 @@ mod tests { use crate::gui::styles::types::gradient_type::GradientType; use crate::gui::types::message::Message; use crate::gui::types::timing_events::TimingEvents; + use crate::networking::types::data_info::DataInfo; use crate::networking::types::host::Host; use crate::networking::types::info_traffic::InfoTrafficMessage; use crate::networking::types::traffic_direction::TrafficDirection; use crate::notifications::types::logged_notification::{ - LoggedNotification, PacketsThresholdExceeded, + DataThresholdExceeded, LoggedNotification, }; use crate::notifications::types::notifications::{ BytesNotification, FavoriteNotification, Notification, Notifications, PacketsNotification, @@ -1922,10 +1923,9 @@ mod tests { let mut sniffer = Sniffer::new(Configs::default()); sniffer.logged_notifications = VecDeque::from([LoggedNotification::PacketsThresholdExceeded( - PacketsThresholdExceeded { + DataThresholdExceeded { threshold: 0, - incoming: 0, - outgoing: 0, + data_info: DataInfo::default(), timestamp: "".to_string(), }, )]); diff --git a/src/networking/types/data_info.rs b/src/networking/types/data_info.rs index 598c710c3..ab29b7d7a 100644 --- a/src/networking/types/data_info.rs +++ b/src/networking/types/data_info.rs @@ -96,6 +96,13 @@ impl DataInfo { self.final_instant = rhs.final_instant; } + pub fn subtract(&mut self, rhs: Self) { + self.incoming_packets -= rhs.incoming_packets; + self.outgoing_packets -= rhs.outgoing_packets; + self.incoming_bytes -= rhs.incoming_bytes; + self.outgoing_bytes -= rhs.outgoing_bytes; + } + pub fn compare(&self, other: &Self, sort_type: SortType, chart_type: ChartType) -> Ordering { match chart_type { ChartType::Packets => match sort_type { diff --git a/src/notifications/notify_and_log.rs b/src/notifications/notify_and_log.rs index 6d046f2eb..fabd84958 100644 --- a/src/notifications/notify_and_log.rs +++ b/src/notifications/notify_and_log.rs @@ -1,7 +1,7 @@ use crate::InfoTraffic; use crate::networking::types::capture_context::CaptureSource; use crate::notifications::types::logged_notification::{ - BytesThresholdExceeded, FavoriteTransmitted, LoggedNotification, PacketsThresholdExceeded, + DataThresholdExceeded, FavoriteTransmitted, LoggedNotification, }; use crate::notifications::types::notifications::Notifications; use crate::notifications::types::sound::{Sound, play}; @@ -20,23 +20,20 @@ pub fn notify_and_log( let mut sound_to_play = Sound::None; let mut emitted_notifications = 0; let timestamp = info_traffic.last_packet_timestamp; + let mut data_info_delta = info_traffic.tot_data_info; + data_info_delta.subtract(info_traffic.tot_data_info_prev); // packets threshold if let Some(threshold) = notifications.packets_notification.threshold { - let sent_packets_entry = info_traffic.tot_data_info.outgoing_packets() - - info_traffic.tot_data_info_prev.outgoing_packets(); - let received_packets_entry = info_traffic.tot_data_info.incoming_packets() - - info_traffic.tot_data_info_prev.incoming_packets(); - if received_packets_entry + sent_packets_entry > u128::from(threshold) { + if data_info_delta.tot_packets() > u128::from(threshold) { // log this notification emitted_notifications += 1; if logged_notifications.len() >= 30 { logged_notifications.pop_back(); } logged_notifications.push_front(LoggedNotification::PacketsThresholdExceeded( - PacketsThresholdExceeded { + DataThresholdExceeded { threshold: notifications.packets_notification.previous_threshold, - incoming: received_packets_entry.try_into().unwrap_or_default(), - outgoing: sent_packets_entry.try_into().unwrap_or_default(), + data_info: data_info_delta, timestamp: get_formatted_timestamp(timestamp), }, )); @@ -47,21 +44,16 @@ pub fn notify_and_log( } // bytes threshold if let Some(threshold) = notifications.bytes_notification.threshold { - let sent_bytes_entry = info_traffic.tot_data_info.outgoing_bytes() - - info_traffic.tot_data_info_prev.outgoing_bytes(); - let received_bytes_entry = info_traffic.tot_data_info.incoming_bytes() - - info_traffic.tot_data_info_prev.incoming_bytes(); - if received_bytes_entry + sent_bytes_entry > u128::from(threshold) { + if data_info_delta.tot_bytes() > u128::from(threshold) { //log this notification emitted_notifications += 1; if logged_notifications.len() >= 30 { logged_notifications.pop_back(); } logged_notifications.push_front(LoggedNotification::BytesThresholdExceeded( - BytesThresholdExceeded { + DataThresholdExceeded { threshold: notifications.bytes_notification.previous_threshold, - incoming: received_bytes_entry.try_into().unwrap_or_default(), - outgoing: sent_bytes_entry.try_into().unwrap_or_default(), + data_info: data_info_delta, timestamp: get_formatted_timestamp(timestamp), }, )); diff --git a/src/notifications/types/logged_notification.rs b/src/notifications/types/logged_notification.rs index d3c90dcf5..ef0c67cc8 100644 --- a/src/notifications/types/logged_notification.rs +++ b/src/notifications/types/logged_notification.rs @@ -1,29 +1,21 @@ +use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::host::Host; /// Enum representing the possible notification events. pub enum LoggedNotification { /// Packets threshold exceeded - PacketsThresholdExceeded(PacketsThresholdExceeded), + PacketsThresholdExceeded(DataThresholdExceeded), /// Byte threshold exceeded - BytesThresholdExceeded(BytesThresholdExceeded), + BytesThresholdExceeded(DataThresholdExceeded), /// Favorite connection exchanged data FavoriteTransmitted(FavoriteTransmitted), } #[derive(Clone)] -pub struct PacketsThresholdExceeded { - pub(crate) threshold: u32, - pub(crate) incoming: u32, - pub(crate) outgoing: u32, - pub(crate) timestamp: String, -} - -#[derive(Clone)] -pub struct BytesThresholdExceeded { +pub struct DataThresholdExceeded { pub(crate) threshold: u64, - pub(crate) incoming: u32, - pub(crate) outgoing: u32, + pub(crate) data_info: DataInfo, pub(crate) timestamp: String, } diff --git a/src/notifications/types/notifications.rs b/src/notifications/types/notifications.rs index 07ab80ece..01e6718f3 100644 --- a/src/notifications/types/notifications.rs +++ b/src/notifications/types/notifications.rs @@ -37,11 +37,11 @@ pub enum Notification { #[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Copy)] pub struct PacketsNotification { /// Threshold of received + sent packets; if exceeded a notification is emitted - pub threshold: Option, + pub threshold: Option, /// The sound to emit pub sound: Sound, /// The last used Some value for the threshold field - pub previous_threshold: u32, + pub previous_threshold: u64, } impl Default for PacketsNotification { diff --git a/src/translations/translations.rs b/src/translations/translations.rs index 5b6c884b6..48c40ab56 100644 --- a/src/translations/translations.rs +++ b/src/translations/translations.rs @@ -1971,32 +1971,32 @@ pub fn bytes_exceeded_translation(language: Language) -> &'static str { } } -pub fn bytes_exceeded_value_translation(language: Language, value: &str) -> String { - match language { - Language::EN => format!("{value} have been exchanged"), - Language::IT => format!("{value} sono stati scambiati"), - Language::FR => format!("{value} ont été échangé"), - Language::ES => format!("{value} han sido intercambiado/s"), - Language::PL => format!("Wymieniono {value}"), - Language::DE => format!("{value} wurden ausgetauscht"), - Language::UK => format!("{value} було обміняно"), - Language::ZH => format!("已交换字节 {value}"), - Language::ZH_TW => format!("已交換 {value} 位元組"), - Language::RO => format!("au fost transferați {value}"), - Language::KO => format!("바이트 {value} 가 교환되었습니다"), - Language::TR => format!("{value} aktarıldı"), - Language::RU => format!("{value} обмена информацией"), - Language::PT => format!("Foram trocados {value}"), - Language::EL => format!("{value} έχουν ανταλλαγεί"), - // Language::FA => format!("{value} بایت مبادله شده است"), - Language::SV => format!("{value} har utbytts"), - Language::FI => format!("{value} on vaihdettu"), - Language::JA => format!("{value} の送受信が発生しました"), - Language::UZ => format!("{value} ma'lumot almashinuvi"), - Language::VI => format!("{value} đã được trao đổi"), - Language::ID => format!("{value} telah dipertukarkan"), - } -} +// pub fn bytes_exceeded_value_translation(language: Language, value: &str) -> String { +// match language { +// Language::EN => format!("{value} have been exchanged"), +// Language::IT => format!("{value} sono stati scambiati"), +// Language::FR => format!("{value} ont été échangé"), +// Language::ES => format!("{value} han sido intercambiado/s"), +// Language::PL => format!("Wymieniono {value}"), +// Language::DE => format!("{value} wurden ausgetauscht"), +// Language::UK => format!("{value} було обміняно"), +// Language::ZH => format!("已交换字节 {value}"), +// Language::ZH_TW => format!("已交換 {value} 位元組"), +// Language::RO => format!("au fost transferați {value}"), +// Language::KO => format!("바이트 {value} 가 교환되었습니다"), +// Language::TR => format!("{value} aktarıldı"), +// Language::RU => format!("{value} обмена информацией"), +// Language::PT => format!("Foram trocados {value}"), +// Language::EL => format!("{value} έχουν ανταλλαγεί"), +// // Language::FA => format!("{value} بایت مبادله شده است"), +// Language::SV => format!("{value} har utbytts"), +// Language::FI => format!("{value} on vaihdettu"), +// Language::JA => format!("{value} の送受信が発生しました"), +// Language::UZ => format!("{value} ma'lumot almashinuvi"), +// Language::VI => format!("{value} đã được trao đổi"), +// Language::ID => format!("{value} telah dipertukarkan"), +// } +// } pub fn packets_exceeded_translation(language: Language) -> &'static str { match language { @@ -2025,56 +2025,56 @@ pub fn packets_exceeded_translation(language: Language) -> &'static str { } } -pub fn packets_exceeded_value_translation(language: Language, value: u32) -> String { - match language { - Language::EN => match value { - 1 => "1 packet has been exchanged".to_owned(), - npackets => format!("{npackets} packets have been exchanged"), - }, - Language::IT => match value { - 1 => "1 pacchetto è stato scambiato".to_owned(), - npackets => format!("{npackets} pacchetti sono stati scambiati"), - }, - Language::FR => match value { - 1 => "1 paquet a été échangé".to_owned(), - npackets => format!("{npackets} paquets ont été échangés"), - }, - Language::ES => format!("{value} paquete/s han sido intercambiado/s"), - Language::PL => format!("Wymieniono {value} pakietów"), - Language::DE => match value { - 1 => "1 Paket wurde ausgetauscht".to_owned(), - npackets => format!("{npackets} Pakete wurden ausgetauscht"), - }, - Language::UK => format!("Обміняно {value} пакетів"), - Language::ZH => format!("已交换数据包 {value}"), - Language::ZH_TW => format!("已交換 {value} 個封包"), - Language::RO => format!("au fost transferate {value} pachete"), - Language::KO => format!("패킷 {value} 가 교환되었습니다"), - Language::TR => format!("{value} paket aktarıldı"), - Language::RU => format!("{value} пакет(ов) обмена информацией"), - Language::PT => match value { - 1 => "Foi trocado 1 pacote".to_owned(), - npackets => format!("Foram trocados {npackets} pacotes"), - }, - Language::EL => match value { - 1 => "1 πακέτο έχει ανταλλαγεί".to_owned(), - npackets => format!("{npackets} πακέτα έχουν ανταλλαγεί"), - }, - // Language::FA => format!("{value} بسته مبادله شده است"), - Language::SV => match value { - 1 => "1 paket har utbytts".to_owned(), - npackets => format!("{npackets} paket har utbytts"), - }, - Language::FI => match value { - 1 => "1 paketti vaihdettu".to_owned(), - npackets => format!("{npackets} pakettia vaihdettu"), - }, - Language::JA => format!("{value} パケットの送受信が発生しました"), - Language::UZ => format!("{value} paket uzatildi"), - Language::VI => format!("{value} gói tin đã được trao đổi"), - Language::ID => format!("{value} paket telah dipertukarkan"), - } -} +// pub fn packets_exceeded_value_translation(language: Language, value: u32) -> String { +// match language { +// Language::EN => match value { +// 1 => "1 packet has been exchanged".to_owned(), +// npackets => format!("{npackets} packets have been exchanged"), +// }, +// Language::IT => match value { +// 1 => "1 pacchetto è stato scambiato".to_owned(), +// npackets => format!("{npackets} pacchetti sono stati scambiati"), +// }, +// Language::FR => match value { +// 1 => "1 paquet a été échangé".to_owned(), +// npackets => format!("{npackets} paquets ont été échangés"), +// }, +// Language::ES => format!("{value} paquete/s han sido intercambiado/s"), +// Language::PL => format!("Wymieniono {value} pakietów"), +// Language::DE => match value { +// 1 => "1 Paket wurde ausgetauscht".to_owned(), +// npackets => format!("{npackets} Pakete wurden ausgetauscht"), +// }, +// Language::UK => format!("Обміняно {value} пакетів"), +// Language::ZH => format!("已交换数据包 {value}"), +// Language::ZH_TW => format!("已交換 {value} 個封包"), +// Language::RO => format!("au fost transferate {value} pachete"), +// Language::KO => format!("패킷 {value} 가 교환되었습니다"), +// Language::TR => format!("{value} paket aktarıldı"), +// Language::RU => format!("{value} пакет(ов) обмена информацией"), +// Language::PT => match value { +// 1 => "Foi trocado 1 pacote".to_owned(), +// npackets => format!("Foram trocados {npackets} pacotes"), +// }, +// Language::EL => match value { +// 1 => "1 πακέτο έχει ανταλλαγεί".to_owned(), +// npackets => format!("{npackets} πακέτα έχουν ανταλλαγεί"), +// }, +// // Language::FA => format!("{value} بسته مبادله شده است"), +// Language::SV => match value { +// 1 => "1 paket har utbytts".to_owned(), +// npackets => format!("{npackets} paket har utbytts"), +// }, +// Language::FI => match value { +// 1 => "1 paketti vaihdettu".to_owned(), +// npackets => format!("{npackets} pakettia vaihdettu"), +// }, +// Language::JA => format!("{value} パケットの送受信が発生しました"), +// Language::UZ => format!("{value} paket uzatildi"), +// Language::VI => format!("{value} gói tin đã được trao đổi"), +// Language::ID => format!("{value} paket telah dipertukarkan"), +// } +// } pub fn favorite_transmitted_translation(language: Language) -> &'static str { match language { From 12b12c6eec318f99a285704f4cd3c31075afdc9e Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 22 Jun 2025 01:54:51 +0200 Subject: [PATCH 09/19] notifications page: use relative bar lengths --- src/gui/pages/notifications_page.rs | 39 ++++++++++++++++--- .../types/logged_notification.rs | 10 +++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index fa18c222c..6d07dd12b 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -24,6 +24,7 @@ use crate::networking::types::traffic_type::TrafficType; use crate::notifications::types::logged_notification::{ DataThresholdExceeded, FavoriteTransmitted, LoggedNotification, }; +use crate::report::types::sort_type::SortType; use crate::translations::translations::{ bytes_exceeded_translation, clear_all_translation, favorite_transmitted_translation, no_notifications_received_translation, no_notifications_set_translation, @@ -143,6 +144,7 @@ fn body_no_notifications_received( fn packets_notification_log<'a>( logged_notification: DataThresholdExceeded, + first_entry_data_info: DataInfo, language: Language, font: Font, ) -> Container<'a, Message, StyleType> { @@ -187,7 +189,7 @@ fn packets_notification_log<'a>( .push(threshold_bar( logged_notification.data_info, ChartType::Packets, - logged_notification.data_info, + first_entry_data_info, font, language, )); @@ -200,6 +202,7 @@ fn packets_notification_log<'a>( fn bytes_notification_log<'a>( logged_notification: DataThresholdExceeded, + first_entry_data_info: DataInfo, language: Language, font: Font, ) -> Container<'a, Message, StyleType> { @@ -244,7 +247,7 @@ fn bytes_notification_log<'a>( .push(threshold_bar( logged_notification.data_info, ChartType::Bytes, - logged_notification.data_info, + first_entry_data_info, font, language, )); @@ -257,6 +260,7 @@ fn bytes_notification_log<'a>( fn favorite_notification_log<'a>( logged_notification: FavoriteTransmitted, + first_entry_data_info: DataInfo, chart_type: ChartType, language: Language, font: Font, @@ -265,7 +269,7 @@ fn favorite_notification_log<'a>( &logged_notification.host, &logged_notification.data_info_host, chart_type, - logged_notification.data_info_host.data_info, + first_entry_data_info, font, language, ); @@ -339,16 +343,39 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> .spacing(10) .align_x(Alignment::Center); + let first_entry_data_info = sniffer + .logged_notifications + .iter() + .map(LoggedNotification::data_info) + .max_by(|d1, d2| d1.compare(d2, SortType::Ascending, chart_type)) + .unwrap_or_default(); + for logged_notification in &sniffer.logged_notifications { ret_val = ret_val.push(match logged_notification { LoggedNotification::PacketsThresholdExceeded(packet_threshold_exceeded) => { - packets_notification_log(packet_threshold_exceeded.clone(), language, font) + packets_notification_log( + packet_threshold_exceeded.clone(), + first_entry_data_info, + language, + font, + ) } LoggedNotification::BytesThresholdExceeded(byte_threshold_exceeded) => { - bytes_notification_log(byte_threshold_exceeded.clone(), language, font) + bytes_notification_log( + byte_threshold_exceeded.clone(), + first_entry_data_info, + language, + font, + ) } LoggedNotification::FavoriteTransmitted(favorite_transmitted) => { - favorite_notification_log(favorite_transmitted.clone(), chart_type, language, font) + favorite_notification_log( + favorite_transmitted.clone(), + first_entry_data_info, + chart_type, + language, + font, + ) } }); } diff --git a/src/notifications/types/logged_notification.rs b/src/notifications/types/logged_notification.rs index ef0c67cc8..3c65c733b 100644 --- a/src/notifications/types/logged_notification.rs +++ b/src/notifications/types/logged_notification.rs @@ -12,6 +12,16 @@ pub enum LoggedNotification { FavoriteTransmitted(FavoriteTransmitted), } +impl LoggedNotification { + pub fn data_info(&self) -> DataInfo { + match self { + LoggedNotification::BytesThresholdExceeded(d) + | LoggedNotification::PacketsThresholdExceeded(d) => d.data_info, + LoggedNotification::FavoriteTransmitted(f) => f.data_info_host.data_info, + } + } +} + #[derive(Clone)] pub struct DataThresholdExceeded { pub(crate) threshold: u64, From 4cbbc8fcd51d1ed4a4fcc6c066b9e4b6d9d70d94 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 22 Jun 2025 11:11:19 +0200 Subject: [PATCH 10/19] refactor: unify packets and bytes notifications --- src/gui/pages/notifications_page.rs | 111 +++++------------- src/notifications/notify_and_log.rs | 7 +- .../types/logged_notification.rs | 11 +- 3 files changed, 38 insertions(+), 91 deletions(-) diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index 6d07dd12b..7432e8ac1 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -5,7 +5,6 @@ use iced::widget::tooltip::Position; use iced::widget::{Column, Container, Row, Scrollable, Text, Tooltip, horizontal_space}; use iced::widget::{Space, button, vertical_space}; use iced::{Alignment, Font, Length, Padding}; -use std::fmt::Write; use crate::chart::types::chart_type::ChartType; use crate::countries::country_utils::get_computer_tooltip; @@ -142,28 +141,36 @@ fn body_no_notifications_received( .push(Space::with_height(FillPortion(2))) } -fn packets_notification_log<'a>( +fn data_notification_log<'a>( logged_notification: DataThresholdExceeded, first_entry_data_info: DataInfo, language: Language, font: Font, ) -> Container<'a, Message, StyleType> { + let chart_type = logged_notification.chart_type; + let data_string = if chart_type == ChartType::Bytes { + ByteMultiple::formatted_string(logged_notification.threshold.into()) + } else { + logged_notification.threshold.to_string() + }; + let icon = if chart_type == ChartType::Bytes { + Icon::BytesThreshold + } else { + Icon::PacketsThreshold + } + .to_text() + .size(80) + .line_height(LineHeight::Relative(1.0)); let threshold_str = format!( - "{}: {} {}", + "{}: {data_string} {}", threshold_translation(language), - logged_notification.threshold, per_second_translation(language) ); let content = Row::new() .align_y(Alignment::Center) .height(Length::Fill) .spacing(30) - .push( - Icon::PacketsThreshold - .to_text() - .size(80) - .line_height(LineHeight::Relative(1.0)), - ) + .push(icon) .push( Column::new() .spacing(7) @@ -175,9 +182,13 @@ fn packets_notification_log<'a>( .push(Text::new(logged_notification.timestamp).font(font)), ) .push( - Text::new(packets_exceeded_translation(language)) - .class(TextType::Title) - .font(font), + Text::new(if chart_type == ChartType::Bytes { + bytes_exceeded_translation(language) + } else { + packets_exceeded_translation(language) + }) + .class(TextType::Title) + .font(font), ) .push( Text::new(threshold_str) @@ -188,65 +199,7 @@ fn packets_notification_log<'a>( ) .push(threshold_bar( logged_notification.data_info, - ChartType::Packets, - first_entry_data_info, - font, - language, - )); - Container::new(content) - .height(120) - .width(Length::Fill) - .padding(10) - .class(ContainerType::BorderedRound) -} - -fn bytes_notification_log<'a>( - logged_notification: DataThresholdExceeded, - first_entry_data_info: DataInfo, - language: Language, - font: Font, -) -> Container<'a, Message, StyleType> { - let mut threshold_str = threshold_translation(language).to_string(); - threshold_str.push_str(": "); - threshold_str.push_str(&ByteMultiple::formatted_string( - (logged_notification.threshold).into(), - )); - let _ = write!(threshold_str, " {}", per_second_translation(language)); - let content = Row::new() - .spacing(30) - .align_y(Alignment::Center) - .height(Length::Fill) - .push( - Icon::BytesThreshold - .to_text() - .size(80) - .line_height(LineHeight::Relative(1.0)), - ) - .push( - Column::new() - .spacing(7) - .width(250) - .push( - Row::new() - .spacing(8) - .push(Icon::Clock.to_text()) - .push(Text::new(logged_notification.timestamp).font(font)), - ) - .push( - Text::new(bytes_exceeded_translation(language)) - .class(TextType::Title) - .font(font), - ) - .push( - Text::new(threshold_str) - .size(FONT_SIZE_FOOTER) - .class(TextType::Subtitle) - .font(font), - ), - ) - .push(threshold_bar( - logged_notification.data_info, - ChartType::Bytes, + chart_type, first_entry_data_info, font, language, @@ -352,17 +305,9 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> for logged_notification in &sniffer.logged_notifications { ret_val = ret_val.push(match logged_notification { - LoggedNotification::PacketsThresholdExceeded(packet_threshold_exceeded) => { - packets_notification_log( - packet_threshold_exceeded.clone(), - first_entry_data_info, - language, - font, - ) - } - LoggedNotification::BytesThresholdExceeded(byte_threshold_exceeded) => { - bytes_notification_log( - byte_threshold_exceeded.clone(), + LoggedNotification::DataThresholdExceeded(data_threshold_exceeded) => { + data_notification_log( + data_threshold_exceeded.clone(), first_entry_data_info, language, font, diff --git a/src/notifications/notify_and_log.rs b/src/notifications/notify_and_log.rs index fabd84958..2c44cc02c 100644 --- a/src/notifications/notify_and_log.rs +++ b/src/notifications/notify_and_log.rs @@ -1,4 +1,5 @@ use crate::InfoTraffic; +use crate::chart::types::chart_type::ChartType; use crate::networking::types::capture_context::CaptureSource; use crate::notifications::types::logged_notification::{ DataThresholdExceeded, FavoriteTransmitted, LoggedNotification, @@ -30,8 +31,9 @@ pub fn notify_and_log( if logged_notifications.len() >= 30 { logged_notifications.pop_back(); } - logged_notifications.push_front(LoggedNotification::PacketsThresholdExceeded( + logged_notifications.push_front(LoggedNotification::DataThresholdExceeded( DataThresholdExceeded { + chart_type: ChartType::Packets, threshold: notifications.packets_notification.previous_threshold, data_info: data_info_delta, timestamp: get_formatted_timestamp(timestamp), @@ -50,8 +52,9 @@ pub fn notify_and_log( if logged_notifications.len() >= 30 { logged_notifications.pop_back(); } - logged_notifications.push_front(LoggedNotification::BytesThresholdExceeded( + logged_notifications.push_front(LoggedNotification::DataThresholdExceeded( DataThresholdExceeded { + chart_type: ChartType::Bytes, threshold: notifications.bytes_notification.previous_threshold, data_info: data_info_delta, timestamp: get_formatted_timestamp(timestamp), diff --git a/src/notifications/types/logged_notification.rs b/src/notifications/types/logged_notification.rs index 3c65c733b..d341ba46a 100644 --- a/src/notifications/types/logged_notification.rs +++ b/src/notifications/types/logged_notification.rs @@ -1,13 +1,12 @@ +use crate::chart::types::chart_type::ChartType; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::host::Host; /// Enum representing the possible notification events. pub enum LoggedNotification { - /// Packets threshold exceeded - PacketsThresholdExceeded(DataThresholdExceeded), - /// Byte threshold exceeded - BytesThresholdExceeded(DataThresholdExceeded), + /// Data threshold exceeded + DataThresholdExceeded(DataThresholdExceeded), /// Favorite connection exchanged data FavoriteTransmitted(FavoriteTransmitted), } @@ -15,8 +14,7 @@ pub enum LoggedNotification { impl LoggedNotification { pub fn data_info(&self) -> DataInfo { match self { - LoggedNotification::BytesThresholdExceeded(d) - | LoggedNotification::PacketsThresholdExceeded(d) => d.data_info, + LoggedNotification::DataThresholdExceeded(d) => d.data_info, LoggedNotification::FavoriteTransmitted(f) => f.data_info_host.data_info, } } @@ -24,6 +22,7 @@ impl LoggedNotification { #[derive(Clone)] pub struct DataThresholdExceeded { + pub(crate) chart_type: ChartType, pub(crate) threshold: u64, pub(crate) data_info: DataInfo, pub(crate) timestamp: String, From 6fccc1c33cbd9bcabfe0b915493d1cf63be007d1 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 22 Jun 2025 12:22:19 +0200 Subject: [PATCH 11/19] refactor: unify InfoTraffic and InfoTrafficMessage structs --- src/chart/manage_chart_data.rs | 50 ++++------------------ src/gui/sniffer.rs | 56 +++++++++--------------- src/gui/types/message.rs | 4 +- src/networking/manage_packets.rs | 4 +- src/networking/parse_packets.rs | 10 ++--- src/networking/types/data_info.rs | 7 --- src/networking/types/info_traffic.rs | 42 ++---------------- src/notifications/notify_and_log.rs | 64 ++++++++++++++++------------ 8 files changed, 78 insertions(+), 159 deletions(-) diff --git a/src/chart/manage_chart_data.rs b/src/chart/manage_chart_data.rs index a79bb2294..5686fb7b5 100644 --- a/src/chart/manage_chart_data.rs +++ b/src/chart/manage_chart_data.rs @@ -4,11 +4,11 @@ use crate::TrafficChart; use crate::networking::types::info_traffic::InfoTraffic; impl TrafficChart { - pub fn update_charts_data(&mut self, info_traffic: &InfoTraffic, no_more_packets: bool) { + pub fn update_charts_data(&mut self, info_traffic_msg: &InfoTraffic, no_more_packets: bool) { self.no_more_packets = no_more_packets; if self.ticks == 0 { - self.first_packet_timestamp = info_traffic.last_packet_timestamp; + self.first_packet_timestamp = info_traffic_msg.last_packet_timestamp; } #[allow(clippy::cast_precision_loss)] @@ -16,20 +16,13 @@ impl TrafficChart { self.ticks += 1; #[allow(clippy::cast_precision_loss)] - let out_bytes_entry = -1.0 - * (info_traffic.tot_data_info.outgoing_bytes() - - info_traffic.tot_data_info_prev.outgoing_bytes()) as f32; + let out_bytes_entry = -1.0 * info_traffic_msg.tot_data_info.outgoing_bytes() as f32; #[allow(clippy::cast_precision_loss)] - let in_bytes_entry = (info_traffic.tot_data_info.incoming_bytes() - - info_traffic.tot_data_info_prev.incoming_bytes()) as f32; + let in_bytes_entry = info_traffic_msg.tot_data_info.incoming_bytes() as f32; #[allow(clippy::cast_precision_loss)] - let out_packets_entry = -1.0 - * (info_traffic.tot_data_info.outgoing_packets() - - info_traffic.tot_data_info_prev.outgoing_packets()) as f32; + let out_packets_entry = -1.0 * info_traffic_msg.tot_data_info.outgoing_packets() as f32; #[allow(clippy::cast_precision_loss)] - let in_packets_entry = (info_traffic.tot_data_info.incoming_packets() - - info_traffic.tot_data_info_prev.incoming_packets()) - as f32; + let in_packets_entry = info_traffic_msg.tot_data_info.incoming_packets() as f32; let out_bytes_point = (tot_seconds, out_bytes_entry); let in_bytes_point = (tot_seconds, in_bytes_entry); @@ -168,7 +161,6 @@ mod tests { use crate::chart::manage_chart_data::{ChartSeries, get_max, get_min}; use crate::networking::types::data_info::DataInfo; - use crate::networking::types::traffic_direction::TrafficDirection; use crate::utils::types::timestamp::Timestamp; use crate::{ChartType, InfoTraffic, Language, StyleType, TrafficChart}; @@ -254,16 +246,7 @@ mod tests { spline: received_spl, all_time: vec![], }; - let tot_sent = 1000 * 28 + 500; - let tot_received = 21000 * 28 + 1000; - let tot_data_info_prev = - DataInfo::new_for_tests(tot_received, tot_sent, tot_received, tot_sent); - let tot_data_info = DataInfo::new_for_tests( - tot_received + 4444, - tot_sent + 3333, - tot_received + 2222, - tot_sent + 1111, - ); + let tot_data_info = DataInfo::new_for_tests(4444, 3333, 2222, 1111); let mut traffic_chart = TrafficChart { ticks: 29, out_bytes: sent.clone(), @@ -287,7 +270,6 @@ mod tests { all_packets: 0, tot_data_info, dropped_packets: 0, - tot_data_info_prev, ..Default::default() }; @@ -299,9 +281,6 @@ mod tests { assert_eq!(get_min(&traffic_chart.out_packets), -3333.0); assert_eq!(get_max(&traffic_chart.in_bytes), 21000.0); - // prev values aren't updated here anymore: manually set them - info_traffic.tot_data_info_prev = info_traffic.tot_data_info; - let mut sent_bytes = sent.clone(); sent_bytes .spline @@ -342,20 +321,9 @@ mod tests { received_bytes.spline.keys() ); - info_traffic - .tot_data_info - .add_packets(990, 2, TrafficDirection::Incoming); - info_traffic - .tot_data_info - .add_packet(99, TrafficDirection::Outgoing); + info_traffic.tot_data_info = DataInfo::new_for_tests(990, 1, 2, 99); traffic_chart.update_charts_data(&info_traffic, false); - info_traffic.tot_data_info_prev = info_traffic.tot_data_info; - info_traffic - .tot_data_info - .add_packet(0, TrafficDirection::Incoming); - info_traffic - .tot_data_info - .add_packets(220, 77, TrafficDirection::Outgoing); + info_traffic.tot_data_info = DataInfo::new_for_tests(1, 220, 0, 77); traffic_chart.update_charts_data(&info_traffic, false); sent_bytes.spline.remove(0); diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 4040f4094..8e4df7aae 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -49,7 +49,7 @@ use crate::networking::types::capture_context::{CaptureContext, CaptureSource, M use crate::networking::types::filters::Filters; use crate::networking::types::host::{Host, HostMessage}; use crate::networking::types::host_data_states::HostDataStates; -use crate::networking::types::info_traffic::InfoTrafficMessage; +use crate::networking::types::info_traffic::InfoTraffic; use crate::networking::types::ip_collection::AddressCollection; use crate::networking::types::my_device::MyDevice; use crate::networking::types::port_collection::PortCollection; @@ -68,7 +68,7 @@ use crate::utils::check_updates::set_newer_release_status; use crate::utils::error_logger::{ErrorLogger, Location}; use crate::utils::types::file_info::FileInfo; use crate::utils::types::web_page::WebPage; -use crate::{ConfigSettings, Configs, InfoTraffic, StyleType, TrafficChart, location}; +use crate::{ConfigSettings, Configs, StyleType, TrafficChart, location}; pub const FONT_FAMILY_NAME: &str = "Sarasa Mono SC for Sniffnet"; pub const ICON_FONT_FAMILY_NAME: &str = "Icons for Sniffnet"; @@ -686,25 +686,23 @@ impl Sniffer { } } - fn refresh_data(&mut self, msg: InfoTrafficMessage, no_more_packets: bool) { - self.info_traffic.refresh(msg, &self.favorite_hosts); - self.update_thresholds(); - let info_traffic = &self.info_traffic; - if info_traffic.tot_data_info.tot_packets() == 0 { - return; - } + fn refresh_data(&mut self, msg: InfoTraffic, no_more_packets: bool) { + self.traffic_chart.update_charts_data(&msg, no_more_packets); let emitted_notifications = notify_and_log( &mut self.logged_notifications, self.configs.settings.notifications, - info_traffic, + &msg, + &self.favorite_hosts, &self.capture_source, ); - self.info_traffic.favorites_last_interval = HashSet::new(); + self.info_traffic.refresh(msg); + self.update_thresholds(); + if self.info_traffic.tot_data_info.tot_packets() == 0 { + return; + } if self.thumbnail || self.running_page.ne(&RunningPage::Notifications) { self.unread_notifications += emitted_notifications; } - self.traffic_chart - .update_charts_data(&self.info_traffic, no_more_packets); if let CaptureSource::Device(device) = &self.capture_source { let current_device_name = device.get_name().clone(); @@ -1117,13 +1115,6 @@ impl Sniffer { // update host data states including the new host self.host_data_states.data.update(&host); - - // check if the newly resolved host was featured in the favorites (possible in case of already existing host) - if self.favorite_hosts.contains(&host) { - self.info_traffic - .favorites_last_interval - .insert((host, data_info_host)); - } } fn register_sigint_handler() -> Task { @@ -1160,7 +1151,7 @@ mod tests { use crate::gui::types::timing_events::TimingEvents; use crate::networking::types::data_info::DataInfo; use crate::networking::types::host::Host; - use crate::networking::types::info_traffic::InfoTrafficMessage; + use crate::networking::types::info_traffic::InfoTraffic; use crate::networking::types::traffic_direction::TrafficDirection; use crate::notifications::types::logged_notification::{ DataThresholdExceeded, LoggedNotification, @@ -1704,12 +1695,7 @@ mod tests { .add_packet(0, TrafficDirection::Outgoing); // Simulate a tick to apply the settings - sniffer.update(Message::TickRun( - 0, - InfoTrafficMessage::default(), - vec![], - false, - )); + sniffer.update(Message::TickRun(0, InfoTraffic::default(), vec![], false)); } let mut sniffer = Sniffer::new(Configs::default()); @@ -1921,14 +1907,14 @@ mod tests { #[parallel] // needed to not collide with other tests generating configs files fn test_clear_all_notifications() { let mut sniffer = Sniffer::new(Configs::default()); - sniffer.logged_notifications = - VecDeque::from([LoggedNotification::PacketsThresholdExceeded( - DataThresholdExceeded { - threshold: 0, - data_info: DataInfo::default(), - timestamp: "".to_string(), - }, - )]); + sniffer.logged_notifications = VecDeque::from([LoggedNotification::DataThresholdExceeded( + DataThresholdExceeded { + chart_type: ChartType::Packets, + threshold: 0, + data_info: DataInfo::default(), + timestamp: "".to_string(), + }, + )]); assert_eq!(sniffer.modal, None); sniffer.update(Message::ShowModal(MyModal::ClearAll)); diff --git a/src/gui/types/message.rs b/src/gui/types/message.rs index 0e833d1d8..418943367 100644 --- a/src/gui/types/message.rs +++ b/src/gui/types/message.rs @@ -6,7 +6,7 @@ use crate::gui::pages::types::running_page::RunningPage; use crate::gui::pages::types::settings_page::SettingsPage; use crate::gui::styles::types::gradient_type::GradientType; use crate::networking::types::host::{Host, HostMessage}; -use crate::networking::types::info_traffic::InfoTrafficMessage; +use crate::networking::types::info_traffic::InfoTraffic; use crate::notifications::types::notifications::Notification; use crate::report::types::search_parameters::SearchParameters; use crate::report::types::sort_type::SortType; @@ -20,7 +20,7 @@ pub enum Message { /// Run tasks to initialize the app StartApp(Option), /// Sent by the backend parsing packets; includes the capture id, new data, new hosts batched data, and whether an offline capture has finished - TickRun(usize, InfoTrafficMessage, Vec, bool), + TickRun(usize, InfoTraffic, Vec, bool), /// Select network device DeviceSelection(String), /// Select IP filter diff --git a/src/networking/manage_packets.rs b/src/networking/manage_packets.rs index 1b6c76bc3..f8b501ca5 100644 --- a/src/networking/manage_packets.rs +++ b/src/networking/manage_packets.rs @@ -10,7 +10,7 @@ use crate::networking::types::bogon::is_bogon; use crate::networking::types::capture_context::CaptureSource; use crate::networking::types::icmp_type::{IcmpType, IcmpTypeV4, IcmpTypeV6}; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; -use crate::networking::types::info_traffic::InfoTrafficMessage; +use crate::networking::types::info_traffic::InfoTraffic; use crate::networking::types::packet_filters_fields::PacketFiltersFields; use crate::networking::types::service::Service; use crate::networking::types::service_query::ServiceQuery; @@ -249,7 +249,7 @@ pub fn get_service( /// Function to insert the source and destination of a packet into the map containing the analyzed traffic pub fn modify_or_insert_in_map( - info_traffic_msg: &mut InfoTrafficMessage, + info_traffic_msg: &mut InfoTraffic, key: &AddressPortPair, cs: &CaptureSource, mac_addresses: (Option, Option), diff --git a/src/networking/parse_packets.rs b/src/networking/parse_packets.rs index 198434d8c..b18ce3a3c 100644 --- a/src/networking/parse_packets.rs +++ b/src/networking/parse_packets.rs @@ -17,7 +17,7 @@ use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::filters::Filters; use crate::networking::types::host::{Host, HostMessage}; use crate::networking::types::icmp_type::IcmpType; -use crate::networking::types::info_traffic::InfoTrafficMessage; +use crate::networking::types::info_traffic::InfoTraffic; use crate::networking::types::my_link_type::MyLinkType; use crate::networking::types::packet_filters_fields::PacketFiltersFields; use crate::networking::types::traffic_direction::TrafficDirection; @@ -48,7 +48,7 @@ pub fn parse_packets( let my_link_type = capture_context.my_link_type(); let (mut cap, mut savefile) = capture_context.consume(); - let mut info_traffic_msg = InfoTrafficMessage::default(); + let mut info_traffic_msg = InfoTraffic::default(); let resolutions_state = Arc::new(Mutex::new(AddressesResolutionState::default())); // list of newly resolved hosts to be sent (batched to avoid UI updates too often) let new_hosts_to_send = Arc::new(Mutex::new(Vec::new())); @@ -420,14 +420,14 @@ pub struct AddressesResolutionState { #[allow(clippy::large_enum_variant)] pub enum BackendTrafficMessage { - TickRun(usize, InfoTrafficMessage, Vec, bool), + TickRun(usize, InfoTraffic, Vec, bool), PendingHosts(usize, Vec), OfflineGap(usize, u32), } fn maybe_send_tick_run_live( cap_id: usize, - info_traffic_msg: &mut InfoTrafficMessage, + info_traffic_msg: &mut InfoTraffic, new_hosts_to_send: &Arc>>, cs: &mut CaptureSource, first_packet_ticks: &mut Option, @@ -453,7 +453,7 @@ fn maybe_send_tick_run_live( fn maybe_send_tick_run_offline( cap_id: usize, - info_traffic_msg: &mut InfoTrafficMessage, + info_traffic_msg: &mut InfoTraffic, new_hosts_to_send: &Arc>>, next_packet_timestamp: Timestamp, tx: &Sender, diff --git a/src/networking/types/data_info.rs b/src/networking/types/data_info.rs index ab29b7d7a..598c710c3 100644 --- a/src/networking/types/data_info.rs +++ b/src/networking/types/data_info.rs @@ -96,13 +96,6 @@ impl DataInfo { self.final_instant = rhs.final_instant; } - pub fn subtract(&mut self, rhs: Self) { - self.incoming_packets -= rhs.incoming_packets; - self.outgoing_packets -= rhs.outgoing_packets; - self.incoming_bytes -= rhs.incoming_bytes; - self.outgoing_bytes -= rhs.outgoing_bytes; - } - pub fn compare(&self, other: &Self, sort_type: SortType, chart_type: ChartType) -> Ordering { match chart_type { ChartType::Packets => match sort_type { diff --git a/src/networking/types/info_traffic.rs b/src/networking/types/info_traffic.rs index ca2cae4db..4c636aab2 100644 --- a/src/networking/types/info_traffic.rs +++ b/src/networking/types/info_traffic.rs @@ -6,10 +6,10 @@ use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::host::Host; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; use crate::utils::types::timestamp::Timestamp; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; /// Struct containing overall traffic statistics and data. -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct InfoTraffic { /// Total amount of exchanged data pub tot_data_info: DataInfo, @@ -27,16 +27,10 @@ pub struct InfoTraffic { pub services: HashMap, /// Map of the hosts with their data info pub hosts: HashMap, - /// Total amount of exchanged data before the current time interval - pub tot_data_info_prev: DataInfo, - /// Collection of favorite hosts that exchanged data in the last interval - pub favorites_last_interval: HashSet<(Host, DataInfoHost)>, } impl InfoTraffic { - pub fn refresh(&mut self, msg: InfoTrafficMessage, favorites: &HashSet) { - self.tot_data_info_prev = self.tot_data_info; - + pub fn refresh(&mut self, msg: InfoTraffic) { self.tot_data_info.refresh(msg.tot_data_info); self.all_packets += msg.all_packets; @@ -64,13 +58,6 @@ impl InfoTraffic { .or_insert(value); } - self.favorites_last_interval = msg - .hosts - .iter() - .filter(|(h, _)| favorites.contains(h)) - .map(|(h, data)| (h.clone(), *data)) - .collect(); - for (key, value) in msg.hosts { self.hosts .entry(key) @@ -101,30 +88,7 @@ impl InfoTraffic { ) } } -} - -/// Struct containing traffic statistics and data related to the last time interval. -#[derive(Debug, Clone, Default)] -pub struct InfoTrafficMessage { - /// Total amount of exchanged data - pub tot_data_info: DataInfo, - /// Total packets including those not filtered - pub all_packets: u128, - /// Total bytes including those not filtered - pub all_bytes: u128, - /// Number of dropped packets - pub dropped_packets: u32, - /// Timestamp of the latest parsed packet - pub last_packet_timestamp: Timestamp, - /// Map of the filtered traffic - pub map: HashMap, - /// Map of the upper layer services with their data info - pub services: HashMap, - /// Map of the hosts with their data info - pub hosts: HashMap, -} -impl InfoTrafficMessage { pub fn take_but_leave_something(&mut self) -> Self { let info_traffic = Self { last_packet_timestamp: self.last_packet_timestamp, diff --git a/src/notifications/notify_and_log.rs b/src/notifications/notify_and_log.rs index 2c44cc02c..61861888a 100644 --- a/src/notifications/notify_and_log.rs +++ b/src/notifications/notify_and_log.rs @@ -1,13 +1,15 @@ use crate::InfoTraffic; use crate::chart::types::chart_type::ChartType; use crate::networking::types::capture_context::CaptureSource; +use crate::networking::types::data_info_host::DataInfoHost; +use crate::networking::types::host::Host; use crate::notifications::types::logged_notification::{ DataThresholdExceeded, FavoriteTransmitted, LoggedNotification, }; use crate::notifications::types::notifications::Notifications; use crate::notifications::types::sound::{Sound, play}; use crate::utils::formatted_strings::get_formatted_timestamp; -use std::collections::VecDeque; +use std::collections::{HashSet, VecDeque}; /// Checks if one or more notifications have to be emitted and logs them. /// @@ -15,17 +17,17 @@ use std::collections::VecDeque; pub fn notify_and_log( logged_notifications: &mut VecDeque, notifications: Notifications, - info_traffic: &InfoTraffic, + info_traffic_msg: &InfoTraffic, + favorites: &HashSet, cs: &CaptureSource, ) -> usize { let mut sound_to_play = Sound::None; let mut emitted_notifications = 0; - let timestamp = info_traffic.last_packet_timestamp; - let mut data_info_delta = info_traffic.tot_data_info; - data_info_delta.subtract(info_traffic.tot_data_info_prev); + let timestamp = info_traffic_msg.last_packet_timestamp; + let data_info = info_traffic_msg.tot_data_info; // packets threshold if let Some(threshold) = notifications.packets_notification.threshold { - if data_info_delta.tot_packets() > u128::from(threshold) { + if data_info.tot_packets() > u128::from(threshold) { // log this notification emitted_notifications += 1; if logged_notifications.len() >= 30 { @@ -35,7 +37,7 @@ pub fn notify_and_log( DataThresholdExceeded { chart_type: ChartType::Packets, threshold: notifications.packets_notification.previous_threshold, - data_info: data_info_delta, + data_info, timestamp: get_formatted_timestamp(timestamp), }, )); @@ -46,7 +48,7 @@ pub fn notify_and_log( } // bytes threshold if let Some(threshold) = notifications.bytes_notification.threshold { - if data_info_delta.tot_bytes() > u128::from(threshold) { + if data_info.tot_bytes() > u128::from(threshold) { //log this notification emitted_notifications += 1; if logged_notifications.len() >= 30 { @@ -56,7 +58,7 @@ pub fn notify_and_log( DataThresholdExceeded { chart_type: ChartType::Bytes, threshold: notifications.bytes_notification.previous_threshold, - data_info: data_info_delta, + data_info, timestamp: get_formatted_timestamp(timestamp), }, )); @@ -66,26 +68,32 @@ pub fn notify_and_log( } } // from favorites - if notifications.favorite_notification.notify_on_favorite - && !info_traffic.favorites_last_interval.is_empty() - { - for (host, data_info_host) in info_traffic.favorites_last_interval.clone() { - //log this notification - emitted_notifications += 1; - if logged_notifications.len() >= 30 { - logged_notifications.pop_back(); - } + if notifications.favorite_notification.notify_on_favorite { + let favorites_last_interval: HashSet<(Host, DataInfoHost)> = info_traffic_msg + .hosts + .iter() + .filter(|(h, _)| favorites.contains(h)) + .map(|(h, data)| (h.clone(), *data)) + .collect(); + if !favorites_last_interval.is_empty() { + for (host, data_info_host) in favorites_last_interval { + //log this notification + emitted_notifications += 1; + if logged_notifications.len() >= 30 { + logged_notifications.pop_back(); + } - logged_notifications.push_front(LoggedNotification::FavoriteTransmitted( - FavoriteTransmitted { - host, - data_info_host, - timestamp: get_formatted_timestamp(timestamp), - }, - )); - } - if sound_to_play.eq(&Sound::None) { - sound_to_play = notifications.favorite_notification.sound; + logged_notifications.push_front(LoggedNotification::FavoriteTransmitted( + FavoriteTransmitted { + host, + data_info_host, + timestamp: get_formatted_timestamp(timestamp), + }, + )); + } + if sound_to_play.eq(&Sound::None) { + sound_to_play = notifications.favorite_notification.sound; + } } } From 711ca925558b4c816198e9132a956ac6ea6f2e6b Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 22 Jun 2025 12:34:01 +0200 Subject: [PATCH 12/19] fix agglomerate bars length in inspect page --- src/gui/pages/inspect_page.rs | 2 +- src/report/types/report_col.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gui/pages/inspect_page.rs b/src/gui/pages/inspect_page.rs index d6b1ac142..8ee94705c 100644 --- a/src/gui/pages/inspect_page.rs +++ b/src/gui/pages/inspect_page.rs @@ -556,7 +556,7 @@ fn get_agglomerates_row<'a>( let tot_bytes = tot.tot_bytes(); let (in_length, out_length) = get_bars_length(chart_type, &tot, &tot); - let bars = get_bars(in_length, out_length); + let bars = get_bars(in_length, out_length).width(ReportCol::FILTER_COLUMNS_WIDTH); let bytes_col = Column::new() .align_x(Alignment::Center) diff --git a/src/report/types/report_col.rs b/src/report/types/report_col.rs index de8987a9b..1ec1877f6 100644 --- a/src/report/types/report_col.rs +++ b/src/report/types/report_col.rs @@ -41,6 +41,8 @@ impl ReportCol { ReportCol::Packets, ]; + pub(crate) const FILTER_COLUMNS_WIDTH: f32 = 4.0 * SMALL_COL_WIDTH + 2.0 * LARGE_COL_WIDTH; + pub(crate) fn get_title(&self, language: Language) -> String { match self { ReportCol::SrcIp | ReportCol::DstIp => address_translation(language).to_string(), From 100ac1a70e1a84eff23aac19a6f3b8e00ad4cf71 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 22 Jun 2025 14:31:22 +0200 Subject: [PATCH 13/19] fix timestamp disalignments --- src/gui/sniffer.rs | 13 ++++++------ src/networking/types/info_traffic.rs | 31 ++++++++++++++-------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 8e4df7aae..0b3fe2312 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -686,7 +686,12 @@ impl Sniffer { } } - fn refresh_data(&mut self, msg: InfoTraffic, no_more_packets: bool) { + fn refresh_data(&mut self, mut msg: InfoTraffic, no_more_packets: bool) { + self.info_traffic.refresh(&mut msg); + self.update_thresholds(); + if self.info_traffic.tot_data_info.tot_packets() == 0 { + return; + } self.traffic_chart.update_charts_data(&msg, no_more_packets); let emitted_notifications = notify_and_log( &mut self.logged_notifications, @@ -695,11 +700,7 @@ impl Sniffer { &self.favorite_hosts, &self.capture_source, ); - self.info_traffic.refresh(msg); - self.update_thresholds(); - if self.info_traffic.tot_data_info.tot_packets() == 0 { - return; - } + if self.thumbnail || self.running_page.ne(&RunningPage::Notifications) { self.unread_notifications += emitted_notifications; } diff --git a/src/networking/types/info_traffic.rs b/src/networking/types/info_traffic.rs index 4c636aab2..976e78c72 100644 --- a/src/networking/types/info_traffic.rs +++ b/src/networking/types/info_traffic.rs @@ -30,7 +30,7 @@ pub struct InfoTraffic { } impl InfoTraffic { - pub fn refresh(&mut self, msg: InfoTraffic) { + pub fn refresh(&mut self, msg: &mut InfoTraffic) { self.tot_data_info.refresh(msg.tot_data_info); self.all_packets += msg.all_packets; @@ -39,30 +39,29 @@ impl InfoTraffic { // it can happen they're equal due to dis-alignments in the PCAP timestamp if self.last_packet_timestamp.secs() == msg.last_packet_timestamp.secs() { - self.last_packet_timestamp.add_secs(1); - } else { - self.last_packet_timestamp = msg.last_packet_timestamp; + msg.last_packet_timestamp.add_secs(1); } + self.last_packet_timestamp = msg.last_packet_timestamp; - for (key, value) in msg.map { + for (key, value) in &msg.map { self.map - .entry(key) - .and_modify(|x| x.refresh(&value)) - .or_insert(value); + .entry(*key) + .and_modify(|x| x.refresh(value)) + .or_insert_with(|| value.clone()); } - for (key, value) in msg.services { + for (key, value) in &msg.services { self.services - .entry(key) - .and_modify(|x| x.refresh(value)) - .or_insert(value); + .entry(*key) + .and_modify(|x| x.refresh(*value)) + .or_insert(*value); } - for (key, value) in msg.hosts { + for (key, value) in &msg.hosts { self.hosts - .entry(key) - .and_modify(|x| x.refresh(&value)) - .or_insert(value); + .entry(key.clone()) + .and_modify(|x| x.refresh(value)) + .or_insert(*value); } } From c58f33c5feb9e8d00d4dde7e6e25a54b4793f9b5 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 22 Jun 2025 14:34:00 +0200 Subject: [PATCH 14/19] minor fix --- src/gui/sniffer.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 0b3fe2312..0277de575 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -692,7 +692,6 @@ impl Sniffer { if self.info_traffic.tot_data_info.tot_packets() == 0 { return; } - self.traffic_chart.update_charts_data(&msg, no_more_packets); let emitted_notifications = notify_and_log( &mut self.logged_notifications, self.configs.settings.notifications, @@ -700,10 +699,10 @@ impl Sniffer { &self.favorite_hosts, &self.capture_source, ); - if self.thumbnail || self.running_page.ne(&RunningPage::Notifications) { self.unread_notifications += emitted_notifications; } + self.traffic_chart.update_charts_data(&msg, no_more_packets); if let CaptureSource::Device(device) = &self.capture_source { let current_device_name = device.get_name().clone(); From 50c4af5bd1a23a647046a0212d7232841a5977ce Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 22 Jun 2025 23:16:23 +0200 Subject: [PATCH 15/19] threshold notifications: expand / collapse --- resources/fonts/subset/icons.ttf | Bin 16652 -> 17948 bytes src/gui/pages/notifications_page.rs | 76 ++++++++++------ src/gui/sniffer.rs | 22 +++-- src/gui/types/message.rs | 2 + src/notifications/notify_and_log.rs | 81 ++++++++++-------- .../types/logged_notification.rs | 17 ++++ src/utils/types/icon.rs | 4 + 7 files changed, 135 insertions(+), 67 deletions(-) diff --git a/resources/fonts/subset/icons.ttf b/resources/fonts/subset/icons.ttf index 23974b829f1dc9412e7c694df45d6ec11ed07421..247aa74b21e7c98ad3048ebdba304ece5e3909a4 100644 GIT binary patch delta 1737 zcmaKsZ%kWd6vm&^mKFnT5!&8uv}LrTcLN=lOQBGB;*tjM2noiH?YgCQDpmH15L|U1H?J1Q%nRAB_9BPrw(vc;4IF@<-!s`o4M3 z`JLxH=S}Xtc#b~0NID3B8&k03a5CBR+_j^gSs=+l1P|=*O5!$VfUY0-yywu7TD9*} z?F>Li`P`?CrqY@Xp98jbj=eFI8c(mTvJ$#(0Vo_AdHt27BX}HeJPouK4-cjWVvGIX za_%?&Hx3UE>Z!tA4hI+eIIv@Q^u$C-fEM}uC7&;ijA^OIuKND~?6>%QVKg<7rgM%x zoaaBbznU5y3?92$40QIeiQDP1@e?lDxxxmvaDK)djt9J){&1n+yKfOi+=`zE?{uFR z`=4iRbLzX)LQiE{>5-ku%oDa~VuD8y*Aj#SFY{i)KOTyo2JFQ%coE|`&4Y?ZK?3{G zjTBC1&fB6zt!U$DJ37#r`P%kc(cb2LEof~+dq-!c!+s%i*j|{qYacBw|26niXiMl= zXeL}7em?wTh4aRK$9r*ohv@7e_UVEb5-K30mV30}01n{=^kWEPI0YSV;WFOG$GDC! za1-C*d(7b%JitRd!ZKFLMP8Dq0x0tZ)W$?qN$jSG(_gR19`Xh18x%=xjK^fZBF7?5 zpOjFDF+DE36vli3iGfy%$D#~`X-&XYuhJNuf;! zOk=F;3X`5~lmo58s(1@Ug>$u;8ib)h1to}$TIYN|R?A~2lqz3{>Kmd;nUzm8wvkNS zICr-438%OsHWsW7lBV&d2Q}3n4EmSF^0Rkm$de>Ti5&JONzcbslOGt!ZoMXDteDi_ z--w{5nSyos3ga(NXeVn-d&6=`lA7{~O;R3RU`E=+44bB@-jOrQQUCNON#m=e?QY4) z#{q_=yzl>+^(G6A=3VXd#e_4Nyg zmxQ-WeJQ~N`{J!at$mH`7r#V?gee{t9F90vC{(dp&;bPl&@s<5LS3Z? zUSbva=v2`Sjb8|hHo)VB;)B|)@g6bFheZp&7tOMoy_y$iy)*56kc0AOE-3A{^JTR? YSFO6NWWeKJ;5omCY5ddQBpP?_KmLYiHvj+t diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index 7432e8ac1..487993f1d 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -7,19 +7,19 @@ use iced::widget::{Space, button, vertical_space}; use iced::{Alignment, Font, Length, Padding}; use crate::chart::types::chart_type::ChartType; -use crate::countries::country_utils::get_computer_tooltip; +use crate::countries::flags_pictures::FLAGS_WIDTH_BIG; use crate::gui::components::header::get_button_settings; use crate::gui::components::tab::get_pages_tabs; use crate::gui::components::types::my_modal::MyModal; use crate::gui::pages::overview_page::{get_bars, get_bars_length, host_bar}; use crate::gui::pages::types::settings_page::SettingsPage; +use crate::gui::styles::button::ButtonType; use crate::gui::styles::container::ContainerType; use crate::gui::styles::scrollbar::ScrollbarType; use crate::gui::styles::style_constants::FONT_SIZE_FOOTER; use crate::gui::styles::text::TextType; use crate::gui::types::message::Message; use crate::networking::types::data_info::DataInfo; -use crate::networking::types::traffic_type::TrafficType; use crate::notifications::types::logged_notification::{ DataThresholdExceeded, FavoriteTransmitted, LoggedNotification, }; @@ -61,11 +61,11 @@ pub fn notifications_page(sniffer: &Sniffer) -> Container { if notifications.packets_notification.threshold.is_none() && notifications.bytes_notification.threshold.is_none() && !notifications.favorite_notification.notify_on_favorite - && sniffer.logged_notifications.is_empty() + && sniffer.logged_notifications.0.is_empty() { let body = body_no_notifications_set(font, language); tab_and_body = tab_and_body.push(body); - } else if sniffer.logged_notifications.is_empty() { + } else if sniffer.logged_notifications.0.is_empty() { let body = body_no_notifications_received(font, language, &sniffer.dots_pulse.0); tab_and_body = tab_and_body.push(body); } else { @@ -74,7 +74,7 @@ pub fn notifications_page(sniffer: &Sniffer) -> Container { .spacing(10) .padding(Padding::new(10.0).bottom(0)) .push( - Container::new(if sniffer.logged_notifications.len() < 30 { + Container::new(if sniffer.logged_notifications.0.len() < 30 { Text::new("") } else { Text::new(only_last_30_translation(language)).font(font) @@ -168,7 +168,6 @@ fn data_notification_log<'a>( ); let content = Row::new() .align_y(Alignment::Center) - .height(Length::Fill) .spacing(30) .push(icon) .push( @@ -179,7 +178,7 @@ fn data_notification_log<'a>( Row::new() .spacing(8) .push(Icon::Clock.to_text()) - .push(Text::new(logged_notification.timestamp).font(font)), + .push(Text::new(logged_notification.timestamp.clone()).font(font)), ) .push( Text::new(if chart_type == ChartType::Bytes { @@ -198,16 +197,21 @@ fn data_notification_log<'a>( ), ) .push(threshold_bar( - logged_notification.data_info, + &logged_notification, chart_type, first_entry_data_info, font, - language, )); - Container::new(content) - .height(120) + let content_and_extra = Column::new() + .push(content) + .push(if logged_notification.is_expanded { + Row::new().push(get_button_clear_all(font, language)) + } else { + Row::new() + }); + Container::new(content_and_extra) .width(Length::Fill) - .padding(10) + .padding(15) .class(ContainerType::BorderedRound) } @@ -230,7 +234,6 @@ fn favorite_notification_log<'a>( let content = Row::new() .spacing(30) .align_y(Alignment::Center) - .height(Length::Fill) .push( Icon::Star .to_text() @@ -257,9 +260,8 @@ fn favorite_notification_log<'a>( .push(host_bar); Container::new(content) - .height(120) .width(Length::Fill) - .padding(10) + .padding(15) .class(ContainerType::BorderedRound) } @@ -298,12 +300,13 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> let first_entry_data_info = sniffer .logged_notifications + .0 .iter() .map(LoggedNotification::data_info) .max_by(|d1, d2| d1.compare(d2, SortType::Ascending, chart_type)) .unwrap_or_default(); - for logged_notification in &sniffer.logged_notifications { + for logged_notification in &sniffer.logged_notifications.0 { ret_val = ret_val.push(match logged_notification { LoggedNotification::DataThresholdExceeded(data_threshold_exceeded) => { data_notification_log( @@ -328,26 +331,22 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> } fn threshold_bar<'a>( - data_info: DataInfo, + logged_notification: &DataThresholdExceeded, chart_type: ChartType, first_entry_data_info: DataInfo, font: Font, - language: Language, ) -> Row<'a, Message, StyleType> { + let data_info = logged_notification.data_info; + let id = logged_notification.id; + let is_expanded = logged_notification.is_expanded; + let (incoming_bar_len, outgoing_bar_len) = get_bars_length(chart_type, &first_entry_data_info, &data_info); Row::new() .align_y(Alignment::Center) .spacing(5) - .push(get_computer_tooltip( - true, - true, - None, - TrafficType::Unicast, - language, - font, - )) + .push(button_expand(id, is_expanded)) .push( Column::new() .spacing(1) @@ -364,3 +363,28 @@ fn threshold_bar<'a>( .push(get_bars(incoming_bar_len, outgoing_bar_len)), ) } + +fn button_expand<'a>( + notification_id: usize, + is_expanded: bool, +) -> Container<'a, Message, StyleType> { + let button = button( + if is_expanded { + Icon::Collapse + } else { + Icon::Expand + } + .to_text() + .size(25) + .align_x(Alignment::Center) + .align_y(Alignment::Center), + ) + .width(FLAGS_WIDTH_BIG) + .padding(Padding::ZERO) + .class(ButtonType::SortArrows) + .on_press(Message::ExpandNotification(notification_id, !is_expanded)); + + Container::new(button) + .align_x(Alignment::Center) + .align_y(Alignment::Center) +} diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 0277de575..fb29da755 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -87,8 +87,8 @@ pub struct Sniffer { pub addresses_resolved: HashMap, /// Collection of the favorite hosts pub favorite_hosts: HashSet, - /// Log of the received notifications - pub logged_notifications: VecDeque, + /// Log of the displayed notifications, with the total number of notifications for this capture + pub logged_notifications: (VecDeque, usize), /// Reports if a newer release of the software is available on GitHub pub newer_release_available: Option, /// Network device to be analyzed, or PCAP file to be imported @@ -155,7 +155,7 @@ impl Sniffer { info_traffic: InfoTraffic::default(), addresses_resolved: HashMap::new(), favorite_hosts: HashSet::new(), - logged_notifications: VecDeque::new(), + logged_notifications: (VecDeque::new(), 0), newer_release_available: None, capture_source: CaptureSource::Device(device), my_devices: Vec::new(), @@ -364,7 +364,7 @@ impl Sniffer { self.configs.settings.notifications.volume = volume; } Message::ClearAllNotifications => { - self.logged_notifications = VecDeque::new(); + self.logged_notifications.0 = VecDeque::new(); self.modal = None; } Message::SwitchPage(next) => { @@ -552,6 +552,16 @@ impl Sniffer { self.update_waiting_dots(); self.fetch_devices(); } + Message::ExpandNotification(id, expand) => { + if let Some(n) = self + .logged_notifications + .0 + .iter_mut() + .find(|n| n.id() == id) + { + n.expand(expand); + } + } } Task::none() } @@ -798,7 +808,7 @@ impl Sniffer { self.info_traffic = InfoTraffic::default(); self.addresses_resolved = HashMap::new(); self.favorite_hosts = HashSet::new(); - self.logged_notifications = VecDeque::new(); + self.logged_notifications = (VecDeque::new(), 0); self.pcap_error = None; self.traffic_chart = TrafficChart::new(style, language); self.report_sort_type = ReportSortType::default(); @@ -1058,7 +1068,7 @@ impl Sniffer { fn shortcut_ctrl_d(&mut self) -> Task { if self.running_page.eq(&RunningPage::Notifications) - && !self.logged_notifications.is_empty() + && !self.logged_notifications.0.is_empty() { return Task::done(Message::ShowModal(MyModal::ClearAll)); } diff --git a/src/gui/types/message.rs b/src/gui/types/message.rs index 418943367..6e0bbdc09 100644 --- a/src/gui/types/message.rs +++ b/src/gui/types/message.rs @@ -133,4 +133,6 @@ pub enum Message { OfflineGap(usize, u32), /// Emitted every second to repeat certain tasks (such as fetching the network devices) Periodic, + /// Expand or collapse the given logged notification + ExpandNotification(usize, bool), } diff --git a/src/notifications/notify_and_log.rs b/src/notifications/notify_and_log.rs index 61861888a..5d6de69da 100644 --- a/src/notifications/notify_and_log.rs +++ b/src/notifications/notify_and_log.rs @@ -15,32 +15,36 @@ use std::collections::{HashSet, VecDeque}; /// /// It returns the number of new notifications emitted pub fn notify_and_log( - logged_notifications: &mut VecDeque, + logged_notifications: &mut (VecDeque, usize), notifications: Notifications, info_traffic_msg: &InfoTraffic, favorites: &HashSet, cs: &CaptureSource, ) -> usize { let mut sound_to_play = Sound::None; - let mut emitted_notifications = 0; + let emitted_notifications_prev = logged_notifications.1; let timestamp = info_traffic_msg.last_packet_timestamp; let data_info = info_traffic_msg.tot_data_info; // packets threshold if let Some(threshold) = notifications.packets_notification.threshold { if data_info.tot_packets() > u128::from(threshold) { // log this notification - emitted_notifications += 1; - if logged_notifications.len() >= 30 { - logged_notifications.pop_back(); + logged_notifications.1 += 1; + if logged_notifications.0.len() >= 30 { + logged_notifications.0.pop_back(); } - logged_notifications.push_front(LoggedNotification::DataThresholdExceeded( - DataThresholdExceeded { - chart_type: ChartType::Packets, - threshold: notifications.packets_notification.previous_threshold, - data_info, - timestamp: get_formatted_timestamp(timestamp), - }, - )); + logged_notifications + .0 + .push_front(LoggedNotification::DataThresholdExceeded( + DataThresholdExceeded { + id: logged_notifications.1, + chart_type: ChartType::Packets, + threshold: notifications.packets_notification.previous_threshold, + data_info, + timestamp: get_formatted_timestamp(timestamp), + is_expanded: false, + }, + )); if sound_to_play.eq(&Sound::None) { sound_to_play = notifications.packets_notification.sound; } @@ -50,18 +54,22 @@ pub fn notify_and_log( if let Some(threshold) = notifications.bytes_notification.threshold { if data_info.tot_bytes() > u128::from(threshold) { //log this notification - emitted_notifications += 1; - if logged_notifications.len() >= 30 { - logged_notifications.pop_back(); + logged_notifications.1 += 1; + if logged_notifications.0.len() >= 30 { + logged_notifications.0.pop_back(); } - logged_notifications.push_front(LoggedNotification::DataThresholdExceeded( - DataThresholdExceeded { - chart_type: ChartType::Bytes, - threshold: notifications.bytes_notification.previous_threshold, - data_info, - timestamp: get_formatted_timestamp(timestamp), - }, - )); + logged_notifications + .0 + .push_front(LoggedNotification::DataThresholdExceeded( + DataThresholdExceeded { + id: logged_notifications.1, + chart_type: ChartType::Bytes, + threshold: notifications.bytes_notification.previous_threshold, + data_info, + timestamp: get_formatted_timestamp(timestamp), + is_expanded: false, + }, + )); if sound_to_play.eq(&Sound::None) { sound_to_play = notifications.bytes_notification.sound; } @@ -78,18 +86,21 @@ pub fn notify_and_log( if !favorites_last_interval.is_empty() { for (host, data_info_host) in favorites_last_interval { //log this notification - emitted_notifications += 1; - if logged_notifications.len() >= 30 { - logged_notifications.pop_back(); + logged_notifications.1 += 1; + if logged_notifications.0.len() >= 30 { + logged_notifications.0.pop_back(); } - logged_notifications.push_front(LoggedNotification::FavoriteTransmitted( - FavoriteTransmitted { - host, - data_info_host, - timestamp: get_formatted_timestamp(timestamp), - }, - )); + logged_notifications + .0 + .push_front(LoggedNotification::FavoriteTransmitted( + FavoriteTransmitted { + id: logged_notifications.1, + host, + data_info_host, + timestamp: get_formatted_timestamp(timestamp), + }, + )); } if sound_to_play.eq(&Sound::None) { sound_to_play = notifications.favorite_notification.sound; @@ -102,5 +113,5 @@ pub fn notify_and_log( play(sound_to_play, notifications.volume); } - emitted_notifications + logged_notifications.1 - emitted_notifications_prev } diff --git a/src/notifications/types/logged_notification.rs b/src/notifications/types/logged_notification.rs index d341ba46a..c44a5a67d 100644 --- a/src/notifications/types/logged_notification.rs +++ b/src/notifications/types/logged_notification.rs @@ -12,24 +12,41 @@ pub enum LoggedNotification { } impl LoggedNotification { + pub fn id(&self) -> usize { + match self { + LoggedNotification::DataThresholdExceeded(d) => d.id, + LoggedNotification::FavoriteTransmitted(f) => f.id, + } + } + pub fn data_info(&self) -> DataInfo { match self { LoggedNotification::DataThresholdExceeded(d) => d.data_info, LoggedNotification::FavoriteTransmitted(f) => f.data_info_host.data_info, } } + + pub fn expand(&mut self, expand: bool) { + match self { + LoggedNotification::DataThresholdExceeded(d) => d.is_expanded = expand, + LoggedNotification::FavoriteTransmitted(_) => {} + } + } } #[derive(Clone)] pub struct DataThresholdExceeded { + pub(crate) id: usize, pub(crate) chart_type: ChartType, pub(crate) threshold: u64, pub(crate) data_info: DataInfo, pub(crate) timestamp: String, + pub(crate) is_expanded: bool, } #[derive(Clone)] pub struct FavoriteTransmitted { + pub(crate) id: usize, pub(crate) host: Host, pub(crate) data_info_host: DataInfoHost, pub(crate) timestamp: String, diff --git a/src/utils/types/icon.rs b/src/utils/types/icon.rs index bacf36a9e..0d18158bf 100644 --- a/src/utils/types/icon.rs +++ b/src/utils/types/icon.rs @@ -14,9 +14,11 @@ pub enum Icon { Book, BytesThreshold, Clock, + Collapse, Copy, Generals, Error, + Expand, File, Forbidden, Funnel, @@ -99,6 +101,8 @@ impl Icon { Icon::Roadmap => '?', Icon::News => '>', Icon::Update => '<', + Icon::Expand => 'p', + Icon::Collapse => 'q', } } From 29d42b4ac2244a62bdc4b7410bb94c432414a088 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Mon, 23 Jun 2025 12:09:11 +0200 Subject: [PATCH 16/19] threshold notifications: include hosts & services --- src/countries/country_utils.rs | 19 +-- src/countries/flags_pictures.rs | 2 + src/gui/pages/notifications_page.rs | 137 +++++++++++++----- src/gui/pages/overview_page.rs | 62 +++++--- src/gui/sniffer.rs | 25 ++-- src/notifications/notify_and_log.rs | 47 ++++++ .../types/logged_notification.rs | 3 + src/utils/types/icon.rs | 8 +- 8 files changed, 225 insertions(+), 78 deletions(-) diff --git a/src/countries/country_utils.rs b/src/countries/country_utils.rs index 465bc6b13..cefc3b319 100644 --- a/src/countries/country_utils.rs +++ b/src/countries/country_utils.rs @@ -8,14 +8,15 @@ use crate::countries::flags_pictures::{ AD, AE, AF, AG, AI, AL, AM, AO, AQ, AR, AS, AT, AU, AW, AX, AZ, BA, BB, BD, BE, BF, BG, BH, BI, BJ, BM, BN, BO, BOGON, BR, BROADCAST, BS, BT, BV, BW, BY, BZ, CA, CC, CD, CF, CG, CH, CI, CK, CL, CM, CN, CO, COMPUTER, CR, CU, CV, CW, CX, CY, CZ, DE, DJ, DK, DM, DO, DZ, EC, EE, EG, EH, - ER, ES, ET, FI, FJ, FK, FLAGS_WIDTH_BIG, FLAGS_WIDTH_SMALL, FM, FO, FR, GA, GB, GD, GE, GG, GH, - GI, GL, GM, GN, GQ, GR, GS, GT, GU, GW, GY, HK, HN, HOME, HR, HT, HU, ID, IE, IL, IM, IN, IO, - IQ, IR, IS, IT, JE, JM, JO, JP, KE, KG, KH, KI, KM, KN, KP, KR, KW, KY, KZ, LA, LB, LC, LI, LK, - LR, LS, LT, LU, LV, LY, MA, MC, MD, ME, MG, MH, MK, ML, MM, MN, MO, MP, MR, MS, MT, MU, - MULTICAST, MV, MW, MX, MY, MZ, NA, NC, NE, NF, NG, NI, NL, NO, NP, NR, NU, NZ, OM, PA, PE, PF, - PG, PH, PK, PL, PN, PR, PS, PT, PW, PY, QA, RO, RS, RU, RW, SA, SB, SC, SD, SE, SG, SH, SI, SK, - SL, SM, SN, SO, SR, SS, ST, SV, SX, SY, SZ, TC, TD, TF, TG, TH, TJ, TK, TL, TM, TN, TO, TR, TT, - TV, TW, TZ, UA, UG, UNKNOWN, US, UY, UZ, VA, VC, VE, VG, VI, VN, VU, WS, YE, ZA, ZM, ZW, + ER, ES, ET, FI, FJ, FK, FLAGS_HEIGHT_BIG, FLAGS_WIDTH_BIG, FLAGS_WIDTH_SMALL, FM, FO, FR, GA, + GB, GD, GE, GG, GH, GI, GL, GM, GN, GQ, GR, GS, GT, GU, GW, GY, HK, HN, HOME, HR, HT, HU, ID, + IE, IL, IM, IN, IO, IQ, IR, IS, IT, JE, JM, JO, JP, KE, KG, KH, KI, KM, KN, KP, KR, KW, KY, KZ, + LA, LB, LC, LI, LK, LR, LS, LT, LU, LV, LY, MA, MC, MD, ME, MG, MH, MK, ML, MM, MN, MO, MP, MR, + MS, MT, MU, MULTICAST, MV, MW, MX, MY, MZ, NA, NC, NE, NF, NG, NI, NL, NO, NP, NR, NU, NZ, OM, + PA, PE, PF, PG, PH, PK, PL, PN, PR, PS, PT, PW, PY, QA, RO, RS, RU, RW, SA, SB, SC, SD, SE, SG, + SH, SI, SK, SL, SM, SN, SO, SR, SS, ST, SV, SX, SY, SZ, TC, TD, TF, TG, TH, TJ, TK, TL, TM, TN, + TO, TR, TT, TV, TW, TZ, UA, UG, UNKNOWN, US, UY, UZ, VA, VC, VE, VG, VI, VN, VU, WS, YE, ZA, + ZM, ZW, }; use crate::countries::types::country::Country; use crate::gui::styles::container::ContainerType; @@ -384,7 +385,7 @@ pub fn get_computer_tooltip<'a>( ))) .class(SvgType::AdaptColor) .width(FLAGS_WIDTH_BIG) - .height(FLAGS_WIDTH_BIG * 0.75); + .height(FLAGS_HEIGHT_BIG); let tooltip = match (is_my_address, is_local, is_bogon, traffic_type) { (true, _, _, _) => your_network_adapter_translation(language).to_string(), diff --git a/src/countries/flags_pictures.rs b/src/countries/flags_pictures.rs index f13feced8..4f6c9b723 100644 --- a/src/countries/flags_pictures.rs +++ b/src/countries/flags_pictures.rs @@ -1,6 +1,8 @@ pub const FLAGS_WIDTH_SMALL: f32 = 20.0; pub const FLAGS_WIDTH_BIG: f32 = 37.5; +pub const FLAGS_HEIGHT_BIG: f32 = FLAGS_WIDTH_BIG * 3.0 / 4.0; + pub const AD: &[u8] = include_bytes!("../../resources/countries_flags/4x3/ad.svg"); pub const AE: &[u8] = include_bytes!("../../resources/countries_flags/4x3/ae.svg"); pub const AF: &[u8] = include_bytes!("../../resources/countries_flags/4x3/af.svg"); diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index 487993f1d..4f6f2e5d3 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -1,25 +1,21 @@ -use iced::Length::FillPortion; -use iced::widget::scrollable::Direction; -use iced::widget::text::LineHeight; -use iced::widget::tooltip::Position; -use iced::widget::{Column, Container, Row, Scrollable, Text, Tooltip, horizontal_space}; -use iced::widget::{Space, button, vertical_space}; -use iced::{Alignment, Font, Length, Padding}; - use crate::chart::types::chart_type::ChartType; -use crate::countries::flags_pictures::FLAGS_WIDTH_BIG; +use crate::countries::country_utils::get_computer_tooltip; +use crate::countries::flags_pictures::FLAGS_HEIGHT_BIG; use crate::gui::components::header::get_button_settings; use crate::gui::components::tab::get_pages_tabs; use crate::gui::components::types::my_modal::MyModal; -use crate::gui::pages::overview_page::{get_bars, get_bars_length, host_bar}; +use crate::gui::pages::overview_page::{get_bars, get_bars_length, host_bar, service_bar}; use crate::gui::pages::types::settings_page::SettingsPage; -use crate::gui::styles::button::ButtonType; use crate::gui::styles::container::ContainerType; use crate::gui::styles::scrollbar::ScrollbarType; use crate::gui::styles::style_constants::FONT_SIZE_FOOTER; use crate::gui::styles::text::TextType; use crate::gui::types::message::Message; use crate::networking::types::data_info::DataInfo; +use crate::networking::types::data_info_host::DataInfoHost; +use crate::networking::types::host::Host; +use crate::networking::types::service::Service; +use crate::networking::types::traffic_type::TrafficType; use crate::notifications::types::logged_notification::{ DataThresholdExceeded, FavoriteTransmitted, LoggedNotification, }; @@ -32,6 +28,14 @@ use crate::translations::translations::{ }; use crate::utils::types::icon::Icon; use crate::{ByteMultiple, ConfigSettings, Language, RunningPage, Sniffer, StyleType}; +use iced::Length::FillPortion; +use iced::widget::scrollable::Direction; +use iced::widget::text::LineHeight; +use iced::widget::tooltip::Position; +use iced::widget::{Column, Container, Row, Rule, Scrollable, Text, Tooltip, horizontal_space}; +use iced::widget::{Space, button, vertical_space}; +use iced::{Alignment, Font, Length, Padding}; +use std::cmp::max; /// Computes the body of gui notifications page pub fn notifications_page(sniffer: &Sniffer) -> Container { @@ -142,7 +146,7 @@ fn body_no_notifications_received( } fn data_notification_log<'a>( - logged_notification: DataThresholdExceeded, + logged_notification: &DataThresholdExceeded, first_entry_data_info: DataInfo, language: Language, font: Font, @@ -197,18 +201,20 @@ fn data_notification_log<'a>( ), ) .push(threshold_bar( - &logged_notification, + logged_notification, chart_type, first_entry_data_info, + language, font, )); let content_and_extra = Column::new() + .spacing(10) .push(content) - .push(if logged_notification.is_expanded { - Row::new().push(get_button_clear_all(font, language)) - } else { - Row::new() - }); + .push(button_expand( + logged_notification.id, + logged_notification.is_expanded, + )) + .push_maybe(data_notification_extra(logged_notification, font, language)); Container::new(content_and_extra) .width(Length::Fill) .padding(15) @@ -216,7 +222,7 @@ fn data_notification_log<'a>( } fn favorite_notification_log<'a>( - logged_notification: FavoriteTransmitted, + logged_notification: &FavoriteTransmitted, first_entry_data_info: DataInfo, chart_type: ChartType, language: Language, @@ -249,7 +255,7 @@ fn favorite_notification_log<'a>( Row::new() .spacing(8) .push(Icon::Clock.to_text()) - .push(Text::new(logged_notification.timestamp).font(font)), + .push(Text::new(logged_notification.timestamp.clone()).font(font)), ) .push( Text::new(favorite_transmitted_translation(language)) @@ -310,7 +316,7 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> ret_val = ret_val.push(match logged_notification { LoggedNotification::DataThresholdExceeded(data_threshold_exceeded) => { data_notification_log( - data_threshold_exceeded.clone(), + data_threshold_exceeded, first_entry_data_info, language, font, @@ -318,7 +324,7 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> } LoggedNotification::FavoriteTransmitted(favorite_transmitted) => { favorite_notification_log( - favorite_transmitted.clone(), + favorite_transmitted, first_entry_data_info, chart_type, language, @@ -334,19 +340,24 @@ fn threshold_bar<'a>( logged_notification: &DataThresholdExceeded, chart_type: ChartType, first_entry_data_info: DataInfo, + language: Language, font: Font, ) -> Row<'a, Message, StyleType> { let data_info = logged_notification.data_info; - let id = logged_notification.id; - let is_expanded = logged_notification.is_expanded; - let (incoming_bar_len, outgoing_bar_len) = get_bars_length(chart_type, &first_entry_data_info, &data_info); Row::new() .align_y(Alignment::Center) .spacing(5) - .push(button_expand(id, is_expanded)) + .push(get_computer_tooltip( + true, + true, + None, + TrafficType::Unicast, + language, + font, + )) .push( Column::new() .spacing(1) @@ -370,21 +381,81 @@ fn button_expand<'a>( ) -> Container<'a, Message, StyleType> { let button = button( if is_expanded { - Icon::Collapse + Icon::SortAscending } else { - Icon::Expand + Icon::SortDescending } .to_text() - .size(25) + .size(11) .align_x(Alignment::Center) .align_y(Alignment::Center), ) - .width(FLAGS_WIDTH_BIG) - .padding(Padding::ZERO) - .class(ButtonType::SortArrows) + .padding(Padding::ZERO.top(if is_expanded { 0 } else { 2 })) + .width(25) + .height(25) .on_press(Message::ExpandNotification(notification_id, !is_expanded)); Container::new(button) - .align_x(Alignment::Center) + .padding(Padding::ZERO.left(395)) .align_y(Alignment::Center) } + +fn data_notification_extra<'a>( + logged_notification: &DataThresholdExceeded, + font: Font, + language: Language, +) -> Option> { + let max_entries = max( + logged_notification.hosts.len(), + logged_notification.services.len(), + ); + if !logged_notification.is_expanded || max_entries == 0 { + return None; + } + let spacing = 10.0; + #[allow(clippy::cast_precision_loss)] + let height = (FLAGS_HEIGHT_BIG + spacing) * max_entries as f32; + + let mut hosts_col = Column::new().spacing(spacing).width(Length::FillPortion(5)); + let first_data_info_host = logged_notification + .hosts + .first() + .unwrap_or(&(Host::default(), DataInfoHost::default())) + .1 + .data_info; + for (host, data_info_host) in &logged_notification.hosts { + let host_bar = host_bar( + host, + data_info_host, + logged_notification.chart_type, + first_data_info_host, + font, + language, + ); + hosts_col = hosts_col.push(host_bar); + } + + let mut services_col = Column::new().spacing(spacing).width(Length::FillPortion(2)); + let first_data_info_service = logged_notification + .services + .first() + .unwrap_or(&(Service::default(), DataInfo::default())) + .1; + for (service, data_info) in &logged_notification.services { + let service_bar = service_bar( + service, + data_info, + logged_notification.chart_type, + first_data_info_service, + font, + ); + services_col = services_col.push(service_bar); + } + + Some( + Row::new() + .push(hosts_col) + .push(Container::new(Rule::vertical(30)).height(height)) + .push(services_col), + ) +} diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 79a7749e9..9f8341e2f 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -5,7 +5,7 @@ use crate::chart::types::donut_chart::donut_chart; use crate::countries::country_utils::get_flag_tooltip; -use crate::countries::flags_pictures::FLAGS_WIDTH_BIG; +use crate::countries::flags_pictures::{FLAGS_HEIGHT_BIG, FLAGS_WIDTH_BIG}; use crate::gui::components::tab::get_pages_tabs; use crate::gui::sniffer::Sniffer; use crate::gui::styles::button::ButtonType; @@ -21,6 +21,7 @@ use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::filters::Filters; use crate::networking::types::host::Host; +use crate::networking::types::service::Service; use crate::report::get_report_entries::{get_host_entries, get_service_entries}; use crate::report::types::search_parameters::SearchParameters; use crate::report::types::sort_type::SortType; @@ -334,29 +335,11 @@ fn col_service<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { .unwrap_or_default(); for (service, data_info) in &entries { - let (incoming_bar_len, outgoing_bar_len) = - get_bars_length(chart_type, &first_entry_data_info, data_info); - - let content = Column::new() - .spacing(1) - .push( - Row::new() - .push(Text::new(service.to_string()).font(font)) - .push(horizontal_space()) - .push( - Text::new(if chart_type.eq(&ChartType::Packets) { - data_info.tot_packets().to_string() - } else { - ByteMultiple::formatted_string(data_info.tot_bytes()) - }) - .font(font), - ), - ) - .push(get_bars(incoming_bar_len, outgoing_bar_len)); + let content = service_bar(service, data_info, chart_type, first_entry_data_info, font); scroll_service = scroll_service.push( button(content) - .padding(Padding::new(5.0).right(15).bottom(8).left(10)) + .padding(Padding::new(5.0).right(15).left(10)) .on_press(Message::Search(SearchParameters::new_service_search( service, ))) @@ -413,6 +396,7 @@ pub fn host_bar<'a>( ); Row::new() + .height(FLAGS_HEIGHT_BIG) .align_y(Alignment::Center) .spacing(5) .push(get_flag_tooltip( @@ -450,6 +434,40 @@ pub fn host_bar<'a>( ) } +pub fn service_bar<'a>( + service: &Service, + data_info: &DataInfo, + chart_type: ChartType, + first_entry_data_info: DataInfo, + font: Font, +) -> Row<'a, Message, StyleType> { + let (incoming_bar_len, outgoing_bar_len) = + get_bars_length(chart_type, &first_entry_data_info, data_info); + + Row::new() + .height(FLAGS_HEIGHT_BIG) + .align_y(Alignment::Center) + .spacing(5) + .push( + Column::new() + .spacing(1) + .push( + Row::new() + .push(Text::new(service.to_string()).font(font)) + .push(horizontal_space()) + .push( + Text::new(if chart_type.eq(&ChartType::Packets) { + data_info.tot_packets().to_string() + } else { + ByteMultiple::formatted_string(data_info.tot_bytes()) + }) + .font(font), + ), + ) + .push(get_bars(incoming_bar_len, outgoing_bar_len)), + ) +} + fn col_info<'a>(sniffer: &Sniffer) -> Container<'a, Message, StyleType> { let ConfigSettings { style, language, .. @@ -780,7 +798,7 @@ fn get_star_button<'a>(is_favorite: bool, host: Host) -> Button<'a, Message, Sty .align_y(Alignment::Center), ) .padding(0) - .height(FLAGS_WIDTH_BIG * 0.75) + .height(FLAGS_HEIGHT_BIG) .width(FLAGS_WIDTH_BIG) .class(if is_favorite { ButtonType::Starred diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index fb29da755..660c5b6f5 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -1917,22 +1917,27 @@ mod tests { #[parallel] // needed to not collide with other tests generating configs files fn test_clear_all_notifications() { let mut sniffer = Sniffer::new(Configs::default()); - sniffer.logged_notifications = VecDeque::from([LoggedNotification::DataThresholdExceeded( - DataThresholdExceeded { - chart_type: ChartType::Packets, - threshold: 0, - data_info: DataInfo::default(), - timestamp: "".to_string(), - }, - )]); + sniffer.logged_notifications.0 = + VecDeque::from([LoggedNotification::DataThresholdExceeded( + DataThresholdExceeded { + id: 1, + chart_type: ChartType::Packets, + threshold: 0, + data_info: DataInfo::default(), + timestamp: "".to_string(), + services: Vec::new(), + hosts: Vec::new(), + is_expanded: false, + }, + )]); assert_eq!(sniffer.modal, None); sniffer.update(Message::ShowModal(MyModal::ClearAll)); assert_eq!(sniffer.modal, Some(MyModal::ClearAll)); - assert_eq!(sniffer.logged_notifications.len(), 1); + assert_eq!(sniffer.logged_notifications.0.len(), 1); sniffer.update(Message::ClearAllNotifications); assert_eq!(sniffer.modal, None); - assert_eq!(sniffer.logged_notifications.len(), 0); + assert_eq!(sniffer.logged_notifications.0.len(), 0); } #[test] diff --git a/src/notifications/notify_and_log.rs b/src/notifications/notify_and_log.rs index 5d6de69da..03e10294d 100644 --- a/src/notifications/notify_and_log.rs +++ b/src/notifications/notify_and_log.rs @@ -1,14 +1,18 @@ use crate::InfoTraffic; use crate::chart::types::chart_type::ChartType; use crate::networking::types::capture_context::CaptureSource; +use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::host::Host; +use crate::networking::types::service::Service; use crate::notifications::types::logged_notification::{ DataThresholdExceeded, FavoriteTransmitted, LoggedNotification, }; use crate::notifications::types::notifications::Notifications; use crate::notifications::types::sound::{Sound, play}; +use crate::report::types::sort_type::SortType; use crate::utils::formatted_strings::get_formatted_timestamp; +use std::cmp::min; use std::collections::{HashSet, VecDeque}; /// Checks if one or more notifications have to be emitted and logs them. @@ -43,6 +47,8 @@ pub fn notify_and_log( data_info, timestamp: get_formatted_timestamp(timestamp), is_expanded: false, + hosts: hosts_list(info_traffic_msg, ChartType::Packets), + services: services_list(info_traffic_msg, ChartType::Packets), }, )); if sound_to_play.eq(&Sound::None) { @@ -68,6 +74,8 @@ pub fn notify_and_log( data_info, timestamp: get_formatted_timestamp(timestamp), is_expanded: false, + hosts: hosts_list(info_traffic_msg, ChartType::Bytes), + services: services_list(info_traffic_msg, ChartType::Bytes), }, )); if sound_to_play.eq(&Sound::None) { @@ -115,3 +123,42 @@ pub fn notify_and_log( logged_notifications.1 - emitted_notifications_prev } + +fn hosts_list(info_traffic_msg: &InfoTraffic, chart_type: ChartType) -> Vec<(Host, DataInfoHost)> { + let mut hosts: Vec<(Host, DataInfoHost)> = info_traffic_msg + .hosts + .iter() + .map(|(h, data)| (h.clone(), *data)) + .collect(); + hosts.sort_by(|(_, a), (_, b)| { + a.data_info + .compare(&b.data_info, SortType::Descending, chart_type) + }); + let n_entry = min(hosts.len(), 4); + hosts + .get(..n_entry) + .unwrap_or_default() + .to_owned() + .into_iter() + .collect() +} + +fn services_list( + info_traffic_msg: &InfoTraffic, + chart_type: ChartType, +) -> Vec<(Service, DataInfo)> { + let mut services: Vec<(Service, DataInfo)> = info_traffic_msg + .services + .iter() + .filter(|(service, _)| service != &&Service::NotApplicable) + .map(|(s, data)| (*s, *data)) + .collect(); + services.sort_by(|(_, a), (_, b)| a.compare(b, SortType::Descending, chart_type)); + let n_entry = min(services.len(), 4); + services + .get(..n_entry) + .unwrap_or_default() + .to_owned() + .into_iter() + .collect() +} diff --git a/src/notifications/types/logged_notification.rs b/src/notifications/types/logged_notification.rs index c44a5a67d..6a75d2cd4 100644 --- a/src/notifications/types/logged_notification.rs +++ b/src/notifications/types/logged_notification.rs @@ -2,6 +2,7 @@ use crate::chart::types::chart_type::ChartType; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::host::Host; +use crate::networking::types::service::Service; /// Enum representing the possible notification events. pub enum LoggedNotification { @@ -42,6 +43,8 @@ pub struct DataThresholdExceeded { pub(crate) data_info: DataInfo, pub(crate) timestamp: String, pub(crate) is_expanded: bool, + pub(crate) hosts: Vec<(Host, DataInfoHost)>, + pub(crate) services: Vec<(Service, DataInfo)>, } #[derive(Clone)] diff --git a/src/utils/types/icon.rs b/src/utils/types/icon.rs index 0d18158bf..f37d610a1 100644 --- a/src/utils/types/icon.rs +++ b/src/utils/types/icon.rs @@ -14,11 +14,11 @@ pub enum Icon { Book, BytesThreshold, Clock, - Collapse, + // Collapse, Copy, Generals, Error, - Expand, + // Expand, File, Forbidden, Funnel, @@ -101,8 +101,8 @@ impl Icon { Icon::Roadmap => '?', Icon::News => '>', Icon::Update => '<', - Icon::Expand => 'p', - Icon::Collapse => 'q', + // Icon::Expand => 'p', + // Icon::Collapse => 'q', } } From 90146717b78b4384ece1ddb22b0464c52da9b1a1 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Tue, 24 Jun 2025 02:59:27 +0200 Subject: [PATCH 17/19] unify packets & bytes threshold notifications --- resources/fonts/subset/icons.ttf | Bin 17948 -> 19828 bytes src/chart/types/chart_type.rs | 3 +- src/cli/mod.rs | 3 +- src/gui/pages/notifications_page.rs | 10 +- src/gui/pages/settings_notifications_page.rs | 264 ++++++++----------- src/gui/sniffer.rs | 238 +++++------------ src/gui/styles/text.rs | 2 - src/gui/types/message.rs | 2 +- src/gui/types/timing_events.rs | 26 +- src/networking/types/data_info.rs | 7 + src/notifications/notify_and_log.rs | 44 +--- src/notifications/types/notifications.rs | 114 ++------ src/notifications/types/sound.rs | 5 +- src/translations/translations_4.rs | 8 + src/utils/types/icon.rs | 4 +- 15 files changed, 236 insertions(+), 494 deletions(-) diff --git a/resources/fonts/subset/icons.ttf b/resources/fonts/subset/icons.ttf index 247aa74b21e7c98ad3048ebdba304ece5e3909a4..a77400c68889757fe6342dd9122f62c947ea5b6c 100644 GIT binary patch delta 2339 zcmb_eYlvM}72a#_efB=@b06n%-_Ojs^GN32-p9E~X6DXhGBaso(oAbjVpYnR#7r@1 zVmgEf(i6egkBT9q{bBn<1f@t3X+R5N5s9sei2oE6{Nn?pR?!xVmKoQ%NtL1qJ`S9F z&R*Zz-&*S&zVF;;o`lyv4>18kh()dwM%LHX9yv$(F| zdjE-cw!0_4eC8vBK!Tqll1oGOOJ2K^Ky=mV;2yAZaf?g z_{t;Mm$`pcL;ed8u9!0Pr0e$01wc6YXIdJf*Zy+xb#A7V!G zbpCN9yO+oz{9Pu8$;$Q%bfY%2H^KMKwmbK9d;LK)+<)NU-2B35kt`h|%ZFFC53x^e zuQPS~`|L#Dd&Ykw*b2Uq>*l_iKU+Ln{88y>>3Zqwr8mo^@_hMf`483WwXNEVb+vwf z{rUQ9jVq1kZr#hj6vmm|9Ri$kg~a43@;v!A`2qO}`8D}H`6~p_fW0t)!*C4V1y|rR zgbZh)H4DRN&oJVFFQg4l5?F-5?~O1X%I2Uqz@&x)Hnd?lO5j#QjMJjUWYq?*Il!G# z_(6;uBE(=bVG8ctk6C+OGpHbKgcNw=Ap<6MCT4M8*b4i1!d37CAH&d(hTSNsFIpj9 zec=zgzAGAtB|V2x;JZ>ZaD^S&$UE$`@GeF=7nz5hAlZh6#*H;v!$^vxcqi#CbjE{p zf_~KVqdpc12RLol$NZiznn@LG49f;KV2KDxyb6pSGY={vCAR0$ZI`r)=m#)It5$_h7Oiq-pLkwZV+;>JyS` zRSH_R7}2T*GOgKA%cTW3A2gV$*pq6^6=9Z&!(48uoI7cArW#5vW9eLF+LG&b>tJgs zooha4y&mI5Ngd5HnaIkpFLjMRxHPbWnw3hq9Z#K~Tdy&tdwSXqiR-4m5LGtu1%b^sZ+1$f>MOf$D1Ji|h5)WAI^}gsC?GA(C{7k3JFX;!PFW4(!d5X{fWPGK4X3Wn zeDimv^7_l&t235TSg{fSDo~>&lv#waWA1FL!T1?56@vQt?PJhhx_ zSd?*f=RPJNU932k%GDx;>`@oEF7}b`aRY4TVK3niih=allj9VnJe&Emcfa9raUu^IiNNH=4Yi2v6nSZ6}k6N*8Sl0b^p!W zkuXk_^a?8JB~(&~N~#2YDdlmoc~?p2MWUp>t!XliLeheq!ca(tZZp1cS4dXIvJAzO z8lB7Ms(loW2C8*OM-QtgD-^!gVmv4-B6a(1IyzO()on_dx^ou)Ak+_ZCqXW8eI8it zY7Nv8*YhJ@+oMO|#(kC6ruUhu^*Qf;*C={vmU$uzrJJaj1++pjZBcDfqk7LB>9>A6 eeWHM$|AVWi*oZ7{-&lPgtZcu!I>X|pZ~P4a7e-A0 delta 446 zcmew|i*Zg5V?6^S0|NsuLjwadLx7uGi0`5ZzSRs2Za_f>8Fv>~H-;k&D;OADPXPHA z{=xc2Vat+h7#J8Mfc&uJ+{A)pCWbW(3``C{vAXoc;)4JGfhIAy9$;W#;7HG@OmkzA z$!1_+@nB%EVfSN^s0xB7~B^3fQg3=GT%fc%c!#EJsOCKd~zJPKr zS1>6no&rjO0tNv<8toYDfusY2BZJfAJxqxTKn@US>*(s~8yFfHo0yuJTUc6I+t@PL z*)upeI!$(D?wK6G%rf~bv%i7xIT03-9#Ia_ed0?bG$fWt+>#WK43jLAoF;ie%1io| z%sE*J*(BK&ay)W=a%be(HV3dQRom=n>%5qfAI diff --git a/src/chart/types/chart_type.rs b/src/chart/types/chart_type.rs index 29b308eee..0e3394358 100644 --- a/src/chart/types/chart_type.rs +++ b/src/chart/types/chart_type.rs @@ -1,8 +1,9 @@ use crate::Language; use crate::translations::translations::{bytes_translation, packets_translation}; +use serde::{Deserialize, Serialize}; /// Enum representing the possible kind of chart displayed. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum ChartType { Packets, Bytes, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 14e23f9fd..0256167c3 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -98,8 +98,7 @@ mod tests { ), notifications: Notifications { volume: 100, - packets_notification: Default::default(), - bytes_notification: Default::default(), + data_notification: Default::default(), favorite_notification: Default::default(), }, style: StyleType::Custom(ExtraStyles::DraculaDark), diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index 4f6f2e5d3..3c602f157 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -62,8 +62,7 @@ pub fn notifications_page(sniffer: &Sniffer) -> Container { tab_and_body = tab_and_body.push(tabs); - if notifications.packets_notification.threshold.is_none() - && notifications.bytes_notification.threshold.is_none() + if notifications.data_notification.threshold.is_none() && !notifications.favorite_notification.notify_on_favorite && sniffer.logged_notifications.0.is_empty() { @@ -157,10 +156,10 @@ fn data_notification_log<'a>( } else { logged_notification.threshold.to_string() }; - let icon = if chart_type == ChartType::Bytes { - Icon::BytesThreshold - } else { + let icon = if chart_type == ChartType::Packets { Icon::PacketsThreshold + } else { + Icon::BytesThreshold } .to_text() .size(80) @@ -244,7 +243,6 @@ fn favorite_notification_log<'a>( Icon::Star .to_text() .size(80) - .class(TextType::Starred) .line_height(LineHeight::Relative(1.0)), ) .push( diff --git a/src/gui/pages/settings_notifications_page.rs b/src/gui/pages/settings_notifications_page.rs index 80b872130..6c10c1c80 100644 --- a/src/gui/pages/settings_notifications_page.rs +++ b/src/gui/pages/settings_notifications_page.rs @@ -1,8 +1,9 @@ use iced::widget::scrollable::Direction; use iced::widget::{Button, Slider, horizontal_space}; use iced::widget::{Checkbox, Column, Container, Row, Scrollable, Space, Text, TextInput}; -use iced::{Alignment, Font, Length}; +use iced::{Alignment, Font, Length, Padding}; +use crate::chart::types::chart_type::ChartType; use crate::gui::components::button::button_hide; use crate::gui::components::tab::get_settings_tabs; use crate::gui::pages::types::settings_page::SettingsPage; @@ -14,14 +15,15 @@ use crate::gui::styles::text::TextType; use crate::gui::styles::types::gradient_type::GradientType; use crate::gui::types::message::Message; use crate::notifications::types::notifications::{ - BytesNotification, FavoriteNotification, Notification, PacketsNotification, + DataNotification, FavoriteNotification, Notification, }; use crate::notifications::types::sound::Sound; use crate::translations::translations::{ - bytes_exceeded_translation, favorite_transmitted_translation, notifications_title_translation, - packets_exceeded_translation, per_second_translation, settings_translation, sound_translation, - threshold_translation, volume_translation, + favorite_transmitted_translation, notifications_title_translation, per_second_translation, + settings_translation, sound_translation, threshold_translation, volume_translation, }; +use crate::translations::translations_2::data_representation_translation; +use crate::translations::translations_4::data_exceeded_translation; use crate::utils::types::icon::Icon; use crate::{ConfigSettings, Language, Sniffer, StyleType}; @@ -36,18 +38,12 @@ pub fn settings_notifications_page<'a>(sniffer: &Sniffer) -> Container<'a, Messa let font = style.get_extension().font; let font_headers = style.get_extension().font_headers; - // Use thresholds that have not yet been applied, if available - if let Some((temp_packets_notifications, temp_bytes_notifications)) = - sniffer.timing_events.temp_thresholds() - { - notifications.packets_notification.threshold = temp_packets_notifications.threshold; - notifications.packets_notification.previous_threshold = - temp_packets_notifications.previous_threshold; - - notifications.bytes_notification.threshold = temp_bytes_notifications.threshold; - notifications.bytes_notification.byte_multiple = temp_bytes_notifications.byte_multiple; - notifications.bytes_notification.previous_threshold = - temp_bytes_notifications.previous_threshold; + // Use threshold that has not yet been applied, if available + if let Some(temp_data_notification) = sniffer.timing_events.temp_threshold() { + notifications.data_notification.threshold = temp_data_notification.threshold; + notifications.data_notification.byte_multiple = temp_data_notification.byte_multiple; + notifications.data_notification.previous_threshold = + temp_data_notification.previous_threshold; } let mut content = Column::new() @@ -82,13 +78,8 @@ pub fn settings_notifications_page<'a>(sniffer: &Sniffer) -> Container<'a, Messa Column::new() .align_x(Alignment::Center) .width(Length::Fill) - .push(get_packets_notify( - notifications.packets_notification, - language, - font, - )) - .push(get_bytes_notify( - notifications.bytes_notification, + .push(get_data_notify( + notifications.data_notification, language, font, )) @@ -108,82 +99,29 @@ pub fn settings_notifications_page<'a>(sniffer: &Sniffer) -> Container<'a, Messa .class(ContainerType::Modal) } -fn get_packets_notify<'a>( - packets_notification: PacketsNotification, - language: Language, - font: Font, -) -> Column<'a, Message, StyleType> { - let checkbox = Checkbox::new( - packets_exceeded_translation(language), - packets_notification.threshold.is_some(), - ) - .on_toggle(move |toggled| { - if toggled { - Message::UpdateNotificationSettings( - Notification::Packets(PacketsNotification { - threshold: Some(packets_notification.previous_threshold), - ..packets_notification - }), - false, - ) - } else { - Message::UpdateNotificationSettings( - Notification::Packets(PacketsNotification { - threshold: None, - ..packets_notification - }), - false, - ) - } - }) - .size(18) - .font(font); - - let mut ret_val = Column::new().spacing(10).push(checkbox); - - if packets_notification.threshold.is_none() { - Column::new().padding(5).push( - Container::new(ret_val) - .padding(10) - .width(700) - .class(ContainerType::BorderedRound), - ) - } else { - let input_row = input_group_packets(packets_notification, font, language); - let sound_row = sound_buttons(Notification::Packets(packets_notification), font, language); - ret_val = ret_val.push(input_row).push(sound_row); - Column::new().padding(5).push( - Container::new(ret_val) - .padding(10) - .width(700) - .class(ContainerType::BorderedRound), - ) - } -} - -fn get_bytes_notify<'a>( - bytes_notification: BytesNotification, +fn get_data_notify<'a>( + data_notification: DataNotification, language: Language, font: Font, ) -> Column<'a, Message, StyleType> { let checkbox = Checkbox::new( - bytes_exceeded_translation(language), - bytes_notification.threshold.is_some(), + data_exceeded_translation(language), + data_notification.threshold.is_some(), ) .on_toggle(move |toggled| { if toggled { Message::UpdateNotificationSettings( - Notification::Bytes(BytesNotification { - threshold: Some(bytes_notification.previous_threshold), - ..bytes_notification + Notification::Data(DataNotification { + threshold: Some(data_notification.previous_threshold), + ..data_notification }), false, ) } else { Message::UpdateNotificationSettings( - Notification::Bytes(BytesNotification { + Notification::Data(DataNotification { threshold: None, - ..bytes_notification + ..data_notification }), false, ) @@ -192,9 +130,9 @@ fn get_bytes_notify<'a>( .size(18) .font(font); - let mut ret_val = Column::new().spacing(10).push(checkbox); + let mut ret_val = Column::new().spacing(15).push(checkbox); - if bytes_notification.threshold.is_none() { + if data_notification.threshold.is_none() { Column::new().padding(5).push( Container::new(ret_val) .padding(10) @@ -202,9 +140,18 @@ fn get_bytes_notify<'a>( .class(ContainerType::BorderedRound), ) } else { - let input_row = input_group_bytes(bytes_notification, font, language); - let sound_row = sound_buttons(Notification::Bytes(bytes_notification), font, language); - ret_val = ret_val.push(input_row).push(sound_row); + let data_representation_row = row_data_representation( + data_notification, + language, + font, + data_notification.chart_type, + ); + let input_row = input_group_bytes(data_notification, font, language); + let sound_row = sound_buttons(Notification::Data(data_notification), font, language); + ret_val = ret_val + .push(sound_row) + .push(data_representation_row) + .push(input_row); Column::new().padding(5).push( Container::new(ret_val) .padding(10) @@ -236,7 +183,7 @@ fn get_favorite_notify<'a>( .size(18) .font(font); - let mut ret_val = Column::new().spacing(10).push(checkbox); + let mut ret_val = Column::new().spacing(15).push(checkbox); if favorite_notification.notify_on_favorite { let sound_row = sound_buttons( @@ -261,54 +208,8 @@ fn get_favorite_notify<'a>( } } -fn input_group_packets<'a>( - packets_notification: PacketsNotification, - font: Font, - language: Language, -) -> Container<'a, Message, StyleType> { - let curr_threshold_str = &packets_notification - .threshold - .unwrap_or_default() - .to_string(); - let input_row = Row::new() - .align_y(Alignment::Center) - .spacing(5) - .push(Space::with_width(45)) - .push(Text::new(format!("{}:", threshold_translation(language))).font(font)) - .push( - TextInput::new( - "0", - if curr_threshold_str == "0" { - "" - } else { - curr_threshold_str - }, - ) - .on_input(move |value| { - let packets_notification = - PacketsNotification::from(&value, Some(packets_notification)); - Message::UpdateNotificationSettings( - Notification::Packets(packets_notification), - false, - ) - }) - .padding([2, 5]) - .font(font) - .width(100), - ) - .push( - Text::new(per_second_translation(language)) - .font(font) - .align_y(Alignment::Center) - .size(FONT_SIZE_FOOTER), - ); - Container::new(input_row) - .align_x(Alignment::Center) - .align_y(Alignment::Center) -} - fn input_group_bytes<'a>( - bytes_notification: BytesNotification, + bytes_notification: DataNotification, font: Font, language: Language, ) -> Container<'a, Message, StyleType> { @@ -331,8 +232,8 @@ fn input_group_bytes<'a>( }, ) .on_input(move |value| { - let bytes_notification = BytesNotification::from(&value, Some(bytes_notification)); - Message::UpdateNotificationSettings(Notification::Bytes(bytes_notification), false) + let bytes_notification = DataNotification::from(&value, Some(bytes_notification)); + Message::UpdateNotificationSettings(Notification::Data(bytes_notification), false) }) .padding([2, 5]) .font(font) @@ -396,12 +297,12 @@ fn sound_buttons<'a>( language: Language, ) -> Row<'a, Message, StyleType> { let current_sound = match notification { - Notification::Packets(n) => n.sound, - Notification::Bytes(n) => n.sound, + Notification::Data(n) => n.sound, Notification::Favorite(n) => n.sound, }; let mut ret_val = Row::new() + .width(Length::Shrink) .align_y(Alignment::Center) .spacing(5) .push(Space::with_width(45)) @@ -410,28 +311,29 @@ fn sound_buttons<'a>( for option in Sound::ALL { let is_active = current_sound.eq(&option); let message_value = match notification { - Notification::Packets(n) => { - Notification::Packets(PacketsNotification { sound: option, ..n }) - } - Notification::Bytes(n) => Notification::Bytes(BytesNotification { sound: option, ..n }), + Notification::Data(n) => Notification::Data(DataNotification { sound: option, ..n }), Notification::Favorite(n) => { Notification::Favorite(FavoriteNotification { sound: option, ..n }) } }; ret_val = ret_val.push( - Button::new(option.get_text(font)) - .padding(0) - .width(80) - .height(25) - .class(if is_active { - ButtonType::BorderedRoundSelected - } else { - ButtonType::BorderedRound - }) - .on_press(Message::UpdateNotificationSettings( - message_value, - option.ne(&Sound::None), - )), + Button::new( + option + .get_text(font) + .align_x(Alignment::Center) + .align_y(Alignment::Center), + ) + .padding(Padding::ZERO.left(15).right(15)) + .height(25) + .class(if is_active { + ButtonType::BorderedRoundSelected + } else { + ButtonType::BorderedRound + }) + .on_press(Message::UpdateNotificationSettings( + message_value, + option.ne(&Sound::None), + )), ); } ret_val @@ -465,3 +367,45 @@ pub fn settings_header<'a>( .width(Length::Fill) .class(ContainerType::Gradient(color_gradient)) } + +fn row_data_representation<'a>( + data_notification: DataNotification, + language: Language, + font: Font, + chart_type: ChartType, +) -> Row<'a, Message, StyleType> { + let mut ret_val = Row::new() + .width(Length::Shrink) + .align_y(Alignment::Center) + .spacing(5) + .push(Space::with_width(45)) + .push(Text::new(format!("{}:", data_representation_translation(language))).font(font)); + + for option in ChartType::ALL { + let is_active = chart_type.eq(&option); + ret_val = ret_val.push( + Button::new( + Text::new(option.get_label(language).to_owned()) + .size(FONT_SIZE_FOOTER) + .align_x(Alignment::Center) + .align_y(Alignment::Center) + .font(font), + ) + .padding(Padding::ZERO.left(15).right(15)) + .height(25) + .class(if is_active { + ButtonType::BorderedRoundSelected + } else { + ButtonType::BorderedRound + }) + .on_press(Message::UpdateNotificationSettings( + Notification::Data(DataNotification { + chart_type: option, + ..data_notification + }), + false, + )), + ); + } + ret_val +} diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 660c5b6f5..8ac31fdb0 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -55,9 +55,7 @@ use crate::networking::types::my_device::MyDevice; use crate::networking::types::port_collection::PortCollection; use crate::notifications::notify_and_log::notify_and_log; use crate::notifications::types::logged_notification::LoggedNotification; -use crate::notifications::types::notifications::{ - BytesNotification, Notification, PacketsNotification, -}; +use crate::notifications::types::notifications::{DataNotification, Notification}; use crate::notifications::types::sound::{Sound, play}; use crate::report::get_report_entries::get_searched_entries; use crate::report::types::report_sort_type::ReportSortType; @@ -662,43 +660,32 @@ impl Sniffer { self.configs.settings.scale_factor } - /// Updates thresholds if they haven't been edited for a while - fn update_thresholds(&mut self) { + /// Updates threshold if they haven't been edited for a while + fn update_threshold(&mut self) { // Ignore if just edited - if let Some(temp_thresholds) = self.timing_events.threshold_adjust_expired_take() { - // Apply the temporary thresholds to the actual config - self.configs - .settings - .notifications - .packets_notification - .threshold = temp_thresholds.0.threshold; - self.configs - .settings - .notifications - .packets_notification - .previous_threshold = temp_thresholds.0.previous_threshold; - + if let Some(temp_threshold) = self.timing_events.threshold_adjust_expired_take() { + // Apply the temporary threshold to the actual config self.configs .settings .notifications - .bytes_notification - .threshold = temp_thresholds.1.threshold; + .data_notification + .threshold = temp_threshold.threshold; self.configs .settings .notifications - .bytes_notification - .byte_multiple = temp_thresholds.1.byte_multiple; + .data_notification + .byte_multiple = temp_threshold.byte_multiple; self.configs .settings .notifications - .bytes_notification - .previous_threshold = temp_thresholds.1.previous_threshold; + .data_notification + .previous_threshold = temp_threshold.previous_threshold; } } fn refresh_data(&mut self, mut msg: InfoTraffic, no_more_packets: bool) { self.info_traffic.refresh(&mut msg); - self.update_thresholds(); + self.update_threshold(); if self.info_traffic.tot_data_info.tot_packets() == 0 { return; } @@ -873,88 +860,57 @@ impl Sniffer { } } - /// Don't update adjustments to thresholds immediately: - /// that is, sound and toggling thresholds on/off should be applied immediately + /// Don't update adjustments to threshold immediately: + /// that is, sound and toggling threshold on/off should be applied immediately /// Threshold adjustments are saved in `self.timing_events.threshold_adjust` and then applied /// after timeout fn update_notifications_settings(&mut self, notification: Notification, emit_sound: bool) { let notifications = self.configs.settings.notifications; let sound = match notification { - Notification::Packets(PacketsNotification { - threshold, - sound, - previous_threshold, - }) => { - let mut temp_thresholds = self.get_temp_thresholds(); - // Check if adjustments have been made to thresholds - if temp_thresholds.0.threshold != threshold - || temp_thresholds.0.previous_threshold != previous_threshold - { - temp_thresholds.0 = PacketsNotification { - threshold, - sound, - previous_threshold, - }; - self.timing_events.threshold_adjust_now(temp_thresholds); - } - // If threshold is toggled, apply immediately - if threshold.is_some() != notifications.packets_notification.threshold.is_some() { - self.configs - .settings - .notifications - .packets_notification - .threshold = threshold; - self.configs - .settings - .notifications - .packets_notification - .previous_threshold = previous_threshold; - } - // always update sound - self.configs - .settings - .notifications - .packets_notification - .sound = sound; - sound - } - Notification::Bytes(BytesNotification { + Notification::Data(DataNotification { + chart_type, threshold, byte_multiple, sound, previous_threshold, }) => { - let mut temp_thresholds = self.get_temp_thresholds(); - if temp_thresholds.1.threshold != threshold - || temp_thresholds.1.byte_multiple != byte_multiple - || temp_thresholds.1.previous_threshold != previous_threshold + let mut temp_threshold = self.get_temp_threshold(); + if temp_threshold.threshold != threshold + || temp_threshold.byte_multiple != byte_multiple + || temp_threshold.previous_threshold != previous_threshold { - temp_thresholds.1 = BytesNotification { + temp_threshold = DataNotification { + chart_type, threshold, byte_multiple, sound, previous_threshold, }; - self.timing_events.threshold_adjust_now(temp_thresholds); + self.timing_events.threshold_adjust_now(temp_threshold); } - if threshold.is_some() != notifications.bytes_notification.threshold.is_some() { + if threshold.is_some() != notifications.data_notification.threshold.is_some() { self.configs .settings .notifications - .bytes_notification + .data_notification .threshold = threshold; self.configs .settings .notifications - .bytes_notification + .data_notification .byte_multiple = byte_multiple; self.configs .settings .notifications - .bytes_notification + .data_notification .previous_threshold = previous_threshold; } - self.configs.settings.notifications.bytes_notification.sound = sound; + self.configs.settings.notifications.data_notification.sound = sound; + self.configs + .settings + .notifications + .data_notification + .chart_type = chart_type; sound } Notification::Favorite(favorite_notification) => { @@ -967,16 +923,13 @@ impl Sniffer { } } - /// Returns thresholds in `timing_events.threshold_adjust` or copy of current thresholds - fn get_temp_thresholds(&self) -> (PacketsNotification, BytesNotification) { - if let Some(temp_thresholds) = self.timing_events.temp_thresholds() { - temp_thresholds + /// Returns threshold in `timing_events.threshold_adjust` or copy of current threshold + fn get_temp_threshold(&self) -> DataNotification { + if let Some(temp_threshold) = self.timing_events.temp_threshold() { + temp_threshold } else { let notifications = self.configs.settings.notifications; - ( - notifications.packets_notification, - notifications.bytes_notification, - ) + notifications.data_notification } } @@ -1167,7 +1120,7 @@ mod tests { DataThresholdExceeded, LoggedNotification, }; use crate::notifications::types::notifications::{ - BytesNotification, FavoriteNotification, Notification, Notifications, PacketsNotification, + DataNotification, FavoriteNotification, Notification, Notifications, }; use crate::notifications::types::sound::Sound; use crate::report::types::report_col::ReportCol; @@ -1697,7 +1650,7 @@ mod tests { std::thread::sleep(Duration::from_millis( TimingEvents::TIMEOUT_THRESHOLD_ADJUST + 5, )); - // Thresholds adjustments won't be updated if `info_traffic.tot_in_packets` + // Threshold adjustments won't be updated if `info_traffic.tot_in_packets` // and `info_traffic.tot_out_packets` are both `0`. sniffer .info_traffic @@ -1709,53 +1662,32 @@ mod tests { } let mut sniffer = Sniffer::new(Configs::default()); - let packets_notification_init = PacketsNotification { - threshold: None, - sound: Sound::Gulp, - previous_threshold: 750, - }; - - let packets_notification_toggle_on = PacketsNotification { - threshold: Some(750), - sound: Sound::Gulp, - previous_threshold: 750, - }; - - let packets_notification_adjusted_threshold_sound_off = PacketsNotification { - threshold: Some(1122), - sound: Sound::None, - previous_threshold: 1122, - }; - - // Used for comparing that sound is applied right away, but not threshold adjustment - let packets_notification_sound_off_only = PacketsNotification { - threshold: Some(750), - sound: Sound::None, - previous_threshold: 750, - }; - - let bytes_notification_init = BytesNotification { + let bytes_notification_init = DataNotification { + chart_type: ChartType::Bytes, threshold: None, byte_multiple: ByteMultiple::KB, sound: Sound::Pop, previous_threshold: 800000, }; - let bytes_notification_toggled_on = BytesNotification { + let bytes_notification_toggled_on = DataNotification { + chart_type: ChartType::Bytes, threshold: Some(800_000), byte_multiple: ByteMultiple::GB, sound: Sound::Pop, previous_threshold: 800_000, }; - let bytes_notification_adjusted_threshold_sound_off = BytesNotification { + let bytes_notification_adjusted_threshold_sound_off = DataNotification { + chart_type: ChartType::Bytes, threshold: Some(3), byte_multiple: ByteMultiple::KB, sound: Sound::None, previous_threshold: 3, }; - let bytes_notification_sound_off_only = BytesNotification { + let bytes_notification_sound_off_only = DataNotification { + chart_type: ChartType::Bytes, threshold: Some(800_000), byte_multiple: ByteMultiple::GB, sound: Sound::None, @@ -1776,11 +1708,7 @@ mod tests { assert_eq!(sniffer.configs.settings.notifications.volume, 60); assert_eq!(sniffer.configs.settings.notifications.volume, 60); assert_eq!( - sniffer.configs.settings.notifications.packets_notification, - packets_notification_init - ); - assert_eq!( - sniffer.configs.settings.notifications.bytes_notification, + sniffer.configs.settings.notifications.data_notification, bytes_notification_init ); assert_eq!( @@ -1793,11 +1721,7 @@ mod tests { assert_eq!(sniffer.configs.settings.notifications.volume, 95); assert_eq!( - sniffer.configs.settings.notifications.packets_notification, - packets_notification_init, - ); - assert_eq!( - sniffer.configs.settings.notifications.bytes_notification, + sniffer.configs.settings.notifications.data_notification, bytes_notification_init, ); assert_eq!( @@ -1805,38 +1729,8 @@ mod tests { fav_notification_init, ); - sniffer.update(Message::UpdateNotificationSettings( - Notification::Packets(packets_notification_toggle_on), - false, - )); - - // Verify that toggling threshold is applied immediately - assert_eq!( - sniffer.configs.settings.notifications.packets_notification, - packets_notification_toggle_on, - ); - - sniffer.update(Message::UpdateNotificationSettings( - Notification::Packets(packets_notification_adjusted_threshold_sound_off), - false, - )); - - // Verify thresholds are not applied before timeout expires, - // and rest is applied immediately assert_eq!( - sniffer.configs.settings.notifications.packets_notification, - packets_notification_sound_off_only, - ); - - expire_notifications_timeout(&mut sniffer); - - assert_eq!(sniffer.configs.settings.notifications.volume, 95); - assert_eq!( - sniffer.configs.settings.notifications.packets_notification, - packets_notification_adjusted_threshold_sound_off, - ); - assert_eq!( - sniffer.configs.settings.notifications.bytes_notification, + sniffer.configs.settings.notifications.data_notification, bytes_notification_init ); assert_eq!( @@ -1846,25 +1740,25 @@ mod tests { // Toggle on bytes notifications sniffer.update(Message::UpdateNotificationSettings( - Notification::Bytes(bytes_notification_toggled_on), + Notification::Data(bytes_notification_toggled_on), true, )); // Verify that toggling threshold is applied immediately assert_eq!( - sniffer.configs.settings.notifications.bytes_notification, + sniffer.configs.settings.notifications.data_notification, bytes_notification_toggled_on, ); sniffer.update(Message::UpdateNotificationSettings( - Notification::Bytes(bytes_notification_adjusted_threshold_sound_off), + Notification::Data(bytes_notification_adjusted_threshold_sound_off), true, )); - // Verify adjusted thresholds are not applied before timeout expires, + // Verify adjusted threshold is not applied before timeout expires, // and rest is applied immediately assert_eq!( - sniffer.configs.settings.notifications.bytes_notification, + sniffer.configs.settings.notifications.data_notification, bytes_notification_sound_off_only, ); @@ -1872,11 +1766,7 @@ mod tests { assert_eq!(sniffer.configs.settings.notifications.volume, 95); assert_eq!( - sniffer.configs.settings.notifications.packets_notification, - packets_notification_adjusted_threshold_sound_off - ); - assert_eq!( - sniffer.configs.settings.notifications.bytes_notification, + sniffer.configs.settings.notifications.data_notification, bytes_notification_adjusted_threshold_sound_off ); assert_eq!( @@ -1890,7 +1780,7 @@ mod tests { true, )); - // Verify thresholds are not applied before timeout expires, + // Verify threshold is not applied before timeout expires, // and rest is applied immediately assert_eq!( sniffer.configs.settings.notifications.favorite_notification, @@ -1900,11 +1790,7 @@ mod tests { // And the rest is intact assert_eq!(sniffer.configs.settings.notifications.volume, 95); assert_eq!( - sniffer.configs.settings.notifications.packets_notification, - packets_notification_adjusted_threshold_sound_off - ); - assert_eq!( - sniffer.configs.settings.notifications.bytes_notification, + sniffer.configs.settings.notifications.data_notification, bytes_notification_adjusted_threshold_sound_off ); assert_eq!( @@ -2026,8 +1912,7 @@ mod tests { style_path: "".to_string(), notifications: Notifications { volume: 60, - packets_notification: Default::default(), - bytes_notification: Default::default(), + data_notification: Default::default(), favorite_notification: Default::default() }, style: StyleType::Custom(ExtraStyles::A11yDark) @@ -2068,8 +1953,7 @@ mod tests { ), notifications: Notifications { volume: 100, - packets_notification: Default::default(), - bytes_notification: Default::default(), + data_notification: Default::default(), favorite_notification: Default::default() }, style: StyleType::Custom(ExtraStyles::DraculaDark) diff --git a/src/gui/styles/text.rs b/src/gui/styles/text.rs index c21e4807d..917b3949e 100644 --- a/src/gui/styles/text.rs +++ b/src/gui/styles/text.rs @@ -18,7 +18,6 @@ pub enum TextType { Subtitle, Danger, Sponsor, - Starred, } /// Returns a formatted caption followed by subtitle, new line, tab, and desc @@ -76,7 +75,6 @@ pub fn highlight(style: &StyleType, element: TextType) -> Color { TextType::Outgoing => colors.outgoing, TextType::Danger | TextType::Sponsor => ext.red_alert_color, TextType::Standard => colors.text_body, - TextType::Starred => colors.starred, } } diff --git a/src/gui/types/message.rs b/src/gui/types/message.rs index 6e0bbdc09..847c2cb46 100644 --- a/src/gui/types/message.rs +++ b/src/gui/types/message.rs @@ -65,7 +65,7 @@ pub enum Message { ChangeRunningPage(RunningPage), /// Select language LanguageSelection(Language), - /// Set packets notification + /// Set notification settings UpdateNotificationSettings(Notification, bool), /// Clear all received notifications ClearAllNotifications, diff --git a/src/gui/types/timing_events.rs b/src/gui/types/timing_events.rs index 7d9ce5e2c..dd4a52ead 100644 --- a/src/gui/types/timing_events.rs +++ b/src/gui/types/timing_events.rs @@ -2,7 +2,7 @@ use std::net::{IpAddr, Ipv4Addr}; use std::ops::Sub; use std::time::Duration; -use crate::notifications::types::notifications::{BytesNotification, PacketsNotification}; +use crate::notifications::types::notifications::DataNotification; pub struct TimingEvents { /// Instant of the last window focus @@ -13,12 +13,9 @@ pub struct TimingEvents { thumbnail_enter: std::time::Instant, /// Instant of the last click on the thumbnail window thumbnail_click: std::time::Instant, - /// Instant of the last adjust of notifications settings thresholds and storage of these - /// thresholds while editing - threshold_adjust: ( - std::time::Instant, - Option<(PacketsNotification, BytesNotification)>, - ), + /// Instant of the last adjust of notifications settings threshold and storage of this + /// threshold while editing + threshold_adjust: (std::time::Instant, Option), } impl TimingEvents { @@ -66,18 +63,13 @@ impl TimingEvents { < Duration::from_millis(TimingEvents::TIMEOUT_THUMBNAIL_CLICK) } - pub fn threshold_adjust_now( - &mut self, - temp_thresholds: (PacketsNotification, BytesNotification), - ) { + pub fn threshold_adjust_now(&mut self, temp_threshold: DataNotification) { self.threshold_adjust.0 = std::time::Instant::now(); - self.threshold_adjust.1 = Some(temp_thresholds); + self.threshold_adjust.1 = Some(temp_threshold); } - /// If timeout has expired, take temporary thresholds - pub fn threshold_adjust_expired_take( - &mut self, - ) -> Option<(PacketsNotification, BytesNotification)> { + /// If timeout has expired, take temporary threshold + pub fn threshold_adjust_expired_take(&mut self) -> Option { if self.threshold_adjust.0.elapsed() > Duration::from_millis(TimingEvents::TIMEOUT_THRESHOLD_ADJUST) { @@ -87,7 +79,7 @@ impl TimingEvents { } } - pub fn temp_thresholds(&self) -> Option<(PacketsNotification, BytesNotification)> { + pub fn temp_threshold(&self) -> Option { self.threshold_adjust.1 } } diff --git a/src/networking/types/data_info.rs b/src/networking/types/data_info.rs index 598c710c3..5c5d99af7 100644 --- a/src/networking/types/data_info.rs +++ b/src/networking/types/data_info.rs @@ -47,6 +47,13 @@ impl DataInfo { self.incoming_bytes + self.outgoing_bytes } + pub fn tot_data(&self, chart_type: ChartType) -> u128 { + match chart_type { + ChartType::Packets => self.tot_packets(), + ChartType::Bytes => self.tot_bytes(), + } + } + pub fn add_packet(&mut self, bytes: u128, traffic_direction: TrafficDirection) { if traffic_direction.eq(&TrafficDirection::Outgoing) { self.outgoing_packets += 1; diff --git a/src/notifications/notify_and_log.rs b/src/notifications/notify_and_log.rs index 03e10294d..93acddd90 100644 --- a/src/notifications/notify_and_log.rs +++ b/src/notifications/notify_and_log.rs @@ -29,36 +29,10 @@ pub fn notify_and_log( let emitted_notifications_prev = logged_notifications.1; let timestamp = info_traffic_msg.last_packet_timestamp; let data_info = info_traffic_msg.tot_data_info; - // packets threshold - if let Some(threshold) = notifications.packets_notification.threshold { - if data_info.tot_packets() > u128::from(threshold) { - // log this notification - logged_notifications.1 += 1; - if logged_notifications.0.len() >= 30 { - logged_notifications.0.pop_back(); - } - logged_notifications - .0 - .push_front(LoggedNotification::DataThresholdExceeded( - DataThresholdExceeded { - id: logged_notifications.1, - chart_type: ChartType::Packets, - threshold: notifications.packets_notification.previous_threshold, - data_info, - timestamp: get_formatted_timestamp(timestamp), - is_expanded: false, - hosts: hosts_list(info_traffic_msg, ChartType::Packets), - services: services_list(info_traffic_msg, ChartType::Packets), - }, - )); - if sound_to_play.eq(&Sound::None) { - sound_to_play = notifications.packets_notification.sound; - } - } - } - // bytes threshold - if let Some(threshold) = notifications.bytes_notification.threshold { - if data_info.tot_bytes() > u128::from(threshold) { + // data threshold + if let Some(threshold) = notifications.data_notification.threshold { + let chart_type = notifications.data_notification.chart_type; + if data_info.tot_data(chart_type) > u128::from(threshold) { //log this notification logged_notifications.1 += 1; if logged_notifications.0.len() >= 30 { @@ -69,17 +43,17 @@ pub fn notify_and_log( .push_front(LoggedNotification::DataThresholdExceeded( DataThresholdExceeded { id: logged_notifications.1, - chart_type: ChartType::Bytes, - threshold: notifications.bytes_notification.previous_threshold, + chart_type, + threshold: notifications.data_notification.previous_threshold, data_info, timestamp: get_formatted_timestamp(timestamp), is_expanded: false, - hosts: hosts_list(info_traffic_msg, ChartType::Bytes), - services: services_list(info_traffic_msg, ChartType::Bytes), + hosts: hosts_list(info_traffic_msg, chart_type), + services: services_list(info_traffic_msg, chart_type), }, )); if sound_to_play.eq(&Sound::None) { - sound_to_play = notifications.bytes_notification.sound; + sound_to_play = notifications.data_notification.sound; } } } diff --git a/src/notifications/types/notifications.rs b/src/notifications/types/notifications.rs index 01e6718f3..8935ad073 100644 --- a/src/notifications/types/notifications.rs +++ b/src/notifications/types/notifications.rs @@ -1,14 +1,14 @@ use serde::{Deserialize, Serialize}; use crate::ByteMultiple; +use crate::chart::types::chart_type::ChartType; use crate::notifications::types::sound::Sound; /// Used to contain the notifications configuration set by the user #[derive(Clone, Serialize, Deserialize, Copy, PartialEq, Debug)] pub struct Notifications { pub volume: u8, - pub packets_notification: PacketsNotification, - pub bytes_notification: BytesNotification, + pub data_notification: DataNotification, pub favorite_notification: FavoriteNotification, } @@ -16,8 +16,7 @@ impl Default for Notifications { fn default() -> Self { Notifications { volume: 60, - packets_notification: PacketsNotification::default(), - bytes_notification: BytesNotification::default(), + data_notification: DataNotification::default(), favorite_notification: FavoriteNotification::default(), } } @@ -26,54 +25,16 @@ impl Default for Notifications { /// Enum representing the possible notifications. #[derive(Debug, Clone, Copy)] pub enum Notification { - /// Packets notification - Packets(PacketsNotification), - /// Bytes notification - Bytes(BytesNotification), + /// Data notification + Data(DataNotification), /// Favorites notification Favorite(FavoriteNotification), } #[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Copy)] -pub struct PacketsNotification { - /// Threshold of received + sent packets; if exceeded a notification is emitted - pub threshold: Option, - /// The sound to emit - pub sound: Sound, - /// The last used Some value for the threshold field - pub previous_threshold: u64, -} - -impl Default for PacketsNotification { - fn default() -> Self { - PacketsNotification { - threshold: None, - sound: Sound::Gulp, - previous_threshold: 750, - } - } -} - -impl PacketsNotification { - /// Arbitrary string constructor. Will fallback values to existing notification if set, or default otherwise - pub fn from(value: &str, existing: Option) -> Self { - let default = existing.unwrap_or_default(); - - let new_threshold = if value.is_empty() { - 0 - } else { - value.parse().unwrap_or(default.previous_threshold) - }; - Self { - threshold: Some(new_threshold), - previous_threshold: new_threshold, - ..default - } - } -} - -#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Copy)] -pub struct BytesNotification { +pub struct DataNotification { + /// Data representation + pub chart_type: ChartType, /// Threshold of received + sent bytes; if exceeded a notification is emitted pub threshold: Option, /// B, KB, MB or GB @@ -84,9 +45,10 @@ pub struct BytesNotification { pub previous_threshold: u64, } -impl Default for BytesNotification { +impl Default for DataNotification { fn default() -> Self { - BytesNotification { + DataNotification { + chart_type: ChartType::Bytes, threshold: None, byte_multiple: ByteMultiple::KB, sound: Sound::Pop, @@ -95,7 +57,7 @@ impl Default for BytesNotification { } } -impl BytesNotification { +impl DataNotification { /// Arbitrary string constructor. Will fallback values to existing notification if set, or default otherwise pub fn from(value: &str, existing: Option) -> Self { let default = existing.unwrap_or_default(); @@ -181,42 +143,42 @@ mod tests { #[rstest] #[case("123", - BytesNotification{ - previous_threshold: 123, threshold: Some(123), byte_multiple: ByteMultiple::B, ..BytesNotification::default() } + DataNotification{ + previous_threshold: 123, threshold: Some(123), byte_multiple: ByteMultiple::B, ..DataNotification::default() } )] #[case("500k", - BytesNotification{ - previous_threshold: 500_000, threshold: Some(500_000),byte_multiple: ByteMultiple::KB, ..BytesNotification::default() } + DataNotification{ + previous_threshold: 500_000, threshold: Some(500_000),byte_multiple: ByteMultiple::KB, ..DataNotification::default() } )] #[case("420m", - BytesNotification{ - previous_threshold: 420_000_000, threshold: Some(420_000_000),byte_multiple: ByteMultiple::MB, ..BytesNotification::default() } + DataNotification{ + previous_threshold: 420_000_000, threshold: Some(420_000_000),byte_multiple: ByteMultiple::MB, ..DataNotification::default() } )] #[case("744ь", - BytesNotification{ - previous_threshold: 744, threshold: Some(744),byte_multiple: ByteMultiple::B, ..BytesNotification::default() } + DataNotification{ + previous_threshold: 744, threshold: Some(744),byte_multiple: ByteMultiple::B, ..DataNotification::default() } )] #[case("888g", - BytesNotification{ - previous_threshold: 888_000_000_000, threshold: Some(888_000_000_000),byte_multiple: ByteMultiple::GB, ..BytesNotification::default() } + DataNotification{ + previous_threshold: 888_000_000_000, threshold: Some(888_000_000_000),byte_multiple: ByteMultiple::GB, ..DataNotification::default() } )] fn test_can_instantiate_bytes_notification_from_string( #[case] input: &str, - #[case] expected: BytesNotification, + #[case] expected: DataNotification, ) { - assert_eq!(expected, BytesNotification::from(input, None)); + assert_eq!(expected, DataNotification::from(input, None)); } #[rstest] #[case("foob@r")] #[case("2O6")] fn test_will_reuse_previous_value_if_cannot_parse(#[case] input: &str) { - let existing_notification = BytesNotification { + let existing_notification = DataNotification { previous_threshold: 420_000_000_000, byte_multiple: ByteMultiple::GB, ..Default::default() }; - let expected = BytesNotification { + let expected = DataNotification { previous_threshold: 420_000_000_000, threshold: Some(420_000_000_000), byte_multiple: ByteMultiple::GB, @@ -224,7 +186,7 @@ mod tests { }; assert_eq!( expected, - BytesNotification::from(input, Some(existing_notification)) + DataNotification::from(input, Some(existing_notification)) ); } @@ -259,26 +221,4 @@ mod tests { } ); } - - #[rstest] - #[case("123", PacketsNotification{ - previous_threshold: 123, - threshold: Some(123), - ..PacketsNotification::default() })] - #[case("8888", PacketsNotification{ - previous_threshold: 8888, - threshold: Some(8888), - ..PacketsNotification::default() })] - #[case("420 m", PacketsNotification{ - threshold: Some(750), - ..PacketsNotification::default() })] - #[case("foob@r", PacketsNotification{ - threshold: Some(750), - ..PacketsNotification::default() })] - fn test_can_instantiate_packet_notification_from_string( - #[case] input: &str, - #[case] expected: PacketsNotification, - ) { - assert_eq!(expected, PacketsNotification::from(input, None)); - } } diff --git a/src/notifications/types/sound.rs b/src/notifications/types/sound.rs index c98c9bc02..1303ccdfe 100644 --- a/src/notifications/types/sound.rs +++ b/src/notifications/types/sound.rs @@ -1,8 +1,8 @@ use std::fmt; use std::thread; +use iced::Font; use iced::widget::Text; -use iced::{Alignment, Font, Length}; use rodio::{Decoder, OutputStream, Sink}; use serde::{Deserialize, Serialize}; @@ -51,9 +51,6 @@ impl Sound { Sound::None => Icon::Forbidden.to_text(), } .size(FONT_SIZE_FOOTER) - .width(Length::Fill) - .align_x(Alignment::Center) - .align_y(Alignment::Center) } } diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index 797fc6f1f..c40e30f95 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -71,3 +71,11 @@ pub fn reading_from_pcap_translation<'a>(language: Language, file: &str) -> Text ), }) } + +pub fn data_exceeded_translation(language: Language) -> &'static str { + match language { + Language::EN => "Data threshold exceeded", + Language::IT => "Soglia di dati superata", + _ => "Data threshold exceeded", + } +} diff --git a/src/utils/types/icon.rs b/src/utils/types/icon.rs index f37d610a1..cb708e0e8 100644 --- a/src/utils/types/icon.rs +++ b/src/utils/types/icon.rs @@ -63,7 +63,7 @@ impl Icon { Icon::AudioHigh => 'Z', Icon::AudioMute => 'Y', Icon::Bin => 'h', - Icon::BytesThreshold => 'f', + Icon::BytesThreshold => '[', Icon::Clock => '9', Icon::Generals => 'Q', Icon::Error => 'U', @@ -81,7 +81,7 @@ impl Icon { Icon::Moon => 'G', Icon::Notification => '7', Icon::Overview => 'd', - Icon::PacketsThreshold => 'e', + Icon::PacketsThreshold => '\\', // Icon::Restore => 'k', Icon::Rocket => 'S', Icon::Settings => 'a', From 2a19e59c24d4b802a3bcca14f5bdb519c3514922 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Tue, 24 Jun 2025 03:09:45 +0200 Subject: [PATCH 18/19] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca32c718e..d61f69b3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All Sniffnet releases with the relative changes are documented in this file. ## [UNRELEASED] - Import PCAP files ([#795](https://github.com/GyulyVGC/sniffnet/pull/795) — fixes [#283](https://github.com/GyulyVGC/sniffnet/issues/283)) - Donut chart reporting overall traffic statistics ([#756](https://github.com/GyulyVGC/sniffnet/pull/756) — fixes [#687](https://github.com/GyulyVGC/sniffnet/issues/687)) +- Notifications: more details and other improvements ([#830](https://github.com/GyulyVGC/sniffnet/pull/830) — fixes [#637](https://github.com/GyulyVGC/sniffnet/issues/637)) - Added support for ARP protocol ([#759](https://github.com/GyulyVGC/sniffnet/pull/759) — fixes [#680](https://github.com/GyulyVGC/sniffnet/issues/680)) - Identify and tag unassigned/reserved "bogon" IP addresses ([#678](https://github.com/GyulyVGC/sniffnet/pull/678) — fixes [#209](https://github.com/GyulyVGC/sniffnet/issues/209)) - Show data agglomerates in _Inspect_ page table ([#684](https://github.com/GyulyVGC/sniffnet/pull/684) — fixes [#601](https://github.com/GyulyVGC/sniffnet/issues/601)) From 0bc93b18399984f73395a852eaa76ac3c1632384 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Tue, 24 Jun 2025 12:03:47 +0200 Subject: [PATCH 19/19] minor improvement --- src/gui/pages/notifications_page.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index 3c602f157..0249de8d3 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -201,7 +201,6 @@ fn data_notification_log<'a>( ) .push(threshold_bar( logged_notification, - chart_type, first_entry_data_info, language, font, @@ -336,11 +335,11 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> fn threshold_bar<'a>( logged_notification: &DataThresholdExceeded, - chart_type: ChartType, first_entry_data_info: DataInfo, language: Language, font: Font, ) -> Row<'a, Message, StyleType> { + let chart_type = logged_notification.chart_type; let data_info = logged_notification.data_info; let (incoming_bar_len, outgoing_bar_len) = get_bars_length(chart_type, &first_entry_data_info, &data_info);