cli: avoid data-dir initialization for version; create db parent dirs; redact generated passwords in CLI output

- Prevent 'nx9-wg version' from creating data directories by avoiding database initialization.
- Create parent directories when an explicit --database path is provided.
- Redact printed generated administrator passwords; announce file path or redact instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
thakaresandCopilot committed 2026-08-16 16:26:24 +05:30
commit 2ac6c81dfe
140 files changed
+31342

No files matched your search

@@ -0,0 +1,235 @@
//! Peer management table and responsive card component.
use chrono::{NaiveDateTime, Utc};
use nx9_wg_core::types::wireguard::{Peer, PeerProfile, PeerState, PeerType};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// Formatted peer row for table and mobile card representation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerRowView {
pub id: Uuid,
pub name: String,
pub peer_type: PeerType,
pub profile: PeerProfile,
pub state: PeerState,
pub is_online: bool,
pub status_label: String,
pub status_class: String,
pub ipv4: String,
pub ipv6: String,
pub public_key_full: String,
pub public_key_truncated: String,
pub rx_bytes_formatted: String,
pub tx_bytes_formatted: String,
pub last_handshake_relative: String,
pub expires_at: Option<NaiveDateTime>,
pub is_expired: bool,
}
impl PeerRowView {
pub fn from_peer_and_telemetry(
peer: &Peer,
rx_bytes: u64,
tx_bytes: u64,
last_handshake: Option<NaiveDateTime>,
) -> Self {
let now = Utc::now().naive_utc();
let is_expired =
peer.state == PeerState::Expired || peer.expires_at.is_some_and(|exp| exp <= now);
let is_online = if is_expired || peer.state != PeerState::Active {
false
} else if let Some(hs) = last_handshake.or(peer.last_handshake_at) {
let diff = now.signed_duration_since(hs);
diff.num_seconds() >= 0 && diff.num_seconds() < 180
} else {
false
};
let (status_label, status_class) = if is_expired {
("Expired".to_string(), "status-fail".to_string())
} else {
match peer.state {
PeerState::Active => {
if is_online {
("Online".to_string(), "status-pass".to_string())
} else {
("Offline".to_string(), "status-neutral".to_string())
}
}
PeerState::Disabled => ("Disabled".to_string(), "status-warning".to_string()),
PeerState::Revoked => ("Revoked".to_string(), "status-fail".to_string()),
PeerState::Expired => ("Expired".to_string(), "status-fail".to_string()),
}
};
let pub_key_str = peer.public_key.as_str();
let pub_key_truncated = if pub_key_str.len() > 12 {
format!(
"{}...{}",
&pub_key_str[..6],
&pub_key_str[pub_key_str.len() - 6..]
)
} else {
pub_key_str.to_string()
};
let last_handshake_relative = match last_handshake.or(peer.last_handshake_at) {
Some(hs) => format_relative_time(hs, now),
None => "Never".to_string(),
};
Self {
id: peer.id,
name: peer.name.clone(),
peer_type: peer.peer_type,
profile: peer.profile,
state: peer.state,
is_online,
status_label,
status_class,
ipv4: peer
.address_v4
.map(|ip| ip.to_string())
.unwrap_or_else(|| "—".to_string()),
ipv6: peer
.address_v6
.map(|ip| ip.to_string())
.unwrap_or_else(|| "—".to_string()),
public_key_full: pub_key_str.to_string(),
public_key_truncated: pub_key_truncated,
rx_bytes_formatted: format_bytes(rx_bytes),
tx_bytes_formatted: format_bytes(tx_bytes),
last_handshake_relative,
expires_at: peer.expires_at,
is_expired,
}
}
}
/// Filter state for peer table.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PeerTableFilter {
pub search_query: String,
pub status_filter: Option<String>,
}
impl PeerTableFilter {
pub fn matches(&self, row: &PeerRowView) -> bool {
if let Some(s) = self.status_filter.as_ref().filter(|s| !s.is_empty())
&& !row.status_label.eq_ignore_ascii_case(s)
{
return false;
}
if !self.search_query.trim().is_empty() {
let q = self.search_query.to_lowercase();
let name_match = row.name.to_lowercase().contains(&q);
let ip_match = row.ipv4.contains(&q) || row.ipv6.contains(&q);
let key_match = row.public_key_full.to_lowercase().contains(&q);
if !name_match && !ip_match && !key_match {
return false;
}
}
true
}
}
/// Format bytes into human readable binary units (B, KB, MB, GB, TB).
pub fn format_bytes(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
const TB: u64 = GB * 1024;
if bytes >= TB {
format!("{:.2} TB", bytes as f64 / TB as f64)
} else if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.1} KB", bytes as f64 / KB as f64)
} else {
format!("{bytes} B")
}
}
/// Format relative time between a past timestamp and reference now.
pub fn format_relative_time(past: NaiveDateTime, now: NaiveDateTime) -> String {
let diff = now.signed_duration_since(past);
let seconds = diff.num_seconds();
if seconds < 0 {
"Just now".to_string()
} else if seconds < 60 {
format!("{seconds}s ago")
} else if seconds < 3600 {
format!("{}m ago", seconds / 60)
} else if seconds < 86400 {
format!("{}h ago", seconds / 3600)
} else {
format!("{}d ago", seconds / 86400)
}
}
#[cfg(test)]
mod tests {
use super::*;
use nx9_wg_core::types::wireguard::WireGuardPublicKey;
#[test]
fn test_format_bytes() {
assert_eq!(format_bytes(500), "500 B");
assert_eq!(format_bytes(1024), "1.0 KB");
assert_eq!(format_bytes(1048576), "1.00 MB");
assert_eq!(format_bytes(1073741824), "1.00 GB");
}
#[test]
fn test_peer_row_view_and_filtering() {
let peer = Peer {
id: Uuid::new_v4(),
interface_id: Uuid::new_v4(),
name: "alice-laptop".to_string(),
peer_type: PeerType::RoadWarrior,
state: PeerState::Active,
public_key: WireGuardPublicKey::new(
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string(),
),
private_key: None,
preshared_key: None,
endpoint: None,
allowed_ips: "10.0.0.2/32".to_string(),
server_allowed_ips: None,
address_v4: Some("10.0.0.2/32".parse().unwrap()),
address_v6: None,
dns: None,
mtu: Some(1420),
persistent_keepalive: Some(25),
profile: PeerProfile::FullTunnel,
expires_at: None,
last_handshake_at: None,
created_at: Utc::now().naive_utc(),
updated_at: Utc::now().naive_utc(),
};
let row = PeerRowView::from_peer_and_telemetry(&peer, 1024, 2048, None);
assert_eq!(row.name, "alice-laptop");
assert_eq!(row.status_label, "Offline");
assert_eq!(row.rx_bytes_formatted, "1.0 KB");
assert_eq!(row.tx_bytes_formatted, "2.0 KB");
let filter = PeerTableFilter {
search_query: "alice".to_string(),
status_filter: None,
};
assert!(filter.matches(&row));
let non_matching = PeerTableFilter {
search_query: "bob".to_string(),
status_filter: None,
};
assert!(!non_matching.matches(&row));
}
}