use crate::core::scanner::ActiveSession;
use crate::core::telemetry::TelemetryUpdate;
use crate::state::{FocusedPanel, ToastType};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelectionMove {
Next,
Prev,
First,
Last,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollMove {
Up,
Down,
Top,
Bottom,
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub enum Message {
NextPanel,
PreviousPanel,
FocusPanel(FocusedPanel),
ToggleZoom,
ToggleFlip,
ProfileMove(SelectionMove),
ToggleConnect(Option<usize>),
Disconnect,
Reconnect,
ConnectSelected,
QuickConnect(usize),
CloseOverlay,
Toast(String, ToastType),
OpenConfig,
OpenDelete(Option<usize>),
ConfirmDelete,
ConfirmSwitch { idx: usize },
OpenActionMenu,
OpenBulkMenu,
Scroll(ScrollMove),
OpenImport,
Log(String),
CopyIp,
ClearLogs,
Quit,
Telemetry(TelemetryUpdate),
SyncSystemState(Vec<ActiveSession>),
Tick,
ConnectionTimeout(String),
NetworkChanged,
RetryConnect {
idx: usize,
attempt: u32,
},
ConnectResult {
profile: String,
success: bool,
error: Option<String>,
},
DisconnectResult {
profile: String,
success: bool,
error: Option<String>,
},
Resize(u16, u16),
Import(String),
AuthSubmit {
idx: usize,
username: String,
password: String,
save: bool,
connect_after: bool,
},
ManageAuth,
ClearAuth,
CycleSortOrder,
ToggleKillSwitch,
OpenRename,
OpenSearch,
OpenHelp,
CycleLogFilter,
}
#[derive(Debug, Clone)]
pub struct ActionMenuItem {
pub key: &'static str,
pub label: &'static str,
pub message: Message,
}
#[must_use]
pub fn get_single_actions(focused_panel: &FocusedPanel) -> Vec<ActionMenuItem> {
let mut actions = Vec::new();
match focused_panel {
FocusedPanel::Sidebar => {
actions.push(ActionMenuItem {
key: "i",
label: "Import Profiles",
message: Message::OpenImport,
});
actions.push(ActionMenuItem {
key: "c",
label: "Connect / Disconnect",
message: Message::ToggleConnect(None),
});
actions.push(ActionMenuItem {
key: "r",
label: "Reconnect",
message: Message::ConnectSelected,
});
actions.push(ActionMenuItem {
key: "v",
label: "View Configuration",
message: Message::OpenConfig,
});
actions.push(ActionMenuItem {
key: "a",
label: "Edit Auth Credentials",
message: Message::ManageAuth,
});
actions.push(ActionMenuItem {
key: "A",
label: "Clear Auth Credentials",
message: Message::ClearAuth,
});
actions.push(ActionMenuItem {
key: "DEL",
label: "Delete Profile",
message: Message::OpenDelete(None),
});
actions.push(ActionMenuItem {
key: "s",
label: "Sort Profiles",
message: Message::CycleSortOrder,
});
actions.push(ActionMenuItem {
key: "R",
label: "Rename Profile",
message: Message::OpenRename,
});
}
FocusedPanel::Logs => {
actions.push(ActionMenuItem {
key: "L",
label: "Clear Activity Logs",
message: Message::ClearLogs,
});
actions.push(ActionMenuItem {
key: "f",
label: "Filter Log Level",
message: Message::CycleLogFilter,
});
}
FocusedPanel::ConnectionDetails => {
actions.push(ActionMenuItem {
key: "y",
label: "Copy Public IP",
message: Message::CopyIp,
});
}
FocusedPanel::Security => {
actions.push(ActionMenuItem {
key: "K",
label: "Toggle Kill Switch",
message: Message::ToggleKillSwitch,
});
}
FocusedPanel::Chart => {}
}
if matches!(
focused_panel,
FocusedPanel::Chart | FocusedPanel::ConnectionDetails | FocusedPanel::Security
) {
actions.push(ActionMenuItem {
key: "f",
label: "Flip Panel (Front/Back)",
message: Message::ToggleFlip,
});
}
actions.push(ActionMenuItem {
key: "z",
label: "Toggle Zoom View",
message: Message::ToggleZoom,
});
actions
}
#[must_use]
pub fn get_bulk_actions() -> Vec<ActionMenuItem> {
vec![
ActionMenuItem {
key: "i",
label: "Import Profiles",
message: Message::OpenImport,
},
ActionMenuItem {
key: "r",
label: "Reconnect Last",
message: Message::Reconnect,
},
ActionMenuItem {
key: "D",
label: "Disconnect All",
message: Message::Disconnect,
},
ActionMenuItem {
key: "y",
label: "Copy Public IP",
message: Message::CopyIp,
},
ActionMenuItem {
key: "K",
label: "Toggle Kill Switch",
message: Message::ToggleKillSwitch,
},
ActionMenuItem {
key: "/",
label: "Search Profiles",
message: Message::OpenSearch,
},
ActionMenuItem {
key: "?",
label: "Help",
message: Message::OpenHelp,
},
ActionMenuItem {
key: "l",
label: "Next Panel",
message: Message::NextPanel,
},
ActionMenuItem {
key: "h",
label: "Previous Panel",
message: Message::PreviousPanel,
},
ActionMenuItem {
key: "q",
label: "Quit Vortix",
message: Message::Quit,
},
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sidebar_actions_include_connect() {
let actions = get_single_actions(&FocusedPanel::Sidebar);
assert!(actions.iter().any(|a| a.key == "c"));
assert!(actions.iter().any(|a| a.key == "i"));
assert!(actions.iter().any(|a| a.key == "v"));
assert!(actions.iter().any(|a| a.key == "a")); assert!(actions.iter().any(|a| a.key == "A")); assert!(actions.iter().any(|a| a.key == "DEL"));
assert!(actions.iter().any(|a| a.key == "s")); assert!(actions.iter().any(|a| a.key == "R")); assert!(actions.iter().any(|a| a.key == "z")); }
#[test]
fn test_logs_actions_include_clear_and_filter() {
let actions = get_single_actions(&FocusedPanel::Logs);
assert!(actions.iter().any(|a| a.key == "L"));
assert!(actions.iter().any(|a| a.key == "f")); assert!(actions.iter().any(|a| a.key == "z"));
}
#[test]
fn test_connection_details_actions_include_copy_ip_and_flip() {
let actions = get_single_actions(&FocusedPanel::ConnectionDetails);
assert!(actions.iter().any(|a| a.key == "y")); assert!(actions.iter().any(|a| a.key == "f")); }
#[test]
fn test_chart_actions_flip_and_zoom() {
let actions = get_single_actions(&FocusedPanel::Chart);
assert_eq!(actions.len(), 2);
assert!(actions.iter().any(|a| a.key == "f")); assert!(actions.iter().any(|a| a.key == "z")); }
#[test]
fn test_security_actions_include_killswitch_and_flip() {
let actions = get_single_actions(&FocusedPanel::Security);
assert!(actions.iter().any(|a| a.key == "K")); assert!(actions.iter().any(|a| a.key == "f")); assert!(actions.iter().any(|a| a.key == "z")); }
#[test]
fn test_bulk_actions_contains_essentials() {
let actions = get_bulk_actions();
assert!(actions.iter().any(|a| a.key == "i")); assert!(actions.iter().any(|a| a.key == "D")); assert!(actions.iter().any(|a| a.key == "q")); assert!(actions.iter().any(|a| a.key == "y")); assert!(actions.iter().any(|a| a.key == "K")); assert!(actions.iter().any(|a| a.key == "/")); assert!(actions.iter().any(|a| a.key == "?")); }
#[test]
fn test_bulk_actions_count() {
let actions = get_bulk_actions();
assert_eq!(actions.len(), 10);
}
#[test]
fn test_selection_move_variants() {
assert_eq!(SelectionMove::Next, SelectionMove::Next);
assert_ne!(SelectionMove::Next, SelectionMove::Prev);
assert_ne!(SelectionMove::First, SelectionMove::Last);
}
#[test]
fn test_scroll_move_variants() {
assert_eq!(ScrollMove::Up, ScrollMove::Up);
assert_ne!(ScrollMove::Up, ScrollMove::Down);
assert_ne!(ScrollMove::Top, ScrollMove::Bottom);
}
}