Add admin restart flow and loading indicators
This commit is contained in:
@@ -149,6 +149,15 @@ impl CommandError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdminStatusResponse {
|
||||
pub is_windows: bool,
|
||||
pub is_elevated: bool,
|
||||
pub can_restart_elevated: bool,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ValidationIssue {
|
||||
@@ -542,6 +551,18 @@ pub async fn get_status(
|
||||
.map_err(background_task_error)?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_admin_status() -> AdminStatusResponse {
|
||||
admin_status()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn restart_as_admin(app: tauri::AppHandle) -> Result<(), CommandError> {
|
||||
launch_app_as_admin()?;
|
||||
app.exit(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_saved_state(
|
||||
state: tauri::State<'_, CommandState>,
|
||||
@@ -814,6 +835,87 @@ pub async fn uninstall_singbox() -> Result<ComponentStatusDto, CommandError> {
|
||||
.map_err(background_task_error)?
|
||||
}
|
||||
|
||||
pub fn admin_status() -> AdminStatusResponse {
|
||||
let is_windows = cfg!(windows);
|
||||
let is_elevated = is_running_elevated();
|
||||
let message = if !is_windows {
|
||||
"Проверка прав администратора нужна только в Windows.".to_string()
|
||||
} else if is_elevated {
|
||||
"ProxyWarden уже запущен от имени администратора.".to_string()
|
||||
} else {
|
||||
"Для установки компонентов и управления службами можно перезапустить ProxyWarden от имени администратора один раз.".to_string()
|
||||
};
|
||||
|
||||
AdminStatusResponse {
|
||||
is_windows,
|
||||
is_elevated,
|
||||
can_restart_elevated: is_windows && !is_elevated,
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
fn launch_app_as_admin() -> Result<(), CommandError> {
|
||||
if !cfg!(windows) {
|
||||
return Err(CommandError::new(
|
||||
"admin_restart_unsupported",
|
||||
"Перезапуск от имени администратора доступен только в Windows.",
|
||||
));
|
||||
}
|
||||
|
||||
if is_running_elevated() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let exe_path = env::current_exe().map_err(|error| {
|
||||
CommandError::new(
|
||||
"admin_restart_failed",
|
||||
format!("Не удалось определить путь текущего приложения: {error}"),
|
||||
)
|
||||
})?;
|
||||
let working_dir = env::current_dir().ok();
|
||||
let working_dir_arg = working_dir
|
||||
.as_ref()
|
||||
.map(|path| {
|
||||
format!(
|
||||
" -WorkingDirectory '{}'",
|
||||
escape_powershell_single(&path.display().to_string())
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let script = format!(
|
||||
r#"
|
||||
$ErrorActionPreference = 'Stop'
|
||||
try {{
|
||||
Start-Process -FilePath '{}' -Verb RunAs{}
|
||||
exit 0
|
||||
}} catch {{
|
||||
Write-Error ($_ | Out-String)
|
||||
exit 1
|
||||
}}
|
||||
"#,
|
||||
escape_powershell_single(&exe_path.display().to_string()),
|
||||
working_dir_arg
|
||||
);
|
||||
let output = run_powershell_command(&script).map_err(|error| {
|
||||
CommandError::new(
|
||||
"admin_restart_failed",
|
||||
format!("Не удалось запросить права администратора: {error}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
if output.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(CommandError::new(
|
||||
"admin_restart_failed",
|
||||
powershell_output_message(
|
||||
&output,
|
||||
"Перезапуск от имени администратора отменен или не был запущен.",
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn build_status(storage: &JsonStorage) -> Result<StatusResponse, CommandError> {
|
||||
let profiles = storage.read_profiles().map_err(storage_error)?;
|
||||
let targets = storage.read_targets().map_err(storage_error)?;
|
||||
@@ -1933,16 +2035,11 @@ fn run_elevated_singbox_service_command(
|
||||
"$p = Start-Process -FilePath 'powershell.exe' -Verb RunAs -Wait -PassThru -WindowStyle Hidden -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File','{}'); exit $p.ExitCode",
|
||||
escape_powershell_single(&script_path.display().to_string())
|
||||
);
|
||||
let output = Command::new("powershell")
|
||||
.args([
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-Command",
|
||||
launch_script.as_str(),
|
||||
])
|
||||
.output();
|
||||
let output = if is_running_elevated() {
|
||||
run_powershell_file(&script_path)
|
||||
} else {
|
||||
run_powershell_command(&launch_script)
|
||||
};
|
||||
|
||||
let _ = fs::remove_file(&script_path);
|
||||
|
||||
@@ -2218,16 +2315,11 @@ try {{
|
||||
escape_powershell_single(&result_path.display().to_string()),
|
||||
escape_powershell_single(&runner_path.display().to_string())
|
||||
);
|
||||
let output = Command::new("powershell")
|
||||
.args([
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-Command",
|
||||
launch_script.as_str(),
|
||||
])
|
||||
.output();
|
||||
let output = if is_running_elevated() {
|
||||
run_powershell_file(&runner_path)
|
||||
} else {
|
||||
run_powershell_command(&launch_script)
|
||||
};
|
||||
|
||||
let _ = fs::remove_file(&installer_path);
|
||||
let _ = fs::remove_file(&runner_path);
|
||||
@@ -2781,16 +2873,11 @@ fn run_elevated_proxifyre_service_command(
|
||||
"$p = Start-Process -FilePath 'powershell.exe' -Verb RunAs -Wait -PassThru -WindowStyle Hidden -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File','{}'); exit $p.ExitCode",
|
||||
escape_powershell_single(&script_path.display().to_string())
|
||||
);
|
||||
let output = Command::new("powershell")
|
||||
.args([
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-Command",
|
||||
launch_script.as_str(),
|
||||
])
|
||||
.output();
|
||||
let output = if is_running_elevated() {
|
||||
run_powershell_file(&script_path)
|
||||
} else {
|
||||
run_powershell_command(&launch_script)
|
||||
};
|
||||
|
||||
let _ = fs::remove_file(&script_path);
|
||||
|
||||
@@ -3225,16 +3312,11 @@ try {{
|
||||
escape_powershell_single(&result_path.display().to_string()),
|
||||
escape_powershell_single(&script_path.display().to_string())
|
||||
);
|
||||
let output = Command::new("powershell")
|
||||
.args([
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-Command",
|
||||
launch_script.as_str(),
|
||||
])
|
||||
.output();
|
||||
let output = if is_running_elevated() {
|
||||
run_powershell_file(&script_path)
|
||||
} else {
|
||||
run_powershell_command(&launch_script)
|
||||
};
|
||||
|
||||
let _ = fs::remove_file(&script_path);
|
||||
|
||||
@@ -3296,6 +3378,62 @@ fn write_powershell_script(path: &Path, script: &str) -> std::io::Result<()> {
|
||||
fs::write(path, bytes)
|
||||
}
|
||||
|
||||
fn run_powershell_command(script: &str) -> std::io::Result<Output> {
|
||||
Command::new("powershell")
|
||||
.args([
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-Command",
|
||||
script,
|
||||
])
|
||||
.output()
|
||||
}
|
||||
|
||||
fn run_powershell_file(script_path: &Path) -> std::io::Result<Output> {
|
||||
Command::new("powershell")
|
||||
.args([
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
])
|
||||
.arg(script_path)
|
||||
.output()
|
||||
}
|
||||
|
||||
fn is_running_elevated() -> bool {
|
||||
if !cfg!(windows) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let script = r#"([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)"#;
|
||||
let Ok(output) = run_powershell_command(script) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
output.status.success()
|
||||
&& String::from_utf8_lossy(&output.stdout)
|
||||
.trim()
|
||||
.eq_ignore_ascii_case("true")
|
||||
}
|
||||
|
||||
fn powershell_output_message(output: &Output, fallback: &str) -> String {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||||
if !stderr.is_empty() {
|
||||
return stderr;
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if !stdout.is_empty() {
|
||||
return stdout;
|
||||
}
|
||||
|
||||
fallback.to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn install_proxifyre_script(generated_config_path: &Path) -> String {
|
||||
let mut script = String::new();
|
||||
script.push_str(&format!(
|
||||
|
||||
@@ -36,6 +36,8 @@ fn main() {
|
||||
.manage(commands::CommandState::default())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::get_status,
|
||||
commands::get_admin_status,
|
||||
commands::restart_as_admin,
|
||||
commands::get_profiles,
|
||||
commands::get_saved_state,
|
||||
commands::save_profile,
|
||||
|
||||
Reference in New Issue
Block a user