use crate::gui::components::button::button_open_file;
use crate::gui::sniffer::Sniffer;
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, FONT_SIZE_SUBTITLE, FONT_SIZE_TITLE};
use crate::gui::styles::text::TextType;
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::networking::types::my_device::MyDevice;
use crate::networking::types::my_link_type::MyLinkType;
use crate::translations::translations::{network_adapter_translation, start_translation};
use crate::translations::translations_3::{
directory_translation, export_capture_translation, file_name_translation,
};
use crate::translations::translations_4::capture_file_translation;
use crate::translations::translations_5::{filter_traffic_translation, traffic_source_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::{Language, StyleType};
use iced::Length::FillPortion;
use iced::widget::scrollable::Direction;
use iced::widget::{
Button, Checkbox, Column, Container, PickList, Row, Scrollable, Space, Text, TextInput, button,
center, row,
};
use iced::{Alignment, Length, Padding, alignment};
use pcap::Address;
pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
let Settings {
language,
color_gradient,
..
} = sniffer.conf.settings;
let col_data_source = get_col_data_source(sniffer, language);
let col_checkboxes = Column::new()
.spacing(10)
.push(get_filters_group(&sniffer.conf.filters, language))
.push(get_export_pcap_group_maybe(
sniffer.conf.capture_source_picklist,
&sniffer.conf.export_pcap,
language,
));
let is_capture_source_consistent = sniffer.is_capture_source_consistent();
let right_col = Column::new()
.width(FillPortion(1))
.padding(10)
.push(Space::new().height(76))
.push(col_checkboxes)
.push(Space::new().height(Length::Fill))
.push(button_start(
language,
color_gradient,
is_capture_source_consistent,
))
.push(Space::new().height(Length::Fill));
let body = Column::new().push(Space::new().height(5)).push(
Row::new()
.push(col_data_source)
.push(Space::new().width(15))
.push(right_col),
);
Container::new(body).height(Length::Fill)
}
fn button_start<'a>(
language: Language,
color_gradient: GradientType,
is_capture_source_consistent: bool,
) -> Button<'a, Message, StyleType> {
button(
Text::new(start_translation(language))
.size(FONT_SIZE_TITLE)
.width(Length::Fill)
.align_x(alignment::Alignment::Center)
.align_y(alignment::Alignment::Center),
)
.padding(20)
.width(Length::Fill)
.class(ButtonType::Gradient(color_gradient))
.on_press_maybe(if is_capture_source_consistent {
Some(Message::Start)
} else {
None
})
}
fn get_col_data_source(sniffer: &Sniffer, language: Language) -> Column<'_, Message, StyleType> {
let current_option = if sniffer.conf.capture_source_picklist == CaptureSourcePicklist::Device {
network_adapter_translation(language)
} else {
capture_file_translation(language)
};
let picklist = PickList::new(
[
network_adapter_translation(language),
capture_file_translation(language),
],
Some(current_option),
move |option| {
if option == network_adapter_translation(language) {
Message::SetCaptureSource(CaptureSourcePicklist::Device)
} else {
Message::SetCaptureSource(CaptureSourcePicklist::File)
}
},
)
.padding([2, 7]);
let mut col = Column::new()
.align_x(Alignment::Center)
.padding(Padding::new(10.0).top(30).bottom(0))
.spacing(30)
.height(Length::Fill)
.width(FillPortion(1))
.push(
Row::new()
.spacing(10)
.push(
Text::new(traffic_source_translation(language))
.class(TextType::Title)
.size(FONT_SIZE_TITLE),
)
.push(picklist),
);
match &sniffer.conf.capture_source_picklist {
CaptureSourcePicklist::Device => {
col = col.push(get_col_adapter(sniffer));
}
CaptureSourcePicklist::File => {
col = col.push(get_col_import_pcap(
language,
&sniffer.capture_source,
&sniffer.conf.import_pcap_path,
));
}
}
col
}
fn get_col_adapter(sniffer: &Sniffer) -> Column<'_, Message, StyleType> {
Column::new()
.spacing(5)
.height(Length::Fill)
.push(if sniffer.preview_charts.is_empty() {
Into::<iced::Element<Message, StyleType>>::into(center(
Icon::get_hourglass(sniffer.dots_pulse.0.len()).size(60),
))
} else {
Scrollable::with_direction(
sniffer.preview_charts.iter().fold(
Column::new()
.padding(Padding::ZERO.right(13).bottom(10))
.spacing(5),
|scroll_adapters, (my_dev, chart)| {
let name = my_dev.get_name();
let addresses_row =
get_addresses_row(my_dev.get_link_type(), my_dev.get_addresses());
let (title, subtitle) = get_adapter_title_subtitle(my_dev);
scroll_adapters.push(
Button::new(
Column::new()
.spacing(5)
.push(
Text::new(title)
.class(TextType::Subtitle)
.size(FONT_SIZE_SUBTITLE),
)
.push(subtitle.map(Text::new))
.push(addresses_row)
.push(if chart.max_packets > 0.0 {
Some(chart.view())
} else {
None
}),
)
.padding(15)
.width(Length::Fill)
.class(
if let CaptureSource::Device(device) = &sniffer.capture_source {
if name == device.get_name() {
ButtonType::BorderedRoundSelected
} else {
ButtonType::BorderedRound
}
} else {
ButtonType::BorderedRound
},
)
.on_press(Message::DeviceSelection(name.clone())),
)
},
),
Direction::Vertical(ScrollbarType::properties()),
)
.into()
})
}
pub(crate) fn get_addresses_row(
link_type: MyLinkType,
addresses: &Vec<Address>,
) -> Option<row::Wrapping<'_, Message, StyleType>> {
if addresses.is_empty()
|| matches!(
link_type,
MyLinkType::LinuxSll(_) | MyLinkType::LinuxSll2(_)
)
{
return None;
}
let mut row = Row::new().spacing(5);
for addr in addresses {
let address_string = addr.addr.to_string();
row = row.push(
Container::new(Text::new(address_string).size(FONT_SIZE_FOOTER))
.padding(Padding::new(5.0).left(10).right(10))
.class(ContainerType::AdapterAddress),
);
}
Some(row.wrap())
}
fn get_adapter_title_subtitle(my_dev: &MyDevice) -> (String, Option<String>) {
let mut title = String::new();
#[allow(unused_mut)]
let mut subtitle: Option<String> = None;
let name = my_dev.get_name();
match my_dev.get_desc() {
None => {
title.push_str(name);
}
Some(description) => {
#[cfg(not(target_os = "windows"))]
{
title.push_str(name);
subtitle = Some(description.to_owned());
}
#[cfg(target_os = "windows")]
title.push_str(description);
}
}
(title, subtitle)
}
fn get_col_import_pcap<'a>(
language: Language,
cs: &CaptureSource,
path: &str,
) -> Column<'a, Message, StyleType> {
let is_import_pcap_set = matches!(cs, CaptureSource::File(_));
let button_row = Row::new()
.align_y(Alignment::Center)
.push(Text::new(get_path_termination_string(path, 25)))
.push(button_open_file(
path.to_string(),
FileInfo::PcapImport,
language,
true,
Message::SetPcapImport,
));
let content = Column::new()
.width(Length::Fill)
.align_x(alignment::Alignment::Center)
.spacing(5)
.push(button_row);
let button = Container::new(
Button::new(content)
.width(Length::Fill)
.padding([20, 30])
.class(if is_import_pcap_set {
ButtonType::BorderedRoundSelected
} else {
ButtonType::BorderedRound
})
.on_press(Message::SetPcapImport(path.to_string())),
)
.padding(Padding::ZERO.right(13));
Column::new().spacing(5).push(button)
}
fn get_filters_group<'a>(
filters: &Filters,
language: Language,
) -> Container<'a, Message, StyleType> {
let expanded = filters.expanded();
let bpf = filters.bpf();
let caption = filter_traffic_translation(language);
let checkbox = Checkbox::new(expanded)
.label(caption)
.on_toggle(move |_| Message::ToggleFilters)
.size(18);
let mut ret_val = Column::new().spacing(10).push(checkbox);
if expanded {
let input = TextInput::new("", bpf)
.on_input(Message::BpfFilter)
.padding([2, 5]);
let inner_col = Column::new()
.spacing(10)
.padding(Padding::ZERO.left(26))
.push(
Row::new()
.align_y(Alignment::Center)
.spacing(5)
.push(Text::new("BPF:"))
.push(input),
);
ret_val = ret_val.push(inner_col);
}
Container::new(ret_val)
.padding(15)
.width(Length::Fill)
.class(ContainerType::BorderedRound)
}
fn get_export_pcap_group_maybe<'a>(
cs_pick: CaptureSourcePicklist,
export_pcap: &ExportPcap,
language: Language,
) -> Option<Container<'a, Message, StyleType>> {
if cs_pick == CaptureSourcePicklist::File {
return None;
}
let enabled = export_pcap.enabled();
let file_name = export_pcap.file_name();
let directory = export_pcap.directory();
let caption = export_capture_translation(language);
let checkbox = Checkbox::new(enabled)
.label(caption)
.on_toggle(move |_| Message::ToggleExportPcap)
.size(18);
let mut ret_val = Column::new().spacing(10).push(checkbox);
if enabled {
let inner_col = Column::new()
.spacing(10)
.padding(Padding::ZERO.left(26))
.push(
Row::new()
.align_y(Alignment::Center)
.spacing(5)
.push(Text::new(format!("{}:", file_name_translation(language))))
.push(
TextInput::new(ExportPcap::DEFAULT_FILE_NAME, file_name)
.on_input(Message::OutputPcapFile)
.padding([2, 5]),
),
)
.push(
Row::new()
.align_y(Alignment::Center)
.spacing(5)
.push(Text::new(format!("{}:", directory_translation(language))))
.push(Text::new(get_path_termination_string(directory, 25)))
.push(button_open_file(
directory.to_owned(),
FileInfo::Directory,
language,
true,
Message::OutputPcapDir,
)),
);
ret_val = ret_val.push(inner_col);
}
Some(
Container::new(ret_val)
.padding(15)
.width(Length::Fill)
.class(ContainerType::BorderedRound),
)
}