Repository: GyulyVGC/sniffnet (language: rust)

Issue:
`ComboBox` widget for search filters
### Is there an existing issue for this?

- [X] I have searched the existing issues.

### Describe the solution you'd like

<img width="286" alt="Screenshot 2023-08-24 at 10 31 03 PM" src="https://github.com/GyulyVGC/sniffnet/assets/58950467/9b32a044-2de8-4f2b-bec4-f9374c6ee879">

Instead of typing, I was wondering if user could select from the existing domains fetched from the backend(over view page) so user could select directly


### Is your feature request related to a problem?

This would save time to go back to overview page and to copy the exact domain name and could select from a dropdown box.

I would like to get started with drop-down but if a dropdown feature exists already I would refer it directly. Let me know the checks! 

Thanks! 


Discussion / hints:
Hi @Irene-123 and thank you very much for your interest!

Luckily, the latest release of iced introduced a better functionality than a simple dropdown.
The widget you can use is the `ComboBox` widget and you can find an example about it here: https://github.com/iced-rs/iced/tree/master/examples/combo_box 

This is a feature I planned to introduce but I didn't start yet implementing it, so feel free to work on it.

Ideally, I'd like to have this kind of widget for all the text boxes of the inspect page (app protocol, country, domain, ASN).
This will require to store somewhere the collections of available options.
Such collections are not statically defined of course but are modified as new packets arrive.

Let me know if you need additional hints.
I'm facing some issues in understanding the code, is there a discord channel or somewhere I can ask silly doubts? : P
Here's their Discord: https://discord.com/invite/3xZJ65GAhd

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3dd9035ca..8cff17feb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
 All Sniffnet releases with the relative changes are documented in this file.
 
 ## [UNRELEASED]
+- Dropdown menus for network host filters ([#659](https://github.com/GyulyVGC/sniffnet/pull/659) — fixes [#354](https://github.com/GyulyVGC/sniffnet/issues/354))
 - Added CLI argument `--adapter [<NAME>]` to allow immediately starting the capture from a given network interface ([#643](https://github.com/GyulyVGC/sniffnet/pull/643) — fixes [#636](https://github.com/GyulyVGC/sniffnet/issues/636))
 - Added Vietnamese translation 🇻🇳 ([#577](https://github.com/GyulyVGC/sniffnet/pull/577))
 - Ask for quit confirmation before stopping an ongoing analysis ([#652](https://github.com/GyulyVGC/sniffnet/pull/652) — fixes [#570](https://github.com/GyulyVGC/sniffnet/issues/570))
diff --git a/src/gui/components/footer.rs b/src/gui/components/footer.rs
index 0b4b0f229..c7efcd0b6 100644
--- a/src/gui/components/footer.rs
+++ b/src/gui/components/footer.rs
@@ -1,6 +1,6 @@
 //! GUI bottom footer
 
-use std::sync::{Arc, Mutex};
+use std::sync::Mutex;
 
 use iced::widget::text::LineHeight;
 use iced::widget::tooltip::Position;
@@ -28,7 +28,7 @@ pub fn footer<'a>(
     color_gradient: GradientType,
     font: Font,
     font_footer: Font,
-    newer_release_available: &Arc<Mutex<Option<bool>>>,
+    newer_release_available: &Mutex<Option<bool>>,
 ) -> Container<'a, Message, StyleType> {
     if thumbnail {
         return thumbnail_footer();
@@ -136,7 +136,7 @@ fn get_release_details<'a>(
     language: Language,
     font: Font,
     font_footer: Font,
-    newer_release_available: &Arc<Mutex<Option<bool>>>,
+    newer_release_available: &Mutex<Option<bool>>,
 ) -> Row<'a, Message, StyleType> {
     let mut ret_val = Row::new()
         .align_y(Alignment::Center)
diff --git a/src/gui/pages/inspect_page.rs b/src/gui/pages/inspect_page.rs
index 3db51cbcf..db824d053 100644
--- a/src/gui/pages/inspect_page.rs
+++ b/src/gui/pages/inspect_page.rs
@@ -5,7 +5,8 @@ use iced::widget::text::LineHeight;
 use iced::widget::text_input::Side;
 use iced::widget::tooltip::Position;
 use iced::widget::{
-    button, horizontal_space, text_input, vertical_space, Rule, Space, Toggler, Tooltip,
+    button, combo_box, horizontal_space, text_input, vertical_space, ComboBox, Rule, Space,
+    Toggler, Tooltip,
 };
 use iced::widget::{lazy, Button, Column, Container, Row, Scrollable, Text, TextInput};
 use iced::{alignment, Alignment, Font, Length, Padding, Pixels};
@@ -20,6 +21,7 @@ use crate::gui::styles::text::TextType;
 use crate::gui::styles::text_input::TextInputType;
 use crate::gui::types::message::Message;
 use crate::networking::types::address_port_pair::AddressPortPair;
+use crate::networking::types::host_data_states::HostStates;
 use crate::networking::types::info_address_port_pair::InfoAddressPortPair;
 use crate::networking::types::traffic_direction::TrafficDirection;
 use crate::report::get_report_entries::get_searched_entries;
@@ -87,9 +89,14 @@ pub fn inspect_page(sniffer: &Sniffer) -> Container<Message, StyleType> {
 
     body = body
         .push(
-            Container::new(host_filters_col(&sniffer.search, font, language))
-                .padding(10)
-                .class(ContainerType::BorderedRound),
+            Container::new(host_filters_col(
+                &sniffer.search,
+                &sniffer.host_data_states.states,
+                font,
+                language,
+            ))
+            .padding(10)
+            .class(ContainerType::BorderedRound),
         )
         .push(
             Container::new(col_report)
@@ -307,11 +314,12 @@ fn row_report_entry<'a>(
     ret_val
 }
 
-fn host_filters_col(
-    search_params: &SearchParameters,
+fn host_filters_col<'a>(
+    search_params: &'a SearchParameters,
+    host_states: &'a HostStates,
     font: Font,
     language: Language,
-) -> Column<Message, StyleType> {
+) -> Column<'a, Message, StyleType> {
     let search_params2 = search_params.clone();
 
     let mut title_row = Row::new().spacing(10).align_y(Alignment::Center).push(
@@ -327,30 +335,47 @@ fn host_filters_col(
         ));
     }
 
-    let input_country =
-        filter_input(FilterInputType::Country, search_params.clone(), font).width(95);
-    let input_domain =
-        filter_input(FilterInputType::Domain, search_params.clone(), font).width(190);
-    let input_as_name =
-        filter_input(FilterInputType::AsName, search_params.clone(), font).width(190);
+    let combobox_country = filter_combobox(
+        FilterInputType::Country,
+        &host_states.countries,
+        search_params.clone(),
+        font,
+    )
+    .width(95);
+
+    let combobox_domain = filter_combobox(
+        FilterInputType::Domain,
+        &host_states.domains,
+        search_params.clone(),
+        font,
+    )
+    .width(190);
+
+    let combobox_as_name = filter_combobox(
+        FilterInputType::AsName,
+        &host_states.asns,
+        search_params.clone(),
+        font,
+    )
+    .width(190);
 
     let container_country = Row::new()
         .spacing(5)
         .align_y(Alignment::Center)
         .push(Text::new(format!("{}:", country_translation(language))).font(font))
-        .push(input_country);
+        .push(combobox_country);
 
     let container_domain = Row::new()
         .spacing(5)
         .align_y(Alignment::Center)
         .push(Text::new(format!("{}:", domain_name_translation(language))).font(font))
-        .push(input_domain);
+        .push(combobox_domain);
 
     let container_as_name = Row::new()
         .spacing(5)
         .align_y(Alignment::Center)
         .push(Text::new(format!("{}:", administrative_entity_translation(language))).font(font))
-        .push(input_as_name);
+        .push(combobox_as_name);
 
     let col1 = Column::new()
         .align_x(Alignment::Start)
@@ -446,6 +471,64 @@ fn filter_input<'a>(
         })
 }
 
+fn filter_combobox(
+    filter_input_type: FilterInputType,
+    combo_box_state: &combo_box::State<String>,
+    search_params: SearchParameters,
+    font: Font,
+) -> Container<Message, StyleType> {
+    let filter_value = filter_input_type.current_value(&search_params).to_string();
+    let is_filter_active = !filter_value.is_empty();
+
+    let button_clear = button_clear_filter(filter_input_type.clear_search(&search_params), font);
+
+    let update_fn =
+        move |new_value| Message::Search(filter_input_type.new_search(&search_params, new_value));
+
+    let mut combobox = ComboBox::new(combo_box_state, "", Some(&filter_value), update_fn.clone())
+        .on_input(update_fn)
+        .padding([2, 5])
+        .size(FONT_SIZE_FOOTER)
+        .font(font)
+        .width(Length::Fill)
+        .input_class(if is_filter_active {
+            TextInputType::Badge
+        } else {
+            TextInputType::Standard
+        });
+
+    if !is_filter_active {
+        combobox = combobox.icon(text_input::Icon {
+            font: ICONS,
+            code_point: Icon::Funnel.codepoint(),
+            size: Some(Pixels(12.0)),
+            spacing: 2.0,
+            side: Side::Left,
+        });
+    }
+
+    let mut content = Row::new()
+        .spacing(5)
+        .align_y(Alignment::Center)
+        .push(combobox);
+
+    if is_filter_active {
+        content = content.push(button_clear);
+    }
+
+    Container::new(content)
+        .padding(if is_filter_active {
+            Padding::new(5.0).left(10)
+        } else {
+            Padding::new(5.0).right(3).left(3)
+        })
+        .class(if is_filter_active {
+            ContainerType::Badge
+        } else {
+            ContainerType::Standard
+        })
+}
+
 fn get_button_change_page<'a>(increment: bool) -> Button<'a, Message, StyleType> {
     button(
         if increment {
diff --git a/src/gui/pages/settings_general_page.rs b/src/gui/pages/settings_general_page.rs
index ab432b51c..1b90e46c7 100644
--- a/src/gui/pages/settings_general_page.rs
+++ b/src/gui/pages/settings_general_page.rs
@@ -1,5 +1,3 @@
-use std::sync::Arc;
-
 use iced::widget::text::LineHeight;
 use iced::widget::tooltip::Position;
 use iced::widget::{
@@ -16,7 +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::mmdb::types::mmdb_reader::MmdbReader;
+use crate::mmdb::types::mmdb_reader::{MmdbReader, MmdbReaders};
 use crate::translations::translations::language_translation;
 use crate::translations::translations_2::country_translation;
 use crate::translations::translations_3::{
@@ -91,8 +89,7 @@ fn column_all_general_setting(sniffer: &Sniffer, font: Font) -> Column<Message,
         font,
         &mmdb_country,
         &mmdb_asn,
-        &sniffer.country_mmdb_reader,
-        &sniffer.asn_mmdb_reader,
+        &sniffer.mmdb_readers,
     ));
 
     column
@@ -249,8 +246,7 @@ fn mmdb_settings<'a>(
     font: Font,
     country_path: &str,
     asn_path: &str,
-    country_reader: &Arc<MmdbReader>,
-    asn_reader: &Arc<MmdbReader>,
+    mmdb_readers: &MmdbReaders,
 ) -> Column<'a, Message, StyleType> {
     Column::new()
         .spacing(5)
@@ -266,7 +262,7 @@ fn mmdb_settings<'a>(
             font,
             Message::CustomCountryDb,
             country_path,
-            country_reader,
+            &mmdb_readers.country,
             country_translation(language),
             language,
         ))
@@ -275,7 +271,7 @@ fn mmdb_settings<'a>(
             font,
             Message::CustomAsnDb,
             asn_path,
-            asn_reader,
+            &mmdb_readers.asn,
             "ASN",
             language,
         ))
@@ -286,14 +282,14 @@ fn mmdb_selection_row<'a>(
     font: Font,
     message: fn(String) -> Message,
     custom_path: &str,
-    mmdb_reader: &Arc<MmdbReader>,
+    mmdb_reader: &MmdbReader,
     caption: &str,
     language: Language,
 ) -> Row<'a, Message, StyleType> {
     let is_error = if custom_path.is_empty() {
         false
     } else {
-        match **mmdb_reader {
+        match *mmdb_reader {
             MmdbReader::Default(_) => true,
             MmdbReader::Custom(_) => false,
         }
diff --git a/src/gui/pages/thumbnail_page.rs b/src/gui/pages/thumbnail_page.rs
index d4359f294..09752edca 100644
--- a/src/gui/pages/thumbnail_page.rs
+++ b/src/gui/pages/thumbnail_page.rs
@@ -1,6 +1,6 @@
 use std::cmp::min;
 use std::net::IpAddr;
-use std::sync::{Arc, Mutex};
+use std::sync::Mutex;
 
 use iced::widget::{lazy, vertical_space, Column, Container, Row, Rule, Space, Text};
 use iced::{Alignment, Font, Length};
@@ -61,7 +61,7 @@ pub fn thumbnail_page(sniffer: &Sniffer) -> Container<Message, StyleType> {
 }
 
 fn host_col<'a>(
-    info_traffic: &Arc<Mutex<InfoTraffic>>,
+    info_traffic: &Mutex<InfoTraffic>,
     chart_type: ChartType,
     font: Font,
 ) -> Column<'a, Message, StyleType> {
@@ -103,7 +103,7 @@ fn host_col<'a>(
 }
 
 fn service_col<'a>(
-    info_traffic: &Arc<Mutex<InfoTraffic>>,
+    info_traffic: &Mutex<InfoTraffic>,
     chart_type: ChartType,
     font: Font,
 ) -> Column<'a, Message, StyleType> {
diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs
index dbd9bba58..62d41b273 100644
--- a/src/gui/sniffer.rs
+++ b/src/gui/sniffer.rs
@@ -42,10 +42,11 @@ use crate::gui::types::message::Message;
 use crate::gui::types::timing_events::TimingEvents;
 use crate::mmdb::asn::ASN_MMDB;
 use crate::mmdb::country::COUNTRY_MMDB;
-use crate::mmdb::types::mmdb_reader::MmdbReader;
+use crate::mmdb::types::mmdb_reader::{MmdbReader, MmdbReaders};
 use crate::networking::types::capture_context::CaptureContext;
 use crate::networking::types::filters::Filters;
 use crate::networking::types::host::Host;
+use crate::networking::types::host_data_states::HostDataStates;
 use crate::networking::types::ip_collection::AddressCollection;
 use crate::networking::types::my_device::MyDevice;
 use crate::networking::types::my_link_type::MyLinkType;
@@ -113,10 +114,8 @@ pub struct Sniffer {
     pub search: SearchParameters,
     /// Current page number of inspect search results
     pub page_number: usize,
-    /// MMDB reader for countries
-    pub country_mmdb_reader: Arc<MmdbReader>,
-    /// MMDB reader for ASN
-    pub asn_mmdb_reader: Arc<MmdbReader>,
+    /// MMDB readers for country and ASN
+    pub mmdb_readers: MmdbReaders,
     /// Time-related events
     pub timing_events: TimingEvents,
     /// Information about PCAP file export
@@ -125,6 +124,8 @@ pub struct Sniffer {
     pub thumbnail: bool,
     /// Window id
     pub id: Option<Id>,
+    /// Host data for filter dropdowns (comboboxes)
+    pub host_data_states: HostDataStates,
 }
 
 impl Sniffer {
@@ -161,12 +162,15 @@ impl Sniffer {
             unread_notifications: 0,
             search: SearchParameters::default(),
             page_number: 1,
-            country_mmdb_reader: Arc::new(MmdbReader::from(&mmdb_country, COUNTRY_MMDB)),
-            asn_mmdb_reader: Arc::new(MmdbReader::from(&mmdb_asn, ASN_MMDB)),
+            mmdb_readers: MmdbReaders {
+                country: Arc::new(MmdbReader::from(&mmdb_country, COUNTRY_MMDB)),
+                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(),
         }
     }
 
@@ -358,6 +362,14 @@ impl Sniffer {
             Message::ResetButtonPressed => return self.reset_button_pressed(),
             Message::CtrlDPressed => return self.shortcut_ctrl_d(),
             Message::Search(parameters) => {
+                // update comboboxes
+                let mut host_data = self.host_data_states.data.lock().unwrap();
+                host_data.countries.1 = self.search.country != parameters.country;
+                host_data.asns.1 = self.search.as_name != parameters.as_name;
+                host_data.domains.1 = self.search.domain != parameters.domain;
+                drop(host_data);
+                self.host_data_states.update_states(&parameters);
+
                 self.page_number = 1;
                 self.running_page = RunningPage::Inspect;
                 self.search = parameters;
@@ -419,7 +431,7 @@ impl Sniffer {
                     .settings
                     .mmdb_country
                     .clone_from(&db);
-                self.country_mmdb_reader = Arc::new(MmdbReader::from(&db, COUNTRY_MMDB));
+                self.mmdb_readers.country = Arc::new(MmdbReader::from(&db, COUNTRY_MMDB));
             }
             Message::CustomAsnDb(db) => {
                 self.configs
@@ -428,7 +440,7 @@ impl Sniffer {
                     .settings
                     .mmdb_asn
                     .clone_from(&db);
-                self.asn_mmdb_reader = Arc::new(MmdbReader::from(&db, ASN_MMDB));
+                self.mmdb_readers.asn = Arc::new(MmdbReader::from(&db, ASN_MMDB));
             }
             Message::QuitWrapper => return self.quit_wrapper(),
             Message::Quit => {
@@ -661,6 +673,8 @@ impl Sniffer {
         {
             return self.update(Message::Waiting);
         }
+        // update host dropdowns
+        self.host_data_states.update_states(&self.search);
         Task::none()
     }
 
@@ -702,8 +716,8 @@ impl Sniffer {
             // no pcap error
             let current_capture_id = self.current_capture_id.clone();
             let filters = self.filters.clone();
-            let country_mmdb_reader = self.country_mmdb_reader.clone();
-            let asn_mmdb_reader = self.asn_mmdb_reader.clone();
+            let mmdb_readers = self.mmdb_readers.clone();
+            let host_data = self.host_data_states.data.clone();
             self.device.link_type = capture_context.my_link_type();
             thread::Builder::new()
                 .name("thread_parse_packets".to_string())
@@ -713,9 +727,9 @@ impl Sniffer {
                         &device,
                         &filters,
                         &info_traffic_mutex,
-                        &country_mmdb_reader,
-                        &asn_mmdb_reader,
+                        &mmdb_readers,
                         capture_context,
+                        &host_data,
                     );
                 })
                 .unwrap();
@@ -730,6 +744,7 @@ impl Sniffer {
         self.unread_notifications = 0;
         self.search = SearchParameters::default();
         self.page_number = 1;
+        self.host_data_states = HostDataStates::default();
         self.update(Message::HideModal)
     }
 
diff --git a/src/gui/styles/combobox.rs b/src/gui/styles/combobox.rs
new file mode 100644
index 000000000..a116db26c
--- /dev/null
+++ b/src/gui/styles/combobox.rs
@@ -0,0 +1,9 @@
+//! Combobox style
+
+#![allow(clippy::module_name_repetitions)]
+
+use iced::widget::combo_box::Catalog;
+
+use crate::StyleType;
+
+impl Catalog for StyleType {}
diff --git a/src/gui/styles/mod.rs b/src/gui/styles/mod.rs
index 58d43505b..833ffa33b 100644
--- a/src/gui/styles/mod.rs
+++ b/src/gui/styles/mod.rs
@@ -1,5 +1,6 @@
 pub mod button;
 pub mod checkbox;
+pub mod combobox;
 pub mod container;
 pub mod custom_themes;
 pub mod picklist;
diff --git a/src/mmdb/types/mmdb_reader.rs b/src/mmdb/types/mmdb_reader.rs
index 8ffd295a2..c3c6038d8 100644
--- a/src/mmdb/types/mmdb_reader.rs
+++ b/src/mmdb/types/mmdb_reader.rs
@@ -1,8 +1,15 @@
 use std::net::IpAddr;
+use std::sync::Arc;
 
 use maxminddb::{MaxMindDBError, Reader};
 use serde::Deserialize;
 
+#[derive(Clone)]
+pub struct MmdbReaders {
+    pub country: Arc<MmdbReader>,
+    pub asn: Arc<MmdbReader>,
+}
+
 pub enum MmdbReader {
     Default(Reader<&'static [u8]>),
     Custom(Reader<Vec<u8>>),
diff --git a/src/networking/manage_packets.rs b/src/networking/manage_packets.rs
index 86bc28b70..7f4576bd8 100644
--- a/src/networking/manage_packets.rs
+++ b/src/networking/manage_packets.rs
@@ -1,7 +1,7 @@
 use std::collections::HashMap;
 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
 use std::str::FromStr;
-use std::sync::{Arc, Mutex};
+use std::sync::Mutex;
 
 use chrono::Local;
 use dns_lookup::lookup_addr;
@@ -10,10 +10,11 @@ use pcap::{Address, Device};
 
 use crate::mmdb::asn::get_asn;
 use crate::mmdb::country::get_country;
-use crate::mmdb::types::mmdb_reader::MmdbReader;
+use crate::mmdb::types::mmdb_reader::MmdbReaders;
 use crate::networking::types::address_port_pair::AddressPortPair;
 use crate::networking::types::data_info_host::DataInfoHost;
 use crate::networking::types::host::Host;
+use crate::networking::types::host_data_states::HostData;
 use crate::networking::types::icmp_type::{IcmpType, IcmpTypeV4, IcmpTypeV6};
 use crate::networking::types::info_address_port_pair::InfoAddressPortPair;
 use crate::networking::types::my_device::MyDevice;
@@ -210,7 +211,7 @@ pub fn get_service(key: &AddressPortPair, traffic_direction: TrafficDirection) -
 
 /// Function to insert the source and destination of a packet into the shared map containing the analyzed traffic.
 pub fn modify_or_insert_in_map(
-    info_traffic_mutex: &Arc<Mutex<InfoTraffic>>,
+    info_traffic_mutex: &Mutex<InfoTraffic>,
     key: &AddressPortPair,
     my_device: &MyDevice,
     mac_addresses: (Option<String>, Option<String>),
@@ -298,12 +299,12 @@ pub fn modify_or_insert_in_map(
 }
 
 pub fn reverse_dns_lookup(
-    info_traffic: &Arc<Mutex<InfoTraffic>>,
+    info_traffic: &Mutex<InfoTraffic>,
     key: &AddressPortPair,
     traffic_direction: TrafficDirection,
     my_device: &MyDevice,
-    country_db_reader: &Arc<MmdbReader>,
-    asn_db_reader: &Arc<MmdbReader>,
+    mmdb_readers: &MmdbReaders,
+    host_data: &Mutex<HostData>,
 ) {
     let address_to_lookup = get_address_to_lookup(key, traffic_direction);
     let my_interface_addresses = my_device.addresses.lock().unwrap().clone();
@@ -319,8 +320,8 @@ pub fn reverse_dns_lookup(
     );
     let is_loopback = is_loopback(&address_to_lookup);
     let is_local = is_local_connection(&address_to_lookup, &my_interface_addresses);
-    let country = get_country(&address_to_lookup, country_db_reader);
-    let asn = get_asn(&address_to_lookup, asn_db_reader);
+    let country = get_country(&address_to_lookup, &mmdb_readers.country);
+    let asn = get_asn(&address_to_lookup, &mmdb_readers.asn);
     let r_dns = if let Ok(result) = lookup_result {
         if result.is_empty() {
             address_to_lookup.clone()
@@ -359,6 +360,10 @@ pub fn reverse_dns_lookup(
             is_local,
             traffic_type,
         });
+
+    // update host data states including the new host
+    host_data.lock().unwrap().update(&new_host);
+
     // check if the newly resolved host was featured in the favorites (possible in case of already existing host)
     if info_traffic_lock.favorite_hosts.contains(&new_host) {
         info_traffic_lock.favorites_last_interval.insert(new_host);
diff --git a/src/networking/types/host_data_states.rs b/src/networking/types/host_data_states.rs
new file mode 100644
index 000000000..a71be20a6
--- /dev/null
+++ b/src/networking/types/host_data_states.rs
@@ -0,0 +1,78 @@
+use crate::countries::types::country::Country;
+use crate::networking::types::host::Host;
+use crate::report::types::search_parameters::SearchParameters;
+use iced::widget::combo_box;
+use std::collections::BTreeSet;
+use std::net::IpAddr;
+use std::sync::{Arc, Mutex};
+
+/// Struct to contain all the sets of data related to network hosts
+///
+/// It also stores combobox states for the host-related filters
+#[derive(Default)]
+pub struct HostDataStates {
+    pub data: Arc<Mutex<HostData>>,
+    pub states: HostStates,
+}
+
+impl HostDataStates {
+    pub fn update_states(&mut self, search: &SearchParameters) {
+        let states = &mut self.states;
+        let mut data = self.data.lock().unwrap();
+
+        if data.domains.1 {
+            states.domains = combo_box::State::with_selection(
+                data.domains.0.iter().cloned().collect(),
+                Some(&search.domain),
+            );
+            data.domains.1 = false;
+        }
+
+        if data.asns.1 {
+            states.asns = combo_box::State::with_selection(
+                data.asns.0.iter().cloned().collect(),
+                Some(&search.as_name),
+            );
+            data.asns.1 = false;
+        }
+
+        if data.countries.1 {
+            states.countries = combo_box::State::with_selection(
+                data.countries.0.iter().cloned().collect(),
+                Some(&search.country),
+            );
+            data.countries.1 = false;
+        }
+    }
+}
+
+#[derive(Default)]
+pub struct HostData {
+    pub domains: (BTreeSet<String>, bool),
+    pub asns: (BTreeSet<String>, bool),
+    pub countries: (BTreeSet<String>, bool),
+}
+
+impl HostData {
+    pub fn update(&mut self, host: &Host) {
+        if !host.domain.is_empty() && host.domain.parse::<IpAddr>().is_err() {
+            self.domains.1 = self.domains.0.insert(host.domain.clone()) || self.domains.1;
+        }
+
+        if !host.asn.name.is_empty() {
+            self.asns.1 = self.asns.0.insert(host.asn.name.clone()) || self.asns.1;
+        }
+
+        if host.country != Country::ZZ {
+            self.countries.1 =
+                self.countries.0.insert(host.country.to_string()) || self.countries.1;
+        }
+    }
+}
+
+#[derive(Default)]
+pub struct HostStates {
+    pub domains: combo_box::State<String>,
+    pub asns: combo_box::State<String>,
+    pub countries: combo_box::State<String>,
+}
diff --git a/src/networking/types/mod.rs b/src/networking/types/mod.rs
index cb369e4e3..fe7dec598 100644
--- a/src/networking/types/mod.rs
+++ b/src/networking/types/mod.rs
@@ -6,6 +6,7 @@ pub mod data_info;
 pub mod data_info_host;
 pub mod filters;
 pub mod host;
+pub mod host_data_states;
 pub mod icmp_type;
 pub mod info_address_port_pair;
 pub mod info_traffic;
diff --git a/src/notifications/notify_and_log.rs b/src/notifications/notify_and_log.rs
index 2c7aff31e..03d872193 100644
--- a/src/notifications/notify_and_log.rs
+++ b/src/notifications/notify_and_log.rs
@@ -1,4 +1,4 @@
-use std::sync::{Arc, Mutex};
+use std::sync::Mutex;
 
 use chrono::Local;
 
@@ -15,7 +15,7 @@ use crate::{InfoTraffic, RunTimeData};
 pub fn notify_and_log(
     runtime_data: &mut RunTimeData,
     notifications: Notifications,
-    info_traffic: &Arc<Mutex<InfoTraffic>>,
+    info_traffic: &Mutex<InfoTraffic>,
 ) -> usize {
     let mut already_emitted_sound = false;
     let mut emitted_notifications = 0;
diff --git a/src/report/get_report_entries.rs b/src/report/get_report_entries.rs
index 2425bbb46..367a3363d 100644
--- a/src/report/get_report_entries.rs
+++ b/src/report/get_report_entries.rs
@@ -1,5 +1,5 @@
 use std::cmp::min;
-use std::sync::{Arc, Mutex};
+use std::sync::Mutex;
 
 use crate::networking::manage_packets::get_address_to_lookup;
 use crate::networking::types::address_port_pair::AddressPortPair;
@@ -66,7 +66,7 @@ pub fn get_searched_entries(
 }
 
 pub fn get_host_entries(
-    info_traffic: &Arc<Mutex<InfoTraffic>>,
+    info_traffic: &Mutex<InfoTraffic>,
     chart_type: ChartType,
     sort_type: SortType,
 ) -> Vec<(Host, DataInfoHost)> {
@@ -83,7 +83,7 @@ pub fn get_host_entries(
 }
 
 pub fn get_service_entries(
-    info_traffic: &Arc<Mutex<InfoTraffic>>,
+    info_traffic: &Mutex<InfoTraffic>,
     chart_type: ChartType,
     sort_type: SortType,
 ) -> Vec<(Service, DataInfo)> {
diff --git a/src/secondary_threads/check_updates.rs b/src/secondary_threads/check_updates.rs
index ec84bd75f..0aafde441 100644
--- a/src/secondary_threads/check_updates.rs
+++ b/src/secondary_threads/check_updates.rs
@@ -1,4 +1,4 @@
-use std::sync::{Arc, Mutex};
+use std::sync::Mutex;
 use std::thread;
 use std::time::Duration;
 
@@ -14,7 +14,7 @@ struct AppVersion {
 
 /// Calls a method to check if a newer release of Sniffnet is available on GitHub
 /// and updates application status accordingly
-pub fn set_newer_release_status(newer_release_available: &Arc<Mutex<Option<bool>>>) {
+pub fn set_newer_release_status(newer_release_available: &Mutex<Option<bool>>) {
     let result = is_newer_release_available(6, 30);
     *newer_release_available.lock().unwrap() = result;
 }
diff --git a/src/secondary_threads/parse_packets.rs b/src/secondary_threads/parse_packets.rs
index 682708038..fdc1a67d3 100644
--- a/src/secondary_threads/parse_packets.rs
+++ b/src/secondary_threads/parse_packets.rs
@@ -9,13 +9,14 @@ use etherparse::err::{Layer, LenError};
 use etherparse::{LaxPacketHeaders, LenSource};
 use pcap::Packet;
 
-use crate::mmdb::types::mmdb_reader::MmdbReader;
+use crate::mmdb::types::mmdb_reader::MmdbReaders;
 use crate::networking::manage_packets::{
     analyze_headers, get_address_to_lookup, modify_or_insert_in_map, reverse_dns_lookup,
 };
 use crate::networking::types::capture_context::CaptureContext;
 use crate::networking::types::data_info::DataInfo;
 use crate::networking::types::filters::Filters;
+use crate::networking::types::host_data_states::HostData;
 use crate::networking::types::icmp_type::IcmpType;
 use crate::networking::types::info_address_port_pair::InfoAddressPortPair;
 use crate::networking::types::my_device::MyDevice;
@@ -26,13 +27,13 @@ use crate::InfoTraffic;
 /// The calling thread enters a loop in which it waits for network packets, parses them according
 /// to the user specified filters, and inserts them into the shared map variable.
 pub fn parse_packets(
-    current_capture_id: &Arc<Mutex<usize>>,
+    current_capture_id: &Mutex<usize>,
     device: &MyDevice,
     filters: &Filters,
     info_traffic_mutex: &Arc<Mutex<InfoTraffic>>,
-    country_mmdb_reader: &Arc<MmdbReader>,
-    asn_mmdb_reader: &Arc<MmdbReader>,
+    mmdb_readers: &MmdbReaders,
     capture_context: CaptureContext,
+    host_data: &Arc<Mutex<HostData>>,
 ) {
     let my_link_type = capture_context.my_link_type();
     let (mut cap, mut savefile) = capture_context.consume();
@@ -133,8 +134,8 @@ pub fn parse_packets(
                                 let key2 = key.clone();
                                 let info_traffic2 = info_traffic_mutex.clone();
                                 let device2 = device.clone();
-                                let country_db_reader_2 = country_mmdb_reader.clone();
-                                let asn_db_reader_2 = asn_mmdb_reader.clone();
+                                let mmdb_readers_2 = mmdb_readers.clone();
+                                let host_data2 = host_data.clone();
                                 thread::Builder::new()
                                     .name("thread_reverse_dns_lookup".to_string())
                                     .spawn(move || {
@@ -143,8 +144,8 @@ pub fn parse_packets(
                                             &key2,
                                             new_info.traffic_direction,
                                             &device2,
-                                            &country_db_reader_2,
-                                            &asn_db_reader_2,
+                                            &mmdb_readers_2,
+                                            &host_data2,
                                         );
                                     })
                                     .unwrap();


Test patch: