feat: improve IP blacklist import feedback

This commit is contained in:
HangryNugget 2026-05-08 02:16:24 +02:00
parent e4534b0e0f
commit 51e03967f4
3 changed files with 337 additions and 8 deletions

View File

@ -15,14 +15,18 @@
use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::mmdb::types::mmdb_reader::{MmdbReader, MmdbReaders};
use crate::networking::types::ip_blacklist::IpBlacklist;
use crate::networking::types::ip_blacklist::{IpBlacklist, IpBlacklistLoadStatus};
use crate::translations::translations::language_translation;
use crate::translations::translations_2::country_translation;
use crate::translations::translations_3::{
mmdb_files_translation, params_not_editable_translation, zoom_translation,
};
use crate::translations::translations_4::share_feedback_translation;
use crate::translations::translations_5::ip_blacklist_translation;
use crate::translations::translations_5::{
ip_blacklist_file_read_error_translation, ip_blacklist_loaded_translation,
ip_blacklist_loading_translation, ip_blacklist_no_valid_entries_translation,
ip_blacklist_not_selected_translation, ip_blacklist_translation,
};
use crate::utils::formatted_strings::get_path_termination_string;
use crate::utils::types::file_info::FileInfo;
use crate::utils::types::icon::Icon;
@ -331,6 +335,8 @@ fn blacklist_selection<'a>(
};
let message = Message::LoadIpBlacklist;
let status = blacklist_status_text(custom_path, ip_blacklist, language);
let status_text_type = blacklist_status_text_type(ip_blacklist);
Column::new()
.width(Length::Fill)
@ -363,6 +369,44 @@ fn blacklist_selection<'a>(
button_clear_mmdb(message, is_editable)
}),
)
.push(Text::new(status).class(status_text_type).size(12))
}
fn blacklist_status_text(
custom_path: &str,
ip_blacklist: &IpBlacklist,
language: Language,
) -> String {
if custom_path.is_empty() {
return ip_blacklist_not_selected_translation(language).to_string();
}
match ip_blacklist.status() {
IpBlacklistLoadStatus::NotSelected => {
ip_blacklist_not_selected_translation(language).to_string()
}
IpBlacklistLoadStatus::Loading => ip_blacklist_loading_translation(language).to_string(),
IpBlacklistLoadStatus::FileReadError => {
ip_blacklist_file_read_error_translation(language).to_string()
}
IpBlacklistLoadStatus::NoValidEntries { .. } => {
ip_blacklist_no_valid_entries_translation(language).to_string()
}
IpBlacklistLoadStatus::Loaded {
ip_count,
network_count,
..
} => ip_blacklist_loaded_translation(language, *ip_count, *network_count),
}
}
fn blacklist_status_text_type(ip_blacklist: &IpBlacklist) -> TextType {
match ip_blacklist.status() {
IpBlacklistLoadStatus::FileReadError | IpBlacklistLoadStatus::NoValidEntries { .. } => {
TextType::Danger
}
_ => TextType::Standard,
}
}
fn button_clear_mmdb<'a>(

View File

@ -5,20 +5,40 @@
use ipnet::IpNet;
use prefix_trie::joint::set::JointPrefixSet;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum IpBlacklistLoadStatus {
#[default]
NotSelected,
Loading,
Loaded {
ip_count: usize,
network_count: usize,
ignored_lines: usize,
},
FileReadError,
NoValidEntries {
ignored_lines: usize,
},
}
#[derive(Clone, Default, Debug)]
pub struct IpBlacklist {
ips: Arc<HashSet<IpAddr>>,
networks: Arc<JointPrefixSet<IpNet>>,
is_loading: bool,
status: IpBlacklistLoadStatus,
}
impl IpBlacklist {
pub async fn from_file(path: String) -> Self {
let Ok(buf) = tokio::fs::read_to_string(&path).await else {
return IpBlacklist::default();
return IpBlacklist {
status: IpBlacklistLoadStatus::FileReadError,
..IpBlacklist::default()
};
};
let mut ips = HashSet::new();
let mut networks = JointPrefixSet::new();
let mut ignored_lines = 0;
for line in buf.lines() {
let Some(first) = line.split_whitespace().next() else {
continue;
@ -28,12 +48,26 @@ pub async fn from_file(path: String) -> Self {
ips.insert(ip);
} else if let Ok(network) = first.parse::<IpNet>() {
networks.insert(network);
} else {
ignored_lines += 1;
}
}
let ip_count = ips.len();
let network_count = networks.len();
let status = if ip_count == 0 && network_count == 0 {
IpBlacklistLoadStatus::NoValidEntries { ignored_lines }
} else {
IpBlacklistLoadStatus::Loaded {
ip_count,
network_count,
ignored_lines,
}
};
IpBlacklist {
ips: Arc::new(ips),
networks: Arc::new(networks),
is_loading: false,
status,
}
}
@ -42,15 +76,22 @@ pub fn contains(&self, ip: &IpAddr) -> bool {
}
pub fn is_invalid(&self) -> bool {
self.ips.is_empty() && self.networks.is_empty() && !self.is_loading
matches!(
self.status,
IpBlacklistLoadStatus::FileReadError | IpBlacklistLoadStatus::NoValidEntries { .. }
)
}
pub fn is_loading(&self) -> bool {
self.is_loading
matches!(self.status, IpBlacklistLoadStatus::Loading)
}
pub fn status(&self) -> &IpBlacklistLoadStatus {
&self.status
}
pub fn start_loading(&mut self) {
self.is_loading = true;
self.status = IpBlacklistLoadStatus::Loading;
}
}
@ -68,6 +109,14 @@ async fn test_ip_blacklist_valid() {
assert!(!blacklist.is_loading());
assert_eq!(blacklist.ips.len(), 4);
assert_eq!(blacklist.networks.len(), 0);
assert_eq!(
blacklist.status(),
&IpBlacklistLoadStatus::Loaded {
ip_count: 4,
network_count: 0,
ignored_lines: 4,
}
);
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))));
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(1, 2, 3, 255))));
@ -89,6 +138,10 @@ async fn test_ip_blacklist_invalid() {
assert!(!blacklist.is_loading());
assert_eq!(blacklist.ips.len(), 0);
assert_eq!(blacklist.networks.len(), 0);
assert_eq!(
blacklist.status(),
&IpBlacklistLoadStatus::NoValidEntries { ignored_lines: 6 }
);
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))));
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))));
@ -106,6 +159,14 @@ async fn test_ip_blacklist_valid_with_cidr() {
assert!(!blacklist.is_loading());
assert_eq!(blacklist.ips.len(), 2);
assert_eq!(blacklist.networks.len(), 4);
assert_eq!(
blacklist.status(),
&IpBlacklistLoadStatus::Loaded {
ip_count: 2,
network_count: 4,
ignored_lines: 3,
}
);
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))));
assert!(blacklist.contains(&"2001:db8::1".parse::<IpAddr>().unwrap()));
@ -137,6 +198,14 @@ async fn test_ip_blacklist_valid_with_cidr_only() {
assert!(!blacklist.is_loading());
assert_eq!(blacklist.ips.len(), 0);
assert_eq!(blacklist.networks.len(), 1);
assert_eq!(
blacklist.status(),
&IpBlacklistLoadStatus::Loaded {
ip_count: 0,
network_count: 1,
ignored_lines: 0,
}
);
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(1, 2, 3, 1))));
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(1, 2, 4, 1))));
@ -170,4 +239,28 @@ async fn test_ip_blacklist_real_cidr_ranges() {
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 237, 0))));
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 233, 160, 0))));
}
#[tokio::test]
async fn test_ip_blacklist_read_error() {
let blacklist =
IpBlacklist::from_file("resources/test/does_not_exist_blacklist.txt".to_string()).await;
assert!(blacklist.is_invalid());
assert!(!blacklist.is_loading());
assert_eq!(blacklist.ips.len(), 0);
assert_eq!(blacklist.networks.len(), 0);
assert_eq!(blacklist.status(), &IpBlacklistLoadStatus::FileReadError);
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))));
}
#[test]
fn test_ip_blacklist_loading_status() {
let mut blacklist = IpBlacklist::default();
blacklist.start_loading();
assert!(blacklist.is_loading());
assert!(!blacklist.is_invalid());
assert_eq!(blacklist.status(), &IpBlacklistLoadStatus::Loading);
}
}

View File

@ -86,6 +86,198 @@ pub fn ip_blacklist_translation(language: Language) -> &'static str {
}
}
pub fn ip_blacklist_not_selected_translation(language: Language) -> &'static str {
match language {
Language::EN => "No blacklist file selected.",
Language::CS => "Není vybrán žádný soubor blacklistu.",
Language::DE => "Keine Blacklist-Datei ausgewählt.",
Language::EL => "Δεν έχει επιλεγεί αρχείο λίστας αποκλεισμού.",
Language::ES => "No se ha seleccionado ningún archivo de blacklist.",
Language::FI => "Blacklist-tiedostoa ei ole valittu.",
Language::FR => "Aucun fichier de blacklist sélectionné.",
Language::ID => "Belum ada berkas daftar hitam yang dipilih.",
Language::IT => "Nessun file blacklist selezionato.",
Language::JA => "ブラックリストファイルが選択されていません。",
Language::KO => "선택된 블랙리스트 파일이 없습니다.",
Language::NL => "Geen blacklistbestand geselecteerd.",
Language::PL => "Nie wybrano pliku blacklisty.",
Language::PT => "Nenhum arquivo de blacklist selecionado.",
Language::RO => "Nu a fost selectat niciun fișier blacklist.",
Language::RU => "Файл черного списка не выбран.",
Language::SV => "Ingen svartlistfil har valts.",
Language::TR => "Kara liste dosyası seçilmedi.",
Language::UK => "Файл чорного списку не вибрано.",
Language::UZ => "Qora ro'yxat fayli tanlanmagan.",
Language::VI => "Chưa chọn tệp danh sách đen.",
Language::ZH => "未选择黑名单文件。",
Language::ZH_TW => "未選擇黑名單檔案。",
}
}
pub fn ip_blacklist_loading_translation(language: Language) -> &'static str {
match language {
Language::EN => "Loading blacklist...",
Language::CS => "Načítá se blacklist...",
Language::DE => "Blacklist wird geladen...",
Language::EL => "Φόρτωση λίστας αποκλεισμού...",
Language::ES => "Cargando blacklist...",
Language::FI => "Ladataan blacklistia...",
Language::FR => "Chargement de la blacklist...",
Language::ID => "Memuat daftar hitam...",
Language::IT => "Caricamento blacklist...",
Language::JA => "ブラックリストを読み込み中...",
Language::KO => "블랙리스트를 불러오는 중...",
Language::NL => "Blacklist laden...",
Language::PL => "Wczytywanie blacklisty...",
Language::PT => "Carregando blacklist...",
Language::RO => "Se încarcă blacklistul...",
Language::RU => "Загрузка черного списка...",
Language::SV => "Läser in svartlistan...",
Language::TR => "Kara liste yükleniyor...",
Language::UK => "Завантаження чорного списку...",
Language::UZ => "Qora ro'yxat yuklanmoqda...",
Language::VI => "Đang tải danh sách đen...",
Language::ZH => "正在加载黑名单...",
Language::ZH_TW => "正在載入黑名單...",
}
}
pub fn ip_blacklist_file_read_error_translation(language: Language) -> &'static str {
match language {
Language::EN => "Could not read blacklist file.",
Language::CS => "Soubor blacklistu nelze přečíst.",
Language::DE => "Blacklist-Datei konnte nicht gelesen werden.",
Language::EL => "Δεν ήταν δυνατή η ανάγνωση του αρχείου λίστας αποκλεισμού.",
Language::ES => "No se pudo leer el archivo de blacklist.",
Language::FI => "Blacklist-tiedostoa ei voitu lukea.",
Language::FR => "Impossible de lire le fichier de blacklist.",
Language::ID => "Berkas daftar hitam tidak dapat dibaca.",
Language::IT => "Impossibile leggere il file blacklist.",
Language::JA => "ブラックリストファイルを読み取れませんでした。",
Language::KO => "블랙리스트 파일을 읽을 수 없습니다.",
Language::NL => "Kan het blacklistbestand niet lezen.",
Language::PL => "Nie można odczytać pliku blacklisty.",
Language::PT => "Não foi possível ler o arquivo de blacklist.",
Language::RO => "Nu s-a putut citi fișierul blacklist.",
Language::RU => "Не удалось прочитать файл черного списка.",
Language::SV => "Kunde inte läsa svartlistfilen.",
Language::TR => "Kara liste dosyası okunamadı.",
Language::UK => "Не вдалося прочитати файл чорного списку.",
Language::UZ => "Qora ro'yxat faylini o'qib bo'lmadi.",
Language::VI => "Không thể đọc tệp danh sách đen.",
Language::ZH => "无法读取黑名单文件。",
Language::ZH_TW => "無法讀取黑名單檔案。",
}
}
pub fn ip_blacklist_no_valid_entries_translation(language: Language) -> &'static str {
match language {
Language::EN => "No valid IP addresses or CIDR ranges found.",
Language::CS => "Nebyly nalezeny žádné platné IP adresy ani rozsahy CIDR.",
Language::DE => "Keine gültigen IP-Adressen oder CIDR-Bereiche gefunden.",
Language::EL => "Δεν βρέθηκαν έγκυρες διευθύνσεις IP ή εύρη CIDR.",
Language::ES => "No se encontraron direcciones IP ni rangos CIDR válidos.",
Language::FI => "Kelvollisia IP-osoitteita tai CIDR-alueita ei löytynyt.",
Language::FR => "Aucune adresse IP ni plage CIDR valide trouvée.",
Language::ID => "Tidak ada alamat IP atau rentang CIDR yang valid ditemukan.",
Language::IT => "Nessun indirizzo IP o intervallo CIDR valido trovato.",
Language::JA => "有効な IP アドレスまたは CIDR 範囲が見つかりませんでした。",
Language::KO => "유효한 IP 주소 또는 CIDR 범위를 찾을 수 없습니다.",
Language::NL => "Geen geldige IP-adressen of CIDR-bereiken gevonden.",
Language::PL => "Nie znaleziono prawidłowych adresów IP ani zakresów CIDR.",
Language::PT => "Nenhum endereço IP ou intervalo CIDR válido encontrado.",
Language::RO => "Nu au fost găsite adrese IP sau intervale CIDR valide.",
Language::RU => "Действительные IP-адреса или диапазоны CIDR не найдены.",
Language::SV => "Inga giltiga IP-adresser eller CIDR-intervall hittades.",
Language::TR => "Geçerli IP adresi veya CIDR aralığı bulunamadı.",
Language::UK => "Дійсні IP-адреси або діапазони CIDR не знайдено.",
Language::UZ => "Yaroqli IP manzillar yoki CIDR oraliqlari topilmadi.",
Language::VI => "Không tìm thấy địa chỉ IP hoặc dải CIDR hợp lệ.",
Language::ZH => "未找到有效的 IP 地址或 CIDR 范围。",
Language::ZH_TW => "找不到有效的 IP 位址或 CIDR 範圍。",
}
}
pub fn ip_blacklist_loaded_translation(
language: Language,
ip_count: usize,
network_count: usize,
) -> String {
match language {
Language::EN => {
format!("Loaded {ip_count} IP addresses and {network_count} CIDR ranges.")
}
Language::CS => {
format!("Načteno {ip_count} IP adres a {network_count} rozsahů CIDR.")
}
Language::DE => {
format!("{ip_count} IP-Adressen und {network_count} CIDR-Bereiche geladen.")
}
Language::EL => {
format!("Φορτώθηκαν {ip_count} διευθύνσεις IP και {network_count} εύρη CIDR.")
}
Language::ES => {
format!("Cargadas {ip_count} direcciones IP y {network_count} rangos CIDR.")
}
Language::FI => {
format!("Ladattu {ip_count} IP-osoitetta ja {network_count} CIDR-aluetta.")
}
Language::FR => {
format!("{ip_count} adresses IP et {network_count} plages CIDR chargées.")
}
Language::ID => {
format!("Dimuat {ip_count} alamat IP dan {network_count} rentang CIDR.")
}
Language::IT => {
format!("Caricati {ip_count} indirizzi IP e {network_count} intervalli CIDR.")
}
Language::JA => {
format!(
"{ip_count} 件の IP アドレスと {network_count} 件の CIDR 範囲を読み込みました。"
)
}
Language::KO => {
format!("IP 주소 {ip_count}개와 CIDR 범위 {network_count}개를 불러왔습니다.")
}
Language::NL => {
format!("{ip_count} IP-adressen en {network_count} CIDR-bereiken geladen.")
}
Language::PL => {
format!("Wczytano adresów IP: {ip_count}, zakresów CIDR: {network_count}.")
}
Language::PT => {
format!("Carregados {ip_count} endereços IP e {network_count} intervalos CIDR.")
}
Language::RO => {
format!("Încărcate {ip_count} adrese IP și {network_count} intervale CIDR.")
}
Language::RU => {
format!("Загружено IP-адресов: {ip_count}, диапазонов CIDR: {network_count}.")
}
Language::SV => {
format!("{ip_count} IP-adresser och {network_count} CIDR-intervall har lästs in.")
}
Language::TR => {
format!("{ip_count} IP adresi ve {network_count} CIDR aralığı yüklendi.")
}
Language::UK => {
format!("Завантажено IP-адрес: {ip_count}, діапазонів CIDR: {network_count}.")
}
Language::UZ => {
format!("{ip_count} ta IP manzil va {network_count} ta CIDR oralig'i yuklandi.")
}
Language::VI => {
format!("Đã tải {ip_count} địa chỉ IP và {network_count} dải CIDR.")
}
Language::ZH => {
format!("已加载 {ip_count} 个 IP 地址和 {network_count} 个 CIDR 范围。")
}
Language::ZH_TW => {
format!("已載入 {ip_count} 個 IP 位址和 {network_count} 個 CIDR 範圍。")
}
}
}
pub fn blacklisted_transmitted_translation(language: Language) -> &'static str {
match language {
Language::EN => "New data exchanged from a blacklisted IP",