feat: add profile sync functionality and redesign desktop client UI
Add sync_profile command to fetch latest profile from backend without re-enrollment. Add DeviceView struct to EnrollResponse. Replace hardcoded "just now" timestamp with now_label helper using Unix epoch seconds. Add sync button to UI with loading state. Redesign client interface with top strip containing brand lockup and action buttons, hero surface with profile metadata tiles, body grid with login/status panels and resources sidebar
This commit is contained in:
@@ -4,6 +4,12 @@ use std::{
|
||||
process::{Command, ExitCode},
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match run() {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
@@ -33,14 +39,16 @@ fn run() -> Result<(), String> {
|
||||
fn connect(profile: &Path) -> Result<(), String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
ensure_windows_admin()?;
|
||||
let wireguard = find_windows_wireguard()?;
|
||||
let status = Command::new(wireguard)
|
||||
let output = Command::new(wireguard)
|
||||
.arg("/installtunnelservice")
|
||||
.arg(profile)
|
||||
.status()
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.output()
|
||||
.map_err(|err| format!("unable to launch WireGuard runtime: {err}"))?;
|
||||
if !status.success() {
|
||||
return Err(format!("WireGuard runtime connect failed with status {status}"));
|
||||
if !output.status.success() {
|
||||
return Err(format_windows_runtime_error("connect", &output));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@@ -66,18 +74,20 @@ fn connect(profile: &Path) -> Result<(), String> {
|
||||
fn disconnect(profile: &Path) -> Result<(), String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
ensure_windows_admin()?;
|
||||
let wireguard = find_windows_wireguard()?;
|
||||
let tunnel_name = profile
|
||||
.file_stem()
|
||||
.and_then(|value| value.to_str())
|
||||
.ok_or_else(|| "invalid profile filename".to_string())?;
|
||||
let status = Command::new(wireguard)
|
||||
let output = Command::new(wireguard)
|
||||
.arg("/uninstalltunnelservice")
|
||||
.arg(tunnel_name)
|
||||
.status()
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.output()
|
||||
.map_err(|err| format!("unable to launch WireGuard runtime: {err}"))?;
|
||||
if !status.success() {
|
||||
return Err(format!("WireGuard runtime disconnect failed with status {status}"));
|
||||
if !output.status.success() {
|
||||
return Err(format_windows_runtime_error("disconnect", &output));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@@ -112,3 +122,35 @@ fn find_windows_wireguard() -> Result<PathBuf, String> {
|
||||
.find(|path| path.exists())
|
||||
.ok_or_else(|| "required Windows tunnel runtime is not available".to_string())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn ensure_windows_admin() -> Result<(), String> {
|
||||
let status = Command::new("net")
|
||||
.arg("session")
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.status()
|
||||
.map_err(|err| format!("unable to determine Windows privilege level: {err}"))?;
|
||||
|
||||
if status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err("Administrator rights are required to activate the VPN tunnel on Windows. Start NexaVPN as Administrator for now.".into())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn format_windows_runtime_error(action: &str, output: &std::process::Output) -> String {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let details = if !stderr.trim().is_empty() {
|
||||
stderr.trim()
|
||||
} else {
|
||||
stdout.trim()
|
||||
};
|
||||
|
||||
if details.is_empty() {
|
||||
return format!("WireGuard runtime {} failed with status {}", action, output.status);
|
||||
}
|
||||
|
||||
format!("WireGuard runtime {} failed: {}", action, details)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user