create new Conf struct to accomodate parameters to be persisted (WIP)

This commit is contained in:
GyulyVGC 2025-08-22 00:42:49 +02:00
parent 34dcdc943d
commit 2929bf1bbc
22 changed files with 226 additions and 219 deletions

View file

@ -76,7 +76,7 @@ mod tests {
use crate::gui::styles::types::custom_palette::ExtraStyles;
use crate::gui::styles::types::gradient_type::GradientType;
use crate::notifications::types::notifications::Notifications;
use crate::{ConfigDevice, ConfigSettings, ConfigWindow, Language, Sniffer, StyleType};
use crate::{ConfigDevice, ConfigWindow, Language, Settings, Sniffer, StyleType};
use super::*;
@ -86,7 +86,7 @@ mod tests {
// initial configs stored are the default ones
assert_eq!(Configs::load(), Configs::default());
let modified_configs = Configs {
settings: ConfigSettings {
settings: Settings {
color_gradient: GradientType::Wild,
language: Language::ZH,
scale_factor: 0.65,

View file

@ -1,11 +1,13 @@
use crate::{ConfigDevice, ConfigSettings, ConfigWindow};
use crate::ConfigWindow;
use crate::gui::types::settings::Settings;
use crate::networking::types::config_device::ConfigDevice;
use confy::ConfyError;
pub static CONFIGS: std::sync::LazyLock<Configs> = std::sync::LazyLock::new(Configs::load);
#[derive(Default, Clone, PartialEq, Debug)]
pub struct Configs {
pub settings: ConfigSettings,
pub settings: Settings,
pub device: ConfigDevice,
pub window: ConfigWindow,
}
@ -15,7 +17,7 @@ impl Configs {
/// use `CONFIGS` instead to access the initial instance
pub fn load() -> Self {
Configs {
settings: ConfigSettings::load(),
settings: Settings::load(),
device: ConfigDevice::load(),
window: ConfigWindow::load(),
}

View file

@ -1,4 +1 @@
pub mod config_device;
pub mod config_settings;
pub mod config_window;
pub mod configs;

View file

@ -5,7 +5,6 @@ use iced::widget::tooltip::Position;
use iced::widget::{Container, Row, Space, Text, Tooltip, button, horizontal_space};
use iced::{Alignment, Font, Length};
use crate::configs::types::config_settings::ConfigSettings;
use crate::gui::components::tab::notifications_badge;
use crate::gui::pages::types::running_page::RunningPage;
use crate::gui::pages::types::settings_page::SettingsPage;
@ -14,6 +13,7 @@ use crate::gui::styles::button::ButtonType;
use crate::gui::styles::container::ContainerType;
use crate::gui::styles::types::gradient_type::GradientType;
use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::translations::translations::{quit_analysis_translation, settings_translation};
use crate::translations::translations_3::thumbnail_mode_translation;
use crate::utils::types::icon::Icon;
@ -21,12 +21,12 @@ use crate::{Language, SNIFFNET_TITLECASE, StyleType};
pub fn header(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
let thumbnail = sniffer.thumbnail;
let ConfigSettings {
let Settings {
style,
language,
color_gradient,
..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let font = style.get_extension().font;
if thumbnail {
@ -41,7 +41,7 @@ pub fn header(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
);
}
let last_opened_setting = sniffer.last_opened_setting;
let last_opened_setting = sniffer.conf.last_opened_setting;
let is_running = sniffer.running_page.ne(&RunningPage::Init);
let logo = Icon::Sniffnet

View file

@ -8,6 +8,7 @@ use crate::gui::styles::style_constants::FONT_SIZE_TITLE;
use crate::gui::styles::text::TextType;
use crate::gui::styles::types::gradient_type::GradientType;
use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::gui::types::timing_events::TimingEvents;
use crate::networking::manage_packets::{
get_address_to_lookup, get_traffic_type, is_local_connection, is_my_address,
@ -34,7 +35,7 @@ use crate::translations::translations_3::{
};
use crate::utils::formatted_strings::{get_formatted_timestamp, get_socket_address};
use crate::utils::types::icon::Icon;
use crate::{ConfigSettings, Language, Protocol, Sniffer, StyleType};
use crate::{Language, Protocol, Sniffer, StyleType};
use iced::alignment::Vertical;
use iced::widget::scrollable::Direction;
use iced::widget::tooltip::Position;
@ -50,12 +51,12 @@ pub fn connection_details_page(
}
fn page_content<'a>(sniffer: &Sniffer, key: &AddressPortPair) -> Container<'a, Message, StyleType> {
let ConfigSettings {
let Settings {
style,
language,
color_gradient,
..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let data_repr = sniffer.traffic_chart.data_repr;
let font = style.get_extension().font;
let font_headers = style.get_extension().font_headers;
@ -299,9 +300,9 @@ fn get_local_tooltip<'a>(
address_to_lookup: &IpAddr,
key: &AddressPortPair,
) -> Tooltip<'a, Message, StyleType> {
let ConfigSettings {
let Settings {
style, language, ..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let local_address = if address_to_lookup.eq(&key.address1) {
&key.address2

View file

@ -15,6 +15,7 @@ use crate::gui::styles::types::gradient_type::GradientType;
use crate::gui::types::export_pcap::ExportPcap;
use crate::gui::types::filters::Filters;
use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::networking::types::capture_context::{CaptureSource, CaptureSourcePicklist};
use crate::translations::translations::{
address_translation, addresses_translation, network_adapter_translation, start_translation,
@ -27,7 +28,7 @@ use crate::translations::translations_5::{filter_traffic_translation, traffic_so
use crate::utils::formatted_strings::get_path_termination_string;
use crate::utils::types::file_info::FileInfo;
use crate::utils::types::icon::Icon;
use crate::{ConfigSettings, Language, StyleType};
use crate::{Language, StyleType};
use iced::Length::FillPortion;
use iced::widget::scrollable::Direction;
use iced::widget::{
@ -38,12 +39,12 @@ use iced::{Alignment, Font, Length, Padding, alignment};
/// Computes the body of gui initial page
pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
let ConfigSettings {
let Settings {
style,
language,
color_gradient,
..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let font = style.get_extension().font;
let font_headers = style.get_extension().font_headers;
@ -51,10 +52,10 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
let col_checkboxes = Column::new()
.spacing(10)
.push(get_filters_group(&sniffer.filters, font, language))
.push(get_filters_group(&sniffer.conf.filters, font, language))
.push_maybe(get_export_pcap_group_maybe(
sniffer.capture_source_picklist,
&sniffer.export_pcap,
sniffer.conf.capture_source_picklist,
&sniffer.conf.export_pcap,
language,
font,
));
@ -113,7 +114,7 @@ fn get_col_data_source(
font: Font,
language: Language,
) -> Column<'_, Message, StyleType> {
let current_option = if sniffer.capture_source_picklist == CaptureSourcePicklist::Device {
let current_option = if sniffer.conf.capture_source_picklist == CaptureSourcePicklist::Device {
network_adapter_translation(language)
} else {
capture_file_translation(language)
@ -153,7 +154,7 @@ fn get_col_data_source(
.push(picklist),
);
match &sniffer.capture_source_picklist {
match &sniffer.conf.capture_source_picklist {
CaptureSourcePicklist::Device => {
col = col.push(get_col_adapter(sniffer, font, language));
}
@ -162,7 +163,7 @@ fn get_col_data_source(
language,
font,
&sniffer.capture_source,
&sniffer.import_pcap_path,
&sniffer.conf.import_pcap_path,
));
}
}

View file

@ -21,6 +21,7 @@ use crate::gui::styles::style_constants::{FONT_SIZE_FOOTER, FONT_SIZE_SUBTITLE,
use crate::gui::styles::text::TextType;
use crate::gui::styles::text_input::TextInputType;
use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::networking::types::address_port_pair::AddressPortPair;
use crate::networking::types::data_info::DataInfo;
use crate::networking::types::data_representation::DataRepr;
@ -36,13 +37,13 @@ use crate::translations::translations_2::{
};
use crate::translations::translations_3::filter_by_host_translation;
use crate::utils::types::icon::Icon;
use crate::{ConfigSettings, Language, ReportSortType, RunningPage, Sniffer, StyleType};
use crate::{Language, ReportSortType, RunningPage, Sniffer, StyleType};
/// Computes the body of gui inspect page
pub fn inspect_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
let ConfigSettings {
let Settings {
style, language, ..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let font = style.get_extension().font;
let font_headers = style.get_extension().font_headers;
@ -74,7 +75,7 @@ pub fn inspect_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
language,
&sniffer.search,
font,
sniffer.report_sort_type,
sniffer.conf.report_sort_type,
sniffer.traffic_chart.data_repr,
))
.push(Space::with_height(4))
@ -105,9 +106,9 @@ pub fn inspect_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
}
fn report<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> {
let ConfigSettings {
let Settings {
style, language, ..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let data_repr = sniffer.traffic_chart.data_repr;
let font = style.get_extension().font;

View file

@ -10,6 +10,7 @@ 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::gui::types::settings::Settings;
use crate::networking::types::data_info::DataInfo;
use crate::networking::types::data_info_host::DataInfoHost;
use crate::networking::types::data_representation::DataRepr;
@ -26,7 +27,7 @@ use crate::translations::translations::{
threshold_translation,
};
use crate::utils::types::icon::Icon;
use crate::{ConfigSettings, Language, RunningPage, Sniffer, StyleType};
use crate::{Language, RunningPage, Sniffer, StyleType};
use iced::Length::FillPortion;
use iced::widget::scrollable::Direction;
use iced::widget::text::LineHeight;
@ -38,12 +39,12 @@ use std::cmp::max;
/// Computes the body of gui notifications page
pub fn notifications_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
let ConfigSettings {
let Settings {
style,
language,
notifications,
..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let font = style.get_extension().font;
let font_headers = style.get_extension().font_headers;
@ -282,9 +283,9 @@ fn get_button_clear_all<'a>(font: Font, language: Language) -> Tooltip<'a, Messa
}
fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> {
let ConfigSettings {
let Settings {
style, language, ..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let data_repr = sniffer.traffic_chart.data_repr;
let font = style.get_extension().font;
let mut ret_val = Column::new()

View file

@ -17,6 +17,7 @@ use crate::gui::styles::text::TextType;
use crate::gui::styles::types::palette_extension::PaletteExtension;
use crate::gui::types::filters::Filters;
use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::networking::types::capture_context::CaptureSource;
use crate::networking::types::data_info::DataInfo;
use crate::networking::types::data_info_host::DataInfoHost;
@ -37,7 +38,7 @@ use crate::translations::translations_2::{
use crate::translations::translations_3::{service_translation, unsupported_link_type_translation};
use crate::translations::translations_4::reading_from_pcap_translation;
use crate::utils::types::icon::Icon;
use crate::{ConfigSettings, Language, RunningPage, StyleType};
use crate::{Language, RunningPage, StyleType};
use iced::Length::{Fill, FillPortion};
use iced::alignment::{Horizontal, Vertical};
use iced::widget::scrollable::Direction;
@ -52,9 +53,9 @@ use std::fmt::Write;
/// Computes the body of gui overview page
pub fn overview_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
let ConfigSettings {
let Settings {
style, language, ..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let font = style.get_extension().font;
let font_headers = style.get_extension().font_headers;
@ -210,16 +211,20 @@ fn row_report<'a>(sniffer: &Sniffer) -> Row<'a, Message, StyleType> {
}
fn col_host<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> {
let ConfigSettings {
let Settings {
style, language, ..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let font = style.get_extension().font;
let data_repr = sniffer.traffic_chart.data_repr;
let mut scroll_host = Column::new()
.padding(Padding::ZERO.right(11.0))
.align_x(Alignment::Center);
let entries = get_host_entries(&sniffer.info_traffic, data_repr, sniffer.host_sort_type);
let entries = get_host_entries(
&sniffer.info_traffic,
data_repr,
sniffer.conf.host_sort_type,
);
let first_entry_data_info = entries
.iter()
.map(|(_, d)| d.data_info)
@ -273,7 +278,7 @@ fn col_host<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> {
)
.push(horizontal_space())
.push(sort_arrows(
sniffer.host_sort_type,
sniffer.conf.host_sort_type,
Message::HostSortSelection,
)),
)
@ -287,16 +292,20 @@ fn col_host<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> {
}
fn col_service<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> {
let ConfigSettings {
let Settings {
style, language, ..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let font = style.get_extension().font;
let data_repr = sniffer.traffic_chart.data_repr;
let mut scroll_service = Column::new()
.padding(Padding::ZERO.right(11.0))
.align_x(Alignment::Center);
let entries = get_service_entries(&sniffer.info_traffic, data_repr, sniffer.service_sort_type);
let entries = get_service_entries(
&sniffer.info_traffic,
data_repr,
sniffer.conf.service_sort_type,
);
let first_entry_data_info = entries
.iter()
.map(|&(_, d)| d)
@ -337,7 +346,7 @@ fn col_service<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> {
)
.push(horizontal_space())
.push(sort_arrows(
sniffer.service_sort_type,
sniffer.conf.service_sort_type,
Message::ServiceSortSelection,
)),
)
@ -430,12 +439,17 @@ pub fn service_bar<'a>(
}
fn col_info(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
let ConfigSettings {
let Settings {
style, language, ..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let PaletteExtension { font, .. } = style.get_extension();
let col_device = col_device(language, font, &sniffer.capture_source, &sniffer.filters);
let col_device = col_device(
language,
font,
&sniffer.capture_source,
&sniffer.conf.filters,
);
let col_data_representation =
col_data_representation(language, font, sniffer.traffic_chart.data_repr);
@ -469,7 +483,7 @@ fn col_info(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
}
fn container_chart(sniffer: &Sniffer, font: Font) -> Container<'_, Message, StyleType> {
let ConfigSettings { language, .. } = sniffer.configs.settings;
let Settings { language, .. } = sniffer.conf.settings;
let traffic_chart = &sniffer.traffic_chart;
Container::new(

View file

@ -14,6 +14,7 @@ use crate::gui::styles::container::ContainerType;
use crate::gui::styles::style_constants::FONT_SIZE_SUBTITLE;
use crate::gui::styles::text::TextType;
use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::mmdb::types::mmdb_reader::{MmdbReader, MmdbReaders};
use crate::translations::translations::language_translation;
use crate::translations::translations_2::country_translation;
@ -25,15 +26,15 @@ use crate::utils::formatted_strings::get_path_termination_string;
use crate::utils::types::file_info::FileInfo;
use crate::utils::types::icon::Icon;
use crate::utils::types::web_page::WebPage;
use crate::{ConfigSettings, Language, RunningPage, Sniffer, StyleType};
use crate::{Language, RunningPage, Sniffer, StyleType};
pub fn settings_general_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
let ConfigSettings {
let Settings {
style,
language,
color_gradient,
..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let font = style.get_extension().font;
let font_headers = style.get_extension().font_headers;
@ -57,13 +58,13 @@ pub fn settings_general_page(sniffer: &Sniffer) -> Container<'_, Message, StyleT
}
fn column_all_general_setting(sniffer: &Sniffer, font: Font) -> Column<'_, Message, StyleType> {
let ConfigSettings {
let Settings {
language,
scale_factor,
mmdb_country,
mmdb_asn,
..
} = sniffer.configs.settings.clone();
} = sniffer.conf.settings.clone();
let is_editable = sniffer.running_page.eq(&RunningPage::Init);

View file

@ -13,6 +13,7 @@ use crate::gui::styles::style_constants::{FONT_SIZE_FOOTER, FONT_SIZE_SUBTITLE,
use crate::gui::styles::text::TextType;
use crate::gui::styles::types::gradient_type::GradientType;
use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::networking::types::data_representation::DataRepr;
use crate::notifications::types::notifications::{
DataNotification, FavoriteNotification, Notification,
@ -25,16 +26,16 @@ use crate::translations::translations::{
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};
use crate::{Language, Sniffer, StyleType};
pub fn settings_notifications_page<'a>(sniffer: &Sniffer) -> Container<'a, Message, StyleType> {
let ConfigSettings {
let Settings {
style,
language,
color_gradient,
mut notifications,
..
} = sniffer.configs.settings;
} = sniffer.conf.settings;
let font = style.get_extension().font;
let font_headers = style.get_extension().font_headers;

View file

@ -19,22 +19,23 @@ use crate::gui::styles::types::gradient_type::GradientType;
use crate::gui::styles::types::palette::Palette;
use crate::gui::styles::types::palette_extension::PaletteExtension;
use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::translations::translations::appearance_title_translation;
use crate::translations::translations_2::color_gradients_translation;
use crate::translations::translations_3::custom_style_translation;
use crate::utils::formatted_strings::get_path_termination_string;
use crate::utils::types::file_info::FileInfo;
use crate::utils::types::icon::Icon;
use crate::{ConfigSettings, Language, Sniffer, StyleType};
use crate::{Language, Sniffer, StyleType};
pub fn settings_style_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
let ConfigSettings {
let Settings {
style,
language,
color_gradient,
style_path,
..
} = sniffer.configs.settings.clone();
} = sniffer.conf.settings.clone();
let PaletteExtension {
font, font_headers, ..
} = style.get_extension();

View file

@ -5,12 +5,12 @@ use iced::widget::{Column, Container, Row, Rule, Space, Text, vertical_space};
use iced::{Alignment, Font, Length};
use crate::chart::types::donut_chart::donut_chart;
use crate::configs::types::config_settings::ConfigSettings;
use crate::countries::country_utils::get_flag_tooltip;
use crate::gui::sniffer::Sniffer;
use crate::gui::styles::style_constants::FONT_SIZE_FOOTER;
use crate::gui::styles::types::style_type::StyleType;
use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::networking::types::data_representation::DataRepr;
use crate::networking::types::host::{Host, ThumbnailHost};
use crate::networking::types::info_traffic::InfoTraffic;
@ -24,7 +24,7 @@ const MAX_CHARS_SERVICE: usize = 13;
/// Computes the body of the thumbnail view
pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
let ConfigSettings { style, .. } = sniffer.configs.settings;
let Settings { style, .. } = sniffer.conf.settings;
let font = style.get_extension().font;
let tot_packets = sniffer
@ -75,14 +75,14 @@ pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
info_traffic,
data_repr,
font,
sniffer.host_sort_type,
sniffer.conf.host_sort_type,
))
.push(Rule::vertical(10))
.push(service_col(
info_traffic,
data_repr,
font,
sniffer.service_sort_type,
sniffer.conf.service_sort_type,
));
let content = Column::new()

View file

@ -17,9 +17,6 @@ use std::sync::Arc;
use std::thread;
use std::time::Duration;
use crate::configs::types::config_window::{
ConfigWindow, PositionTuple, ScaleAndCheck, SizeTuple, ToPoint, ToSize,
};
use crate::gui::components::footer::footer;
use crate::gui::components::header::header;
use crate::gui::components::modal::{get_clear_all_overlay, get_exit_overlay, modal};
@ -37,9 +34,12 @@ use crate::gui::pages::types::running_page::RunningPage;
use crate::gui::pages::types::settings_page::SettingsPage;
use crate::gui::styles::types::custom_palette::{CustomPalette, ExtraStyles};
use crate::gui::styles::types::palette::Palette;
use crate::gui::types::export_pcap::ExportPcap;
use crate::gui::types::filters::Filters;
use crate::gui::types::conf::Conf;
use crate::gui::types::config_window::{
ConfigWindow, PositionTuple, ScaleAndCheck, SizeTuple, ToPoint, ToSize,
};
use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::gui::types::timing_events::TimingEvents;
use crate::mmdb::asn::ASN_MMDB;
use crate::mmdb::country::COUNTRY_MMDB;
@ -59,25 +59,23 @@ use crate::notifications::types::logged_notification::LoggedNotification;
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;
use crate::report::types::search_parameters::SearchParameters;
use crate::report::types::sort_type::SortType;
use crate::translations::types::language::Language;
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, StyleType, TrafficChart, location};
use crate::{StyleType, TrafficChart, location};
pub const FONT_FAMILY_NAME: &str = "Sarasa Mono SC for Sniffnet";
pub const ICON_FONT_FAMILY_NAME: &str = "Icons for Sniffnet";
/// Struct on which the gui is based
/// Struct on which the GUI is based
///
/// It contains gui statuses and network traffic statistics
/// It carries statuses, network traffic statistics, and more
pub struct Sniffer {
/// Application's configurations: settings, window properties, name of last device sniffed
pub configs: Configs,
/// Parameters that are persistent across runs
pub conf: Conf,
/// Capture receiver clone (to close the channel after every run), with the current capture id (to ignore pending messages from previous captures)
pub current_capture_rx: (usize, Option<Receiver<BackendTrafficMessage>>),
/// Capture data
@ -90,32 +88,21 @@ pub struct Sniffer {
pub logged_notifications: (VecDeque<LoggedNotification>, usize),
/// Reports if a newer release of the software is available on GitHub
pub newer_release_available: Option<bool>,
/// Capture source picklist, to select the source of the capture
pub capture_source_picklist: CaptureSourcePicklist,
/// Network device to be analyzed, or PCAP file to be imported
/// TODO: Conf???
pub capture_source: CaptureSource,
/// List of network devices
pub my_devices: Vec<MyDevice>,
/// BPF filter program to be applied to the capture
pub filters: Filters,
/// Signals if a pcap error occurred
pub pcap_error: Option<String>,
/// Messages status
pub dots_pulse: (String, u8),
/// Chart displayed
pub traffic_chart: TrafficChart,
/// Report sort type (inspect page)
pub report_sort_type: ReportSortType,
/// Host sort type (overview page)
pub host_sort_type: SortType,
/// Service sort type (overview page)
pub service_sort_type: SortType,
/// Currently displayed modal; None if no modal is displayed
pub modal: Option<MyModal>,
/// Currently displayed settings page; None if settings is closed
pub settings_page: Option<SettingsPage>,
/// Remembers the last opened setting page
pub last_opened_setting: SettingsPage,
/// Defines the current running page
pub running_page: RunningPage,
/// Number of unread notifications
@ -128,49 +115,39 @@ pub struct Sniffer {
pub mmdb_readers: MmdbReaders,
/// Time-related events
pub timing_events: TimingEvents,
/// Information about PCAP file export
pub export_pcap: ExportPcap,
/// Whether thumbnail mode is currently active
pub thumbnail: bool,
/// Window id
pub id: Option<Id>,
/// Host data for filter dropdowns (comboboxes)
pub host_data_states: HostDataStates,
/// Import path for PCAP file
pub import_pcap_path: String,
}
impl Sniffer {
pub fn new(configs: Configs) -> Self {
let ConfigSettings {
pub fn new(conf: Conf) -> Self {
let Settings {
style,
language,
mmdb_country,
mmdb_asn,
..
} = configs.settings.clone();
let device = configs.device.to_my_device();
} = conf.settings.clone();
let device = conf.device.to_my_device();
Self {
configs,
conf,
current_capture_rx: (0, None),
info_traffic: InfoTraffic::default(),
addresses_resolved: HashMap::new(),
favorite_hosts: HashSet::new(),
logged_notifications: (VecDeque::new(), 0),
newer_release_available: None,
capture_source_picklist: CaptureSourcePicklist::default(),
capture_source: CaptureSource::Device(device),
my_devices: Vec::new(),
filters: Filters::default(),
pcap_error: None,
dots_pulse: (".".to_string(), 0),
traffic_chart: TrafficChart::new(style, language),
report_sort_type: ReportSortType::default(),
host_sort_type: SortType::default(),
service_sort_type: SortType::default(),
modal: None,
settings_page: None,
last_opened_setting: SettingsPage::Notifications,
running_page: RunningPage::Init,
unread_notifications: 0,
search: SearchParameters::default(),
@ -180,11 +157,9 @@ impl Sniffer {
asn: Arc::new(MmdbReader::from(&mmdb_asn, ASN_MMDB)),
},
timing_events: TimingEvents::default(),
export_pcap: ExportPcap::default(),
thumbnail: false,
id: None,
host_data_states: HostDataStates::default(),
import_pcap_path: String::new(),
}
}
@ -283,25 +258,25 @@ impl Sniffer {
}
Message::DeviceSelection(name) => self.set_device(&name),
Message::SetCaptureSource(cs_pick) => {
self.capture_source_picklist = cs_pick;
self.conf.capture_source_picklist = cs_pick;
return if cs_pick == CaptureSourcePicklist::File {
Task::done(Message::SetPcapImport(self.import_pcap_path.clone()))
Task::done(Message::SetPcapImport(self.conf.import_pcap_path.clone()))
} else {
Task::done(Message::DeviceSelection(
self.configs.device.device_name.clone(),
self.conf.device.device_name.clone(),
))
};
}
Message::ToggleFilters => {
self.filters.toggle();
self.conf.filters.toggle();
}
Message::BpfFilter(value) => {
self.filters.set_bpf(value);
self.conf.filters.set_bpf(value);
}
Message::DataReprSelection(unit) => self.traffic_chart.change_kind(unit),
Message::ReportSortSelection(sort) => {
self.page_number = 1;
self.report_sort_type = sort;
self.conf.report_sort_type = sort;
}
Message::OpenWebPage(web_page) => Self::open_web(&web_page),
Message::Start => {
@ -311,16 +286,16 @@ impl Sniffer {
}
Message::Reset => self.reset(),
Message::Style(style) => {
self.configs.settings.style = style;
self.conf.settings.style = style;
self.traffic_chart.change_style(style);
}
Message::LoadStyle(path) => {
self.configs.settings.style_path.clone_from(&path);
self.conf.settings.style_path.clone_from(&path);
if let Ok(palette) = Palette::from_file(path) {
let style = StyleType::Custom(ExtraStyles::CustomToml(
CustomPalette::from_palette(palette),
));
self.configs.settings.style = style;
self.conf.settings.style = style;
self.traffic_chart.change_style(style);
}
}
@ -338,7 +313,7 @@ impl Sniffer {
}
Message::OpenLastSettings => {
if self.modal.is_none() && self.settings_page.is_none() {
self.settings_page = Some(self.last_opened_setting);
self.settings_page = Some(self.conf.last_opened_setting);
}
}
Message::CloseSettings => self.close_settings(),
@ -349,7 +324,7 @@ impl Sniffer {
}
}
Message::LanguageSelection(language) => {
self.configs.settings.language = language;
self.conf.settings.language = language;
self.traffic_chart.change_language(language);
}
Message::UpdateNotificationSettings(notification, emit_sound) => {
@ -357,7 +332,7 @@ impl Sniffer {
}
Message::ChangeVolume(volume) => {
play(Sound::Pop, volume);
self.configs.settings.notifications.volume = volume;
self.conf.settings.notifications.volume = volume;
}
Message::ClearAllNotifications => {
self.logged_notifications.0 = VecDeque::new();
@ -405,41 +380,40 @@ impl Sniffer {
}
Message::WindowFocused => self.timing_events.focus_now(),
Message::GradientsSelection(gradient_type) => {
self.configs.settings.color_gradient = gradient_type;
self.conf.settings.color_gradient = gradient_type;
}
Message::ChangeScaleFactor(slider_val) => {
let scale_factor_str = format!("{:.1}", 3.0_f64.powf(slider_val));
self.configs.settings.scale_factor = scale_factor_str.parse().unwrap_or(1.0);
self.conf.settings.scale_factor = scale_factor_str.parse().unwrap_or(1.0);
}
Message::WindowMoved(x, y) => {
let scale_factor = self.configs.settings.scale_factor;
let scale_factor = self.conf.settings.scale_factor;
let scaled = PositionTuple(x, y).scale_and_check(scale_factor);
if self.thumbnail {
self.configs.window.thumbnail_position = scaled;
self.conf.window.thumbnail_position = scaled;
} else {
self.configs.window.position = scaled;
self.conf.window.position = scaled;
}
}
Message::WindowResized(width, height) => {
if !self.thumbnail {
let scale_factor = self.configs.settings.scale_factor;
self.configs.window.size =
SizeTuple(width, height).scale_and_check(scale_factor);
let scale_factor = self.conf.settings.scale_factor;
self.conf.window.size = SizeTuple(width, height).scale_and_check(scale_factor);
} else if !self.timing_events.was_just_thumbnail_enter() {
return Task::done(Message::ToggleThumbnail(true));
}
}
Message::CustomCountryDb(db) => {
self.configs.settings.mmdb_country.clone_from(&db);
self.conf.settings.mmdb_country.clone_from(&db);
self.mmdb_readers.country = Arc::new(MmdbReader::from(&db, COUNTRY_MMDB));
}
Message::CustomAsnDb(db) => {
self.configs.settings.mmdb_asn.clone_from(&db);
self.conf.settings.mmdb_asn.clone_from(&db);
self.mmdb_readers.asn = Arc::new(MmdbReader::from(&db, ASN_MMDB));
}
Message::QuitWrapper => return self.quit_wrapper(),
Message::Quit => {
let _ = self.configs.clone().store();
let _ = self.conf.clone().store();
return window::close(self.id.unwrap_or_else(Id::unique));
}
Message::CopyIp(ip) => {
@ -448,24 +422,24 @@ impl Sniffer {
}
Message::OpenFile(old_file, file_info, consumer_message) => {
return Task::perform(
Self::open_file(old_file, file_info, self.configs.settings.language),
Self::open_file(old_file, file_info, self.conf.settings.language),
consumer_message,
);
}
Message::HostSortSelection(sort_type) => {
self.host_sort_type = sort_type;
self.conf.host_sort_type = sort_type;
}
Message::ServiceSortSelection(sort_type) => {
self.service_sort_type = sort_type;
self.conf.service_sort_type = sort_type;
}
Message::ToggleExportPcap => {
self.export_pcap.toggle();
self.conf.export_pcap.toggle();
}
Message::OutputPcapDir(path) => {
self.export_pcap.set_directory(path);
self.conf.export_pcap.set_directory(path);
}
Message::OutputPcapFile(name) => {
self.export_pcap.set_file_name(&name);
self.conf.export_pcap.set_file_name(&name);
}
Message::ToggleThumbnail(triggered_by_resize) => {
let window_id = self.id.unwrap_or_else(Id::unique);
@ -474,9 +448,9 @@ impl Sniffer {
self.traffic_chart.thumbnail = self.thumbnail;
return if self.thumbnail {
let scale_factor = self.configs.settings.scale_factor;
let scale_factor = self.conf.settings.scale_factor;
let size = ConfigWindow::thumbnail_size(scale_factor).to_size();
let position = self.configs.window.thumbnail_position;
let position = self.conf.window.thumbnail_position;
self.timing_events.thumbnail_enter_now();
Task::batch([
window::maximize(window_id, false),
@ -494,8 +468,8 @@ impl Sniffer {
window::change_level(window_id, Level::Normal),
];
if !triggered_by_resize {
let size = self.configs.window.size.to_size();
let position = self.configs.window.position.to_point();
let size = self.conf.window.size.to_size();
let position = self.conf.window.position.to_point();
commands.push(window::move_to(window_id, position));
commands.push(window::resize(window_id, size));
}
@ -519,16 +493,16 @@ impl Sniffer {
}
}
Message::ScaleFactorShortcut(increase) => {
let scale_factor = self.configs.settings.scale_factor;
let scale_factor = self.conf.settings.scale_factor;
if !(scale_factor > 2.99 && increase || scale_factor < 0.31 && !increase) {
let delta = if increase { 0.1 } else { -0.1 };
self.configs.settings.scale_factor += delta;
self.conf.settings.scale_factor += delta;
}
}
Message::SetNewerReleaseStatus(status) => self.newer_release_available = status,
Message::SetPcapImport(path) => {
if !path.is_empty() {
self.import_pcap_path.clone_from(&path);
self.conf.import_pcap_path.clone_from(&path);
self.capture_source = CaptureSource::File(MyPcapImport::new(path));
}
}
@ -564,12 +538,12 @@ impl Sniffer {
}
pub fn view(&self) -> Element<'_, Message, StyleType> {
let ConfigSettings {
let Settings {
style,
language,
color_gradient,
..
} = self.configs.settings;
} = self.conf.settings;
let font = style.get_extension().font;
let font_headers = style.get_extension().font_headers;
@ -652,11 +626,11 @@ impl Sniffer {
}
pub fn theme(&self) -> StyleType {
self.configs.settings.style
self.conf.settings.style
}
pub fn scale_factor(&self) -> f64 {
self.configs.settings.scale_factor
self.conf.settings.scale_factor
}
/// Updates threshold if it hasn't been edited for a while
@ -664,17 +638,13 @@ impl Sniffer {
// Ignore if just edited
if let Some(temp_threshold) = self.timing_events.threshold_adjust_expired_take() {
// Apply the temporary threshold to the actual config
self.configs
.settings
.notifications
.data_notification
.threshold = temp_threshold.threshold;
self.configs
self.conf.settings.notifications.data_notification.threshold = temp_threshold.threshold;
self.conf
.settings
.notifications
.data_notification
.byte_multiple = temp_threshold.byte_multiple;
self.configs
self.conf
.settings
.notifications
.data_notification
@ -689,7 +659,7 @@ impl Sniffer {
}
let emitted_notifications = notify_and_log(
&mut self.logged_notifications,
self.configs.settings.notifications,
self.conf.settings.notifications,
&msg,
&self.favorite_hosts,
&self.capture_source,
@ -729,9 +699,9 @@ impl Sniffer {
let current_device_name = &self.capture_source.get_name();
self.set_device(current_device_name);
}
let pcap_path = self.export_pcap.full_path();
let pcap_path = self.conf.export_pcap.full_path();
let capture_context =
CaptureContext::new(&self.capture_source, pcap_path.as_ref(), &self.filters);
CaptureContext::new(&self.capture_source, pcap_path.as_ref(), &self.conf.filters);
self.pcap_error = capture_context.error().map(ToString::to_string);
self.running_page = RunningPage::Overview;
@ -776,9 +746,9 @@ impl Sniffer {
if let Some(rx) = &self.current_capture_rx.1 {
rx.close();
}
let ConfigSettings {
let Settings {
style, language, ..
} = self.configs.settings;
} = self.conf.settings;
// increment capture id to ignore pending messages from previous captures
self.current_capture_rx = (self.current_capture_rx.0 + 1, None);
self.info_traffic = InfoTraffic::default();
@ -787,9 +757,6 @@ impl Sniffer {
self.logged_notifications = (VecDeque::new(), 0);
self.pcap_error = None;
self.traffic_chart = TrafficChart::new(style, language);
self.report_sort_type = ReportSortType::default();
self.host_sort_type = SortType::default();
self.service_sort_type = SortType::default();
self.modal = None;
self.settings_page = None;
self.running_page = RunningPage::Init;
@ -803,7 +770,7 @@ impl Sniffer {
fn set_device(&mut self, name: &str) {
for my_dev in &self.my_devices {
if my_dev.get_name().eq(&name) {
self.configs.device.device_name = name.to_string();
self.conf.device.device_name = name.to_string();
self.capture_source = CaptureSource::Device(my_dev.clone());
break;
}
@ -845,7 +812,7 @@ impl Sniffer {
fn close_settings(&mut self) {
if let Some(page) = self.settings_page {
self.last_opened_setting = page;
self.conf.last_opened_setting = page;
self.settings_page = None;
}
}
@ -855,7 +822,7 @@ impl Sniffer {
/// 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 notifications = self.conf.settings.notifications;
let sound = match notification {
Notification::Data(DataNotification {
data_repr,
@ -879,37 +846,29 @@ impl Sniffer {
self.timing_events.threshold_adjust_now(temp_threshold);
}
if threshold.is_some() != notifications.data_notification.threshold.is_some() {
self.configs
.settings
.notifications
.data_notification
.threshold = threshold;
self.configs
self.conf.settings.notifications.data_notification.threshold = threshold;
self.conf
.settings
.notifications
.data_notification
.byte_multiple = byte_multiple;
self.configs
self.conf
.settings
.notifications
.data_notification
.previous_threshold = previous_threshold;
}
self.configs.settings.notifications.data_notification.sound = sound;
self.configs
.settings
.notifications
.data_notification
.data_repr = data_repr;
self.conf.settings.notifications.data_notification.sound = sound;
self.conf.settings.notifications.data_notification.data_repr = data_repr;
sound
}
Notification::Favorite(favorite_notification) => {
self.configs.settings.notifications.favorite_notification = favorite_notification;
self.conf.settings.notifications.favorite_notification = favorite_notification;
favorite_notification.sound
}
};
if emit_sound {
play(sound, self.configs.settings.notifications.volume);
play(sound, self.conf.settings.notifications.volume);
}
}
@ -918,7 +877,7 @@ impl Sniffer {
if let Some(temp_threshold) = self.timing_events.temp_threshold() {
temp_threshold
} else {
let notifications = self.configs.settings.notifications;
let notifications = self.conf.settings.notifications;
notifications.data_notification
}
}
@ -1083,9 +1042,9 @@ impl Sniffer {
}
pub fn is_capture_source_consistent(&self) -> bool {
self.capture_source_picklist == CaptureSourcePicklist::Device
self.conf.capture_source_picklist == CaptureSourcePicklist::Device
&& matches!(self.capture_source, CaptureSource::Device(_))
|| self.capture_source_picklist == CaptureSourcePicklist::File
|| self.conf.capture_source_picklist == CaptureSourcePicklist::File
&& matches!(self.capture_source, CaptureSource::File(_))
}
}
@ -1122,17 +1081,17 @@ mod tests {
use crate::notifications::types::sound::Sound;
use crate::report::types::sort_type::SortType;
use crate::{
ByteMultiple, ConfigDevice, ConfigSettings, ConfigWindow, Configs, Language,
ReportSortType, RunningPage, Sniffer, StyleType,
ByteMultiple, ConfigDevice, ConfigWindow, Configs, Language, ReportSortType, RunningPage,
Settings, Sniffer, StyleType,
};
// helpful to clean up files generated from tests
impl Drop for Sniffer {
fn drop(&mut self) {
let settings_path_str = ConfigSettings::test_path();
let settings_path_str = Settings::test_path();
let settings_path = Path::new(&settings_path_str);
if settings_path.exists() {
remove_file(ConfigSettings::test_path()).unwrap();
remove_file(Settings::test_path()).unwrap();
}
let device_path_str = ConfigDevice::test_path();
@ -1823,7 +1782,7 @@ mod tests {
#[test]
#[serial] // needed to not collide with other tests generating configs files
fn test_config_settings() {
let path_string = ConfigSettings::test_path();
let path_string = Settings::test_path();
let path = Path::new(&path_string);
assert!(!path.exists());
@ -1836,7 +1795,7 @@ mod tests {
let settings_start = sniffer.configs.settings.clone();
assert_eq!(
settings_start,
ConfigSettings {
Settings {
color_gradient: GradientType::None,
language: Language::EN,
scale_factor: 1.0,
@ -1874,7 +1833,7 @@ mod tests {
let settings_end = Sniffer::new(Configs::load()).configs.settings.clone();
assert_eq!(
settings_end,
ConfigSettings {
Settings {
color_gradient: GradientType::Wild,
language: Language::ZH,
scale_factor: 1.0,

36
src/gui/types/conf.rs Normal file
View file

@ -0,0 +1,36 @@
use crate::gui::pages::types::settings_page::SettingsPage;
use crate::gui::types::config_window::ConfigWindow;
use crate::gui::types::export_pcap::ExportPcap;
use crate::gui::types::filters::Filters;
use crate::gui::types::settings::Settings;
use crate::networking::types::capture_context::CaptureSourcePicklist;
use crate::networking::types::config_device::ConfigDevice;
use crate::report::types::report_sort_type::ReportSortType;
use crate::report::types::sort_type::SortType;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct Conf {
/// Parameters from settings pages
pub settings: Settings,
/// Last selected network device name
pub device: ConfigDevice,
/// Window configuration, such as size and position
pub window: ConfigWindow,
/// Capture source picklist, to select the source of the capture
pub capture_source_picklist: CaptureSourcePicklist,
/// BPF filter program to be applied to the capture
pub filters: Filters,
/// Report sort type (inspect page)
pub report_sort_type: ReportSortType,
/// Host sort type (overview page)
pub host_sort_type: SortType,
/// Service sort type (overview page)
pub service_sort_type: SortType,
/// Remembers the last opened setting page
pub last_opened_setting: SettingsPage,
/// Information about PCAP file export
pub export_pcap: ExportPcap,
/// Import path for PCAP file
pub import_pcap_path: String,
}

View file

@ -1,4 +1,7 @@
pub mod conf;
pub mod config_window;
pub mod export_pcap;
pub mod filters;
pub mod message;
pub mod settings;
pub mod timing_events;

View file

@ -1,6 +1,3 @@
//! Module defining the `ConfigSettings` struct, which allows to save and reload
//! the application default configuration.
use serde::{Deserialize, Serialize};
use crate::gui::styles::types::gradient_type::GradientType;
@ -12,7 +9,7 @@ use crate::{Language, StyleType};
use crate::{SNIFFNET_LOWERCASE, location};
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct ConfigSettings {
pub struct Settings {
pub color_gradient: GradientType,
pub language: Language,
pub scale_factor: f64,
@ -24,21 +21,17 @@ pub struct ConfigSettings {
pub style: StyleType,
}
impl ConfigSettings {
impl Settings {
const FILE_NAME: &'static str = "settings";
#[cfg(not(test))]
pub fn load() -> Self {
if let Ok(settings) = confy::load::<ConfigSettings>(SNIFFNET_LOWERCASE, Self::FILE_NAME) {
if let Ok(settings) = confy::load::<Settings>(SNIFFNET_LOWERCASE, Self::FILE_NAME) {
settings
} else {
let _ = confy::store(
SNIFFNET_LOWERCASE,
Self::FILE_NAME,
ConfigSettings::default(),
)
.log_err(location!());
ConfigSettings::default()
let _ = confy::store(SNIFFNET_LOWERCASE, Self::FILE_NAME, Settings::default())
.log_err(location!());
Settings::default()
}
}
@ -48,9 +41,9 @@ impl ConfigSettings {
}
}
impl Default for ConfigSettings {
impl Default for Settings {
fn default() -> Self {
ConfigSettings {
Settings {
color_gradient: GradientType::default(),
language: Language::default(),
scale_factor: 1.0,
@ -65,20 +58,20 @@ impl Default for ConfigSettings {
#[cfg(test)]
mod tests {
use crate::ConfigSettings;
use crate::Settings;
impl ConfigSettings {
impl Settings {
pub fn test_path() -> String {
format!("{}/{}.toml", env!("CARGO_MANIFEST_DIR"), Self::FILE_NAME)
}
pub fn load() -> Self {
confy::load_path::<ConfigSettings>(ConfigSettings::test_path())
.unwrap_or_else(|_| ConfigSettings::default())
confy::load_path::<Settings>(Settings::test_path())
.unwrap_or_else(|_| Settings::default())
}
pub fn store(self) -> Result<(), confy::ConfyError> {
confy::store_path(ConfigSettings::test_path(), self)
confy::store_path(Settings::test_path(), self)
}
}
}

View file

@ -11,8 +11,6 @@ use iced::{Font, Pixels, Settings, application, window};
use chart::types::traffic_chart::TrafficChart;
use cli::handle_cli_args;
use configs::types::config_device::ConfigDevice;
use configs::types::config_settings::ConfigSettings;
use gui::pages::types::running_page::RunningPage;
use gui::sniffer::Sniffer;
use gui::styles::style_constants::FONT_SIZE_BODY;
@ -26,10 +24,10 @@ use report::types::report_sort_type::ReportSortType;
use translations::types::language::Language;
use utils::formatted_strings::print_cli_welcome_message;
use crate::configs::types::config_window::{ConfigWindow, ToPosition, ToSize};
use crate::configs::types::configs::{CONFIGS, Configs};
use crate::gui::sniffer::FONT_FAMILY_NAME;
use crate::gui::styles::style_constants::{ICONS_BYTES, SARASA_MONO_BOLD_BYTES, SARASA_MONO_BYTES};
use crate::gui::types::config_window::{ConfigWindow, ToPosition, ToSize};
mod chart;
mod cli;

View file

@ -1,6 +1,3 @@
//! Module defining the `ConfigDevice` struct, which allows to save and reload
//! the application default configuration.
use crate::networking::types::my_device::MyDevice;
#[cfg(not(test))]
use crate::utils::error_logger::{ErrorLogger, Location};
@ -67,7 +64,7 @@ impl ConfigDevice {
#[cfg(test)]
mod tests {
use crate::ConfigDevice;
use crate::networking::types::config_device::ConfigDevice;
impl ConfigDevice {
pub fn test_path() -> String {

View file

@ -3,6 +3,7 @@ pub mod arp_type;
pub mod asn;
pub mod bogon;
pub mod capture_context;
pub mod config_device;
pub mod data_info;
pub mod data_info_host;
pub mod data_representation;

View file

@ -50,7 +50,7 @@ pub fn get_searched_entries(
all_results.sort_by(|&(_, a), &(_, b)| {
a.compare(
b,
sniffer.report_sort_type.data_sort,
sniffer.conf.report_sort_type.data_sort,
sniffer.traffic_chart.data_repr,
)
});