mirror of
https://github.com/OpenListTeam/OpenList-Desktop.git
synced 2025-11-25 03:14:56 +08:00
feat: optimize service control (#2)
* refactor: remove unused flags from RcloneConfig and related components; update monitoring intervals to use dynamic settings * fix: update auto-launch title to indicate immediate effect * refactor: remove service API token and port from AppConfig and related components * refactor: remove auto_mount property from RcloneConfig and related components * refactor: rename service to OpenList Core and update related components
This commit is contained in:
@@ -262,7 +262,6 @@ npm run tauri build
|
||||
"extraFlags": ["--vfs-cache-mode", "full"]
|
||||
}
|
||||
},
|
||||
"auto_mount": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -273,7 +272,6 @@ npm run tauri build
|
||||
{
|
||||
"app": {
|
||||
"theme": "auto",
|
||||
"language": "zh",
|
||||
"auto_update_enabled": true,
|
||||
"monitor_interval": 30000
|
||||
}
|
||||
|
||||
@@ -262,7 +262,6 @@ Add custom Rclone flags for optimal performance:
|
||||
"extraFlags": ["--vfs-cache-mode", "full"]
|
||||
}
|
||||
},
|
||||
"auto_mount": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -273,7 +272,6 @@ Add custom Rclone flags for optimal performance:
|
||||
{
|
||||
"app": {
|
||||
"theme": "auto",
|
||||
"language": "en",
|
||||
"auto_update_enabled": true,
|
||||
"monitor_interval": 30000
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use crate::core::service::check_service_status as check_service_status_impl;
|
||||
use crate::core::service::install_service as install_service_impl;
|
||||
use crate::core::service::restart_service as restart_service_impl;
|
||||
use crate::core::service::stop_service as stop_service_impl;
|
||||
use crate::core::service::start_service as start_service_impl;
|
||||
|
||||
use crate::core::service::uninstall_service as uninstall_service_impl;
|
||||
use crate::object::structs::AppState;
|
||||
use crate::utils::api::{get_api_key, get_server_port};
|
||||
use reqwest;
|
||||
use tauri::State;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_service_status() -> Result<bool, String> {
|
||||
pub async fn check_service_status() -> Result<String, String> {
|
||||
check_service_status_impl().await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -20,16 +24,24 @@ pub async fn uninstall_service() -> Result<bool, String> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn stop_service() -> Result<bool, String> {
|
||||
stop_service_impl().await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn restart_service() -> Result<bool, String> {
|
||||
restart_service_impl().await.map_err(|e| e.to_string())
|
||||
pub async fn stop_service(state: State<'_, AppState>) -> Result<bool, String> {
|
||||
let api_key = get_api_key(state);
|
||||
let port = get_server_port();
|
||||
let client = reqwest::Client::new();
|
||||
let response = client
|
||||
.post(format!("http://127.0.0.1:{}/api/v1/service/stop", port))
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send request: {}", e))?;
|
||||
if response.status().is_success() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(format!("Failed to stop service: {}", response.status()))
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn start_service() -> Result<bool, String> {
|
||||
check_service_status_impl().await.map_err(|e| e.to_string())
|
||||
start_service_impl().await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -4,18 +4,14 @@ use serde::{Deserialize, Serialize};
|
||||
pub struct AppConfig {
|
||||
pub theme: Option<String>,
|
||||
pub monitor_interval: Option<u64>,
|
||||
pub service_api_token: Option<String>,
|
||||
pub service_port: Option<u64>,
|
||||
pub auto_update_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
theme: Some("zh".into()),
|
||||
theme: Some("light".to_string()),
|
||||
monitor_interval: Some(5),
|
||||
service_api_token: Some("yeM6PCcZGaCpapyBKAbjTp2YAhcku6cUr".into()),
|
||||
service_port: Some(53211),
|
||||
auto_update_enabled: Some(true),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::conf::rclone::RcloneConfig;
|
||||
use crate::{conf::core::OpenListCoreConfig, utils::path::app_config_file_path};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::app::AppConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -25,20 +26,67 @@ impl MergedSettings {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_data_config_path() -> Result<PathBuf, String> {
|
||||
let app_dir = std::env::current_exe()
|
||||
.map_err(|e| format!("Failed to get current exe path: {}", e))?
|
||||
.parent()
|
||||
.ok_or("Failed to get parent directory")?
|
||||
.to_path_buf();
|
||||
Ok(app_dir.join("data").join("config.json"))
|
||||
}
|
||||
|
||||
fn read_data_config() -> Result<serde_json::Value, String> {
|
||||
let path = Self::get_data_config_path()?;
|
||||
if !path.exists() {
|
||||
return Err("data/config.json does not exist".to_string());
|
||||
}
|
||||
|
||||
let content = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
|
||||
serde_json::from_str(&content).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn get_port_from_data_config() -> Result<Option<u16>, String> {
|
||||
let config = Self::read_data_config()?;
|
||||
Ok(config
|
||||
.get("scheme")
|
||||
.and_then(|scheme| scheme.get("http_port"))
|
||||
.and_then(|port| port.as_u64())
|
||||
.map(|port| port as u16))
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<(), String> {
|
||||
let path = app_config_file_path().map_err(|e| e.to_string())?;
|
||||
std::fs::create_dir_all(path.parent().unwrap()).map_err(|e| e.to_string())?;
|
||||
let json = serde_json::to_string_pretty(self).map_err(|e| e.to_string())?;
|
||||
std::fs::write(&path, json).map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Self, String> {
|
||||
let path = app_config_file_path().map_err(|e| e.to_string())?;
|
||||
if !path.exists() {
|
||||
let mut merged_settings = if !path.exists() {
|
||||
std::fs::create_dir_all(path.parent().unwrap()).map_err(|e| e.to_string())?;
|
||||
let new_settings = Self::new();
|
||||
std::fs::write(
|
||||
&path,
|
||||
serde_json::to_string_pretty(&Self::new()).map_err(|e| e.to_string())?,
|
||||
serde_json::to_string_pretty(&new_settings).map_err(|e| e.to_string())?,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
return Ok(Self::new());
|
||||
new_settings
|
||||
} else {
|
||||
let config = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
|
||||
serde_json::from_str(&config).map_err(|e| e.to_string())?
|
||||
};
|
||||
|
||||
if let Ok(data_port) = Self::get_port_from_data_config() {
|
||||
if let Some(port) = data_port {
|
||||
if merged_settings.openlist.port != port {
|
||||
merged_settings.openlist.port = port;
|
||||
merged_settings.save()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let config = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
|
||||
let merged_settings: MergedSettings =
|
||||
serde_json::from_str(&config).map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(merged_settings)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct RcloneConfig {
|
||||
pub config: serde_json::Value,
|
||||
pub flags: Option<Vec<String>>,
|
||||
pub auto_mount: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -62,8 +60,6 @@ impl RcloneConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
config: serde_json::Value::Object(Default::default()),
|
||||
flags: None,
|
||||
auto_mount: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,63 +203,8 @@ fn start_service_with_elevation(service_name: &str) -> Result<bool, Box<dyn std:
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
log::info!("Service start not implemented for this platform");
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn install_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let app_dir = env::current_exe()?.parent().unwrap().to_path_buf();
|
||||
let install_path = app_dir.join("install-openlist-service");
|
||||
|
||||
if !install_path.exists() {
|
||||
error!("Service installer not found at {}", install_path.display());
|
||||
return Err(Box::from(format!(
|
||||
"Service installer not found at {}",
|
||||
install_path.display()
|
||||
)));
|
||||
}
|
||||
|
||||
let status = StdCommand::new(&install_path).status()?;
|
||||
|
||||
if status.success() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(Box::from(format!(
|
||||
"Failed to install service, exit status: {}",
|
||||
status
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn uninstall_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let app_dir = env::current_exe()?.parent().unwrap().to_path_buf();
|
||||
let uninstall_path = app_dir.join("uninstall-openlist-service");
|
||||
|
||||
if !uninstall_path.exists() {
|
||||
error!("Uninstaller not found: {:?}", uninstall_path);
|
||||
return Err(Box::from(format!(
|
||||
"Uninstaller not found: {:?}",
|
||||
uninstall_path
|
||||
)));
|
||||
}
|
||||
let status = StdCommand::new(&uninstall_path).status()?;
|
||||
|
||||
if status.success() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(Box::from(format!(
|
||||
"Failed to uninstall service, exit status: {}",
|
||||
status
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn check_service_status() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
use windows_service::service::{ServiceAccess, ServiceState};
|
||||
use windows_service::service_manager::{ServiceManager, ServiceManagerAccess};
|
||||
let service_name = "openlist_desktop_service";
|
||||
@@ -325,7 +270,7 @@ pub async fn check_service_status() -> Result<bool, Box<dyn std::error::Error>>
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn check_service_status() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
const SERVICE_NAME: &str = "openlist-desktop-service";
|
||||
|
||||
log::info!("Checking Linux service status for: {}", SERVICE_NAME);
|
||||
@@ -333,42 +278,17 @@ pub async fn check_service_status() -> Result<bool, Box<dyn std::error::Error>>
|
||||
let init_system = detect_linux_init_system();
|
||||
|
||||
match init_system.as_str() {
|
||||
"systemd" => check_systemd_service_status(SERVICE_NAME).await,
|
||||
"openrc" => check_openrc_service_status(SERVICE_NAME).await,
|
||||
"systemd" => start_systemd_service_with_check(SERVICE_NAME).await,
|
||||
"openrc" => start_openrc_service_with_check(SERVICE_NAME).await,
|
||||
_ => {
|
||||
log::warn!("Unknown init system: {}, assuming systemd", init_system);
|
||||
check_systemd_service_status(SERVICE_NAME).await
|
||||
start_systemd_service_with_check(SERVICE_NAME).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn detect_linux_init_system() -> String {
|
||||
if std::path::Path::new("/run/systemd/system").exists() {
|
||||
return "systemd".to_string();
|
||||
}
|
||||
|
||||
if std::path::Path::new("/run/openrc").exists() {
|
||||
return "openrc".to_string();
|
||||
}
|
||||
|
||||
if let Ok(output) = StdCommand::new("which").arg("systemctl").output() {
|
||||
if output.status.success() && !output.stdout.is_empty() {
|
||||
return "systemd".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(output) = StdCommand::new("which").arg("rc-service").output() {
|
||||
if output.status.success() && !output.stdout.is_empty() {
|
||||
return "openrc".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
"systemd".to_string()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn check_systemd_service_status(
|
||||
async fn start_systemd_service_with_check(
|
||||
service_name: &str,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
log::info!("Checking systemd service status for: {}", service_name);
|
||||
@@ -429,6 +349,249 @@ async fn check_systemd_service_status(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn start_openrc_service_with_check(
|
||||
service_name: &str,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
log::info!("Checking OpenRC service status for: {}", service_name);
|
||||
|
||||
let status_output = StdCommand::new("rc-service")
|
||||
.args(&[service_name, "status"])
|
||||
.output();
|
||||
|
||||
match status_output {
|
||||
Ok(output) => {
|
||||
let status_str = String::from_utf8_lossy(&output.stdout).to_lowercase();
|
||||
let stderr_str = String::from_utf8_lossy(&output.stderr).to_lowercase();
|
||||
|
||||
log::info!("OpenRC service status output: {}", status_str);
|
||||
|
||||
if status_str.contains("started") || status_str.contains("running") {
|
||||
log::info!("Service is running");
|
||||
return Ok(true);
|
||||
} else if status_str.contains("stopped") || status_str.contains("inactive") {
|
||||
log::info!("Service is stopped, attempting to start");
|
||||
return start_openrc_service(service_name).await;
|
||||
} else if stderr_str.contains("does not exist") {
|
||||
log::error!("Service {} does not exist", service_name);
|
||||
return Ok(false);
|
||||
} else {
|
||||
log::warn!("Unknown service status, attempting to start");
|
||||
return start_openrc_service(service_name).await;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to check OpenRC service status: {}", e);
|
||||
return start_openrc_service(service_name).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn install_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let app_dir = env::current_exe()?.parent().unwrap().to_path_buf();
|
||||
let install_path = app_dir.join("install-openlist-service");
|
||||
|
||||
if !install_path.exists() {
|
||||
error!("Service installer not found at {}", install_path.display());
|
||||
return Err(Box::from(format!(
|
||||
"Service installer not found at {}",
|
||||
install_path.display()
|
||||
)));
|
||||
}
|
||||
|
||||
let status = StdCommand::new(&install_path).status()?;
|
||||
|
||||
if status.success() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(Box::from(format!(
|
||||
"Failed to install service, exit status: {}",
|
||||
status
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn uninstall_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let app_dir = env::current_exe()?.parent().unwrap().to_path_buf();
|
||||
let uninstall_path = app_dir.join("uninstall-openlist-service");
|
||||
|
||||
if !uninstall_path.exists() {
|
||||
error!("Uninstaller not found: {:?}", uninstall_path);
|
||||
return Err(Box::from(format!(
|
||||
"Uninstaller not found: {:?}",
|
||||
uninstall_path
|
||||
)));
|
||||
}
|
||||
let status = StdCommand::new(&uninstall_path).status()?;
|
||||
|
||||
if status.success() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(Box::from(format!(
|
||||
"Failed to uninstall service, exit status: {}",
|
||||
status
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn check_service_status() -> Result<String, Box<dyn std::error::Error>> {
|
||||
use windows_service::service::{ServiceAccess, ServiceState};
|
||||
use windows_service::service_manager::{ServiceManager, ServiceManagerAccess};
|
||||
let service_name = "openlist_desktop_service";
|
||||
|
||||
let manager = match ServiceManager::local_computer(
|
||||
None::<&str>,
|
||||
ServiceManagerAccess::CONNECT | ServiceManagerAccess::ENUMERATE_SERVICE,
|
||||
) {
|
||||
Ok(mgr) => mgr,
|
||||
Err(_) => ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)?,
|
||||
};
|
||||
let service = match manager.open_service(service_name, ServiceAccess::QUERY_STATUS) {
|
||||
Ok(svc) => svc,
|
||||
Err(e) => {
|
||||
log::error!("Failed to open service '{}': {:?}", service_name, e);
|
||||
return Ok("not-installed".to_string());
|
||||
}
|
||||
};
|
||||
match service.query_status() {
|
||||
Ok(status) => match status.current_state {
|
||||
ServiceState::Running | ServiceState::StartPending => {
|
||||
return Ok("running".to_string());
|
||||
}
|
||||
ServiceState::StopPending => {
|
||||
std::thread::sleep(std::time::Duration::from_millis(1000));
|
||||
return Ok("stopped".to_string());
|
||||
}
|
||||
_ => {
|
||||
log::info!("Service is in state: {:?}.", status.current_state);
|
||||
return Ok("stopped".to_string());
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to query service status: {:?}", e);
|
||||
match start_service_with_elevation(service_name) {
|
||||
Ok(true) => Ok("running".to_string()),
|
||||
Ok(false) => {
|
||||
log::error!("Failed to start service with elevation.");
|
||||
Ok("stopped".to_string())
|
||||
}
|
||||
Err(elev_err) => {
|
||||
log::error!("Error during service elevation: {:?}", elev_err);
|
||||
Ok("error".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn check_service_status() -> Result<String, Box<dyn std::error::Error>> {
|
||||
const SERVICE_NAME: &str = "openlist-desktop-service";
|
||||
|
||||
log::info!("Checking Linux service status for: {}", SERVICE_NAME);
|
||||
|
||||
let init_system = detect_linux_init_system();
|
||||
|
||||
match init_system.as_str() {
|
||||
"systemd" => check_systemd_service_status(SERVICE_NAME).await,
|
||||
"openrc" => check_openrc_service_status(SERVICE_NAME).await,
|
||||
_ => {
|
||||
log::warn!("Unknown init system: {}, assuming systemd", init_system);
|
||||
check_systemd_service_status(SERVICE_NAME).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn detect_linux_init_system() -> String {
|
||||
if std::path::Path::new("/run/systemd/system").exists() {
|
||||
return "systemd".to_string();
|
||||
}
|
||||
|
||||
if std::path::Path::new("/run/openrc").exists() {
|
||||
return "openrc".to_string();
|
||||
}
|
||||
|
||||
if let Ok(output) = StdCommand::new("which").arg("systemctl").output() {
|
||||
if output.status.success() && !output.stdout.is_empty() {
|
||||
return "systemd".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(output) = StdCommand::new("which").arg("rc-service").output() {
|
||||
if output.status.success() && !output.stdout.is_empty() {
|
||||
return "openrc".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
"systemd".to_string()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn check_systemd_service_status(
|
||||
service_name: &str,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
log::info!("Checking systemd service status for: {}", service_name);
|
||||
|
||||
let status_output = StdCommand::new("systemctl")
|
||||
.args(&["is-active", service_name])
|
||||
.output();
|
||||
|
||||
match status_output {
|
||||
Ok(output) => {
|
||||
let status = String::from_utf8_lossy(&output.stdout)
|
||||
.trim()
|
||||
.to_lowercase();
|
||||
log::info!("Service {} status: {}", service_name, status);
|
||||
|
||||
match status.as_str() {
|
||||
"active" | "activating" => {
|
||||
log::info!("Service is active and running");
|
||||
return Ok("running".to_string());
|
||||
}
|
||||
"inactive" | "failed" => {
|
||||
log::info!("Service is {}", status);
|
||||
return Ok("stopped".to_string());
|
||||
}
|
||||
"unknown" => {
|
||||
log::warn!("Service status unknown, checking if service exists");
|
||||
let exists_output = StdCommand::new("systemctl")
|
||||
.args(&["list-unit-files", &format!("{}.service", service_name)])
|
||||
.output();
|
||||
|
||||
match exists_output {
|
||||
Ok(output) if output.status.success() => {
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
if output_str.contains(service_name) {
|
||||
log::info!("Service exists and not active");
|
||||
return Ok("stopped".to_string());
|
||||
} else {
|
||||
log::error!("Service {} not found", service_name);
|
||||
return Ok("not-installed".to_string());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log::error!("Failed to check if service exists");
|
||||
return Ok("error".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log::warn!("Unknown service status: {}", status);
|
||||
return Ok("error".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to check systemd service status: {}", e);
|
||||
return Ok("error".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn start_systemd_service(service_name: &str) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
use users::get_effective_uid;
|
||||
@@ -480,7 +643,7 @@ async fn start_systemd_service(service_name: &str) -> Result<bool, Box<dyn std::
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn check_openrc_service_status(
|
||||
service_name: &str,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
log::info!("Checking OpenRC service status for: {}", service_name);
|
||||
|
||||
let status_output = StdCommand::new("rc-service")
|
||||
@@ -496,21 +659,21 @@ async fn check_openrc_service_status(
|
||||
|
||||
if status_str.contains("started") || status_str.contains("running") {
|
||||
log::info!("Service is running");
|
||||
return Ok(true);
|
||||
return Ok("running".to_string());
|
||||
} else if status_str.contains("stopped") || status_str.contains("inactive") {
|
||||
log::info!("Service is stopped, attempting to start");
|
||||
return start_openrc_service(service_name).await;
|
||||
log::info!("Service is stopped");
|
||||
return Ok("stopped".to_string());
|
||||
} else if stderr_str.contains("does not exist") {
|
||||
log::error!("Service {} does not exist", service_name);
|
||||
return Ok(false);
|
||||
return Ok("not-installed".to_string());
|
||||
} else {
|
||||
log::warn!("Unknown service status, attempting to start");
|
||||
return start_openrc_service(service_name).await;
|
||||
return Ok("error".to_string());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to check OpenRC service status: {}", e);
|
||||
return start_openrc_service(service_name).await;
|
||||
return Ok("error".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -562,7 +725,7 @@ async fn start_openrc_service(service_name: &str) -> Result<bool, Box<dyn std::e
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn check_service_status() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
const SERVICE_IDENTIFIER: &str = "io.github.openlistteam.openlist.service";
|
||||
|
||||
log::info!("Checking macOS service status for: {}", SERVICE_IDENTIFIER);
|
||||
@@ -624,6 +787,64 @@ pub async fn check_service_status() -> Result<bool, Box<dyn std::error::Error>>
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn check_service_status() -> Result<String, Box<dyn std::error::Error>> {
|
||||
const SERVICE_IDENTIFIER: &str = "io.github.openlistteam.openlist.service";
|
||||
|
||||
log::info!("Checking macOS service status for: {}", SERVICE_IDENTIFIER);
|
||||
|
||||
let status_output = StdCommand::new("launchctl")
|
||||
.args(&["list", SERVICE_IDENTIFIER])
|
||||
.output();
|
||||
|
||||
match status_output {
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
log::info!("launchctl list output: {}", output_str);
|
||||
|
||||
if let Some(pid_value) = extract_plist_value(&output_str, "PID") {
|
||||
log::info!("Extracted PID value: {}", pid_value);
|
||||
if let Ok(pid) = pid_value.parse::<i32>() {
|
||||
if pid > 0 {
|
||||
log::info!("Service is running with PID: {}", pid);
|
||||
return Ok("running".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(exit_status) = extract_plist_value(&output_str, "LastExitStatus") {
|
||||
if let Ok(status) = exit_status.parse::<i32>() {
|
||||
if status == 0 {
|
||||
log::info!("Service is loaded but not running (clean exit)");
|
||||
return Ok("stopped".to_string());
|
||||
} else {
|
||||
log::warn!("Service has non-zero exit status: {}", status);
|
||||
return Ok("stopped".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Service appears to be loaded but status unclear");
|
||||
return Ok("error".to_string());
|
||||
} else {
|
||||
let stderr_str = String::from_utf8_lossy(&output.stderr);
|
||||
if stderr_str.contains("Could not find service") {
|
||||
log::error!("Service {} is not loaded", SERVICE_IDENTIFIER);
|
||||
return Ok("not-installed".to_string());
|
||||
} else {
|
||||
log::warn!("launchctl list failed");
|
||||
return Ok("error".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to check macOS service status: {}", e);
|
||||
return Ok("error".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn start_macos_service(service_identifier: &str) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
log::info!("Attempting to start macOS service: {}", service_identifier);
|
||||
@@ -694,412 +915,3 @@ fn extract_plist_value(plist_output: &str, key: &str) -> Option<String> {
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn restart_macos_service(
|
||||
service_identifier: &str,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
log::info!(
|
||||
"Attempting to restart macOS service: {}",
|
||||
service_identifier
|
||||
);
|
||||
|
||||
let _ = StdCommand::new("launchctl")
|
||||
.args(&["stop", service_identifier])
|
||||
.status();
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
|
||||
start_macos_service(service_identifier).await
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn stop_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
use runas::Command as RunasCommand;
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
let service_name = "openlist_desktop_service";
|
||||
log::info!("Attempting to stop Windows service: {}", service_name);
|
||||
|
||||
let token = Token::with_current_process()?;
|
||||
let level = token.privilege_level()?;
|
||||
|
||||
let powershell_cmd = format!("Stop-Service -Name '{}' -Force", service_name);
|
||||
|
||||
let status = match level {
|
||||
PrivilegeLevel::NotPrivileged => {
|
||||
log::info!("Running without admin privileges, using runas for elevation");
|
||||
RunasCommand::new("powershell.exe")
|
||||
.args(&["-Command", &powershell_cmd])
|
||||
.show(false)
|
||||
.status()?
|
||||
}
|
||||
_ => {
|
||||
log::info!("Already have admin privileges, running directly");
|
||||
StdCommand::new("powershell.exe")
|
||||
.args(&["-Command", &powershell_cmd])
|
||||
.creation_flags(0x08000000)
|
||||
.status()?
|
||||
}
|
||||
};
|
||||
|
||||
if status.success() {
|
||||
log::info!("Service stopped successfully");
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
Ok(true)
|
||||
} else {
|
||||
log::error!("Failed to stop service, exit code: {}", status);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn stop_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
const SERVICE_NAME: &str = "openlist-desktop-service";
|
||||
|
||||
log::info!("Attempting to stop Linux service: {}", SERVICE_NAME);
|
||||
|
||||
let init_system = detect_linux_init_system();
|
||||
|
||||
match init_system.as_str() {
|
||||
"systemd" => stop_systemd_service(SERVICE_NAME).await,
|
||||
"openrc" => stop_openrc_service(SERVICE_NAME).await,
|
||||
_ => {
|
||||
log::warn!("Unknown init system: {}, assuming systemd", init_system);
|
||||
stop_systemd_service(SERVICE_NAME).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn stop_systemd_service(service_name: &str) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
use users::get_effective_uid;
|
||||
|
||||
log::info!("Attempting to stop systemd service: {}", service_name);
|
||||
|
||||
let status = match get_effective_uid() {
|
||||
0 => StdCommand::new("systemctl")
|
||||
.args(&["stop", service_name])
|
||||
.status()?,
|
||||
_ => {
|
||||
let elevator = linux_elevator();
|
||||
log::info!("Using {} for elevation", elevator);
|
||||
|
||||
StdCommand::new(&elevator)
|
||||
.args(&["systemctl", "stop", service_name])
|
||||
.status()?
|
||||
}
|
||||
};
|
||||
|
||||
if status.success() {
|
||||
log::info!("Service stop command completed");
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
||||
let verify_output = StdCommand::new("systemctl")
|
||||
.args(&["is-active", service_name])
|
||||
.output()?;
|
||||
|
||||
let verify_status_str = String::from_utf8_lossy(&verify_output.stdout);
|
||||
let verify_status = verify_status_str.trim();
|
||||
let is_stopped = verify_status == "inactive" || verify_status == "failed";
|
||||
|
||||
if is_stopped {
|
||||
log::info!("Service verified as stopped");
|
||||
} else {
|
||||
log::warn!(
|
||||
"Service stop command succeeded but service is still active: {}",
|
||||
verify_status
|
||||
);
|
||||
}
|
||||
|
||||
Ok(is_stopped)
|
||||
} else {
|
||||
log::error!("Failed to stop systemd service, exit code: {}", status);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn stop_openrc_service(service_name: &str) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
use users::get_effective_uid;
|
||||
|
||||
log::info!("Attempting to stop OpenRC service: {}", service_name);
|
||||
let status = match get_effective_uid() {
|
||||
0 => StdCommand::new("rc-service")
|
||||
.args(&[service_name, "stop"])
|
||||
.status()?,
|
||||
_ => {
|
||||
let elevator = linux_elevator();
|
||||
log::info!("Using {} for elevation", elevator);
|
||||
|
||||
StdCommand::new(&elevator)
|
||||
.args(&["rc-service", service_name, "stop"])
|
||||
.status()?
|
||||
}
|
||||
};
|
||||
|
||||
if status.success() {
|
||||
log::info!("Service stop command completed");
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
||||
let verify_output = StdCommand::new("rc-service")
|
||||
.args(&[service_name, "status"])
|
||||
.output()?;
|
||||
|
||||
let verify_status = String::from_utf8_lossy(&verify_output.stdout).to_lowercase();
|
||||
let is_stopped = verify_status.contains("stopped") || verify_status.contains("inactive");
|
||||
|
||||
if is_stopped {
|
||||
log::info!("Service verified as stopped");
|
||||
} else {
|
||||
log::warn!(
|
||||
"Service stop command succeeded but service is still running: {}",
|
||||
verify_status
|
||||
);
|
||||
}
|
||||
|
||||
Ok(is_stopped)
|
||||
} else {
|
||||
log::error!("Failed to stop OpenRC service, exit code: {}", status);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn stop_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
const SERVICE_IDENTIFIER: &str = "io.github.openlistteam.openlist.service";
|
||||
|
||||
log::info!("Attempting to stop macOS service: {}", SERVICE_IDENTIFIER);
|
||||
|
||||
let status = StdCommand::new("launchctl")
|
||||
.args(&["stop", SERVICE_IDENTIFIER])
|
||||
.status()?;
|
||||
|
||||
if status.success() {
|
||||
log::info!("Service stop command completed");
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
||||
let verify_output = StdCommand::new("launchctl")
|
||||
.args(&["list", SERVICE_IDENTIFIER])
|
||||
.output()?;
|
||||
|
||||
if verify_output.status.success() {
|
||||
let output_str = String::from_utf8_lossy(&verify_output.stdout);
|
||||
log::info!("Verification output after stop: {}", output_str);
|
||||
|
||||
if let Some(pid_value) = extract_plist_value(&output_str, "PID") {
|
||||
if let Ok(pid) = pid_value.parse::<i32>() {
|
||||
let is_stopped = pid <= 0;
|
||||
|
||||
if is_stopped {
|
||||
log::info!("Service verified as stopped");
|
||||
} else {
|
||||
log::warn!(
|
||||
"Service stop command succeeded but service is still running with PID: {}",
|
||||
pid
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(is_stopped);
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("No PID found in output, service appears to be stopped");
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
log::info!("Could not verify service status after stop, assuming success");
|
||||
Ok(true)
|
||||
} else {
|
||||
log::error!("Failed to stop macOS service, exit code: {}", status);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn restart_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
use runas::Command as RunasCommand;
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
let service_name = "openlist_desktop_service";
|
||||
log::info!("Attempting to restart Windows service: {}", service_name);
|
||||
|
||||
let powershell_cmd = format!("Restart-Service -Name '{}' -Force", service_name);
|
||||
|
||||
let status = {
|
||||
let token = Token::with_current_process()?;
|
||||
let level = token.privilege_level()?;
|
||||
|
||||
match level {
|
||||
PrivilegeLevel::NotPrivileged => {
|
||||
log::info!("Running without admin privileges, using runas for elevation");
|
||||
RunasCommand::new("powershell.exe")
|
||||
.args(&["-Command", &powershell_cmd])
|
||||
.show(false)
|
||||
.status()?
|
||||
}
|
||||
_ => {
|
||||
log::info!("Already have admin privileges, running directly");
|
||||
StdCommand::new("powershell.exe")
|
||||
.args(&["-Command", &powershell_cmd])
|
||||
.creation_flags(0x08000000)
|
||||
.status()?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if status.success() {
|
||||
log::info!("Service restart command completed");
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
match check_service_status().await {
|
||||
Ok(true) => {
|
||||
log::info!("Service verified as running after restart");
|
||||
Ok(true)
|
||||
}
|
||||
Ok(false) => {
|
||||
log::warn!("Service restart command succeeded but service is not running");
|
||||
Ok(false)
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Error verifying service status after restart: {}", e);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to restart service, exit code: {}", status);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn restart_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
const SERVICE_NAME: &str = "openlist-desktop-service";
|
||||
|
||||
log::info!("Attempting to restart Linux service: {}", SERVICE_NAME);
|
||||
|
||||
let init_system = detect_linux_init_system();
|
||||
|
||||
match init_system.as_str() {
|
||||
"systemd" => restart_systemd_service(SERVICE_NAME).await,
|
||||
"openrc" => restart_openrc_service(SERVICE_NAME).await,
|
||||
_ => {
|
||||
log::warn!("Unknown init system: {}, assuming systemd", init_system);
|
||||
restart_systemd_service(SERVICE_NAME).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn restart_systemd_service(service_name: &str) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
use users::get_effective_uid;
|
||||
|
||||
log::info!("Attempting to restart systemd service: {}", service_name);
|
||||
let status = match get_effective_uid() {
|
||||
0 => StdCommand::new("systemctl")
|
||||
.args(&["restart", service_name])
|
||||
.status()?,
|
||||
_ => {
|
||||
let elevator = linux_elevator();
|
||||
log::info!("Using {} for elevation", elevator);
|
||||
|
||||
StdCommand::new(&elevator)
|
||||
.args(&["systemctl", "restart", service_name])
|
||||
.status()?
|
||||
}
|
||||
};
|
||||
|
||||
if status.success() {
|
||||
log::info!("Service restart command completed");
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
let verify_output = StdCommand::new("systemctl")
|
||||
.args(&["is-active", service_name])
|
||||
.output()?;
|
||||
|
||||
let verify_status_str = String::from_utf8_lossy(&verify_output.stdout);
|
||||
let verify_status = verify_status_str.trim();
|
||||
let is_running = verify_status == "active" || verify_status == "activating";
|
||||
|
||||
if is_running {
|
||||
log::info!("Service verified as running after restart");
|
||||
} else {
|
||||
log::warn!(
|
||||
"Service restart command succeeded but service is not active: {}",
|
||||
verify_status
|
||||
);
|
||||
}
|
||||
|
||||
Ok(is_running)
|
||||
} else {
|
||||
log::error!("Failed to restart systemd service, exit code: {}", status);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn restart_openrc_service(service_name: &str) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
use users::get_effective_uid;
|
||||
|
||||
log::info!("Attempting to restart OpenRC service: {}", service_name);
|
||||
let status = match get_effective_uid() {
|
||||
0 => StdCommand::new("rc-service")
|
||||
.args(&[service_name, "restart"])
|
||||
.status()?,
|
||||
_ => {
|
||||
let elevator = linux_elevator();
|
||||
log::info!("Using {} for elevation", elevator);
|
||||
|
||||
StdCommand::new(&elevator)
|
||||
.args(&["rc-service", service_name, "restart"])
|
||||
.status()?
|
||||
}
|
||||
};
|
||||
|
||||
if status.success() {
|
||||
log::info!("Service restart command completed");
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
let verify_output = StdCommand::new("rc-service")
|
||||
.args(&[service_name, "status"])
|
||||
.output()?;
|
||||
|
||||
let verify_status = String::from_utf8_lossy(&verify_output.stdout).to_lowercase();
|
||||
let is_running = verify_status.contains("started") || verify_status.contains("running");
|
||||
|
||||
if is_running {
|
||||
log::info!("Service verified as running after restart");
|
||||
} else {
|
||||
log::warn!(
|
||||
"Service restart command succeeded but service is not running: {}",
|
||||
verify_status
|
||||
);
|
||||
}
|
||||
|
||||
Ok(is_running)
|
||||
} else {
|
||||
log::error!("Failed to restart OpenRC service, exit code: {}", status);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn restart_service() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
const SERVICE_IDENTIFIER: &str = "io.github.openlistteam.openlist.service";
|
||||
|
||||
log::info!(
|
||||
"Attempting to restart macOS service: {}",
|
||||
SERVICE_IDENTIFIER
|
||||
);
|
||||
|
||||
let stop_result = stop_service().await?;
|
||||
if !stop_result {
|
||||
log::warn!("Failed to stop service, but continuing with restart attempt");
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
|
||||
start_macos_service(SERVICE_IDENTIFIER).await
|
||||
}
|
||||
|
||||
@@ -34,8 +34,7 @@ use cmd::http_api::{
|
||||
};
|
||||
|
||||
use cmd::service::{
|
||||
check_service_status, install_service, restart_service, start_service, stop_service,
|
||||
uninstall_service,
|
||||
check_service_status, install_service, start_service, stop_service, uninstall_service,
|
||||
};
|
||||
|
||||
use object::structs::*;
|
||||
@@ -160,7 +159,6 @@ pub fn run() {
|
||||
check_service_status,
|
||||
stop_service,
|
||||
start_service,
|
||||
restart_service,
|
||||
check_for_updates,
|
||||
download_update,
|
||||
install_update_and_restart,
|
||||
|
||||
@@ -15,21 +15,31 @@ pub fn create_tray(app_handle: &AppHandle) -> tauri::Result<()> {
|
||||
let hide_i = MenuItem::with_id(app_handle, "hide", "隐藏窗口", true, None::<&str>)?;
|
||||
let restart_i = MenuItem::with_id(app_handle, "restart", "重启应用", true, None::<&str>)?;
|
||||
|
||||
let start_service_i =
|
||||
MenuItem::with_id(app_handle, "start_service", "启动服务", true, None::<&str>)?;
|
||||
let stop_service_i =
|
||||
MenuItem::with_id(app_handle, "stop_service", "停止服务", true, None::<&str>)?;
|
||||
let start_service_i = MenuItem::with_id(
|
||||
app_handle,
|
||||
"start_service",
|
||||
"启动OpenList",
|
||||
true,
|
||||
None::<&str>,
|
||||
)?;
|
||||
let stop_service_i = MenuItem::with_id(
|
||||
app_handle,
|
||||
"stop_service",
|
||||
"停止OpenList",
|
||||
true,
|
||||
None::<&str>,
|
||||
)?;
|
||||
let restart_service_i = MenuItem::with_id(
|
||||
app_handle,
|
||||
"restart_service",
|
||||
"重启服务",
|
||||
"重启OpenList",
|
||||
true,
|
||||
None::<&str>,
|
||||
)?;
|
||||
let service_submenu = Submenu::with_id_and_items(
|
||||
app_handle,
|
||||
"service",
|
||||
"服务控制",
|
||||
"核心控制",
|
||||
true,
|
||||
&[&start_service_i, &stop_service_i, &restart_service_i],
|
||||
)?;
|
||||
@@ -113,15 +123,15 @@ fn handle_menu_event(app_handle: &AppHandle, event: tauri::menu::MenuEvent) {
|
||||
}
|
||||
"start_service" => {
|
||||
log::info!("Start service menu item clicked");
|
||||
handle_service_action(app_handle, "start");
|
||||
handle_core_action(app_handle, "start");
|
||||
}
|
||||
"stop_service" => {
|
||||
log::info!("Stop service menu item clicked");
|
||||
handle_service_action(app_handle, "stop");
|
||||
handle_core_action(app_handle, "stop");
|
||||
}
|
||||
"restart_service" => {
|
||||
log::info!("Restart service menu item clicked");
|
||||
handle_service_action(app_handle, "restart");
|
||||
handle_core_action(app_handle, "restart");
|
||||
}
|
||||
_ => {
|
||||
log::debug!("Unknown menu item clicked: {:?}", event.id());
|
||||
@@ -144,21 +154,21 @@ pub fn update_tray_menu(app_handle: &AppHandle, service_running: bool) -> tauri:
|
||||
let start_service_i = MenuItem::with_id(
|
||||
app_handle,
|
||||
"start_service",
|
||||
"启动服务",
|
||||
"启动OpenList",
|
||||
!service_running,
|
||||
None::<&str>,
|
||||
)?;
|
||||
let stop_service_i = MenuItem::with_id(
|
||||
app_handle,
|
||||
"stop_service",
|
||||
"停止服务",
|
||||
"停止OpenList",
|
||||
service_running,
|
||||
None::<&str>,
|
||||
)?;
|
||||
let restart_service_i = MenuItem::with_id(
|
||||
app_handle,
|
||||
"restart_service",
|
||||
"重启服务",
|
||||
"重启OpenList",
|
||||
service_running,
|
||||
None::<&str>,
|
||||
)?;
|
||||
@@ -166,7 +176,7 @@ pub fn update_tray_menu(app_handle: &AppHandle, service_running: bool) -> tauri:
|
||||
let service_submenu = Submenu::with_id_and_items(
|
||||
app_handle,
|
||||
"service",
|
||||
"服务控制",
|
||||
"核心控制",
|
||||
true,
|
||||
&[&start_service_i, &stop_service_i, &restart_service_i],
|
||||
)?;
|
||||
@@ -212,14 +222,14 @@ pub fn update_tray_menu_delayed(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_service_action(app_handle: &AppHandle, action: &str) {
|
||||
log::info!("Handling service action from tray: {}", action);
|
||||
fn handle_core_action(app_handle: &AppHandle, action: &str) {
|
||||
log::info!("Handling core action from tray: {}", action);
|
||||
|
||||
if let Err(e) = app_handle.emit("tray-service-action", action) {
|
||||
log::error!("Failed to emit tray service action event: {}", e);
|
||||
if let Err(e) = app_handle.emit("tray-core-action", action) {
|
||||
log::error!("Failed to emit tray core action event: {}", e);
|
||||
}
|
||||
|
||||
log::debug!("Service action '{}' dispatched to frontend", action);
|
||||
log::debug!("Core action '{}' dispatched to frontend", action);
|
||||
}
|
||||
|
||||
pub fn force_update_tray_menu(app_handle: &AppHandle, service_running: bool) -> tauri::Result<()> {
|
||||
@@ -227,21 +237,21 @@ pub fn force_update_tray_menu(app_handle: &AppHandle, service_running: bool) ->
|
||||
let start_service_i = MenuItem::with_id(
|
||||
app_handle,
|
||||
"start_service",
|
||||
"启动服务",
|
||||
"启动OpenList",
|
||||
!service_running,
|
||||
None::<&str>,
|
||||
)?;
|
||||
let stop_service_i = MenuItem::with_id(
|
||||
app_handle,
|
||||
"stop_service",
|
||||
"停止服务",
|
||||
"停止OpenList",
|
||||
service_running,
|
||||
None::<&str>,
|
||||
)?;
|
||||
let restart_service_i = MenuItem::with_id(
|
||||
app_handle,
|
||||
"restart_service",
|
||||
"重启服务",
|
||||
"重启OpenList",
|
||||
service_running,
|
||||
None::<&str>,
|
||||
)?;
|
||||
@@ -249,7 +259,7 @@ pub fn force_update_tray_menu(app_handle: &AppHandle, service_running: bool) ->
|
||||
let service_submenu = Submenu::with_id_and_items(
|
||||
app_handle,
|
||||
"service",
|
||||
"服务控制",
|
||||
"核心控制",
|
||||
true,
|
||||
&[&start_service_i, &stop_service_i, &restart_service_i],
|
||||
)?;
|
||||
|
||||
@@ -51,7 +51,7 @@ onMounted(async () => {
|
||||
try {
|
||||
store.init()
|
||||
store.applyTheme(store.settings.app.theme || 'light')
|
||||
await updateTrayMenu(store.serviceStatus.running)
|
||||
await updateTrayMenu(store.openlistCoreStatus.running)
|
||||
|
||||
try {
|
||||
updateUnlisten = await TauriAPI.listenToBackgroundUpdateAvailable(updateInfo => {
|
||||
|
||||
@@ -6,18 +6,18 @@ import {
|
||||
DownloadProgress,
|
||||
FileItem,
|
||||
MergedSettings,
|
||||
OpenListCoreStatus,
|
||||
ProcessConfig,
|
||||
ProcessStatus,
|
||||
RcloneMountInfo,
|
||||
RcloneWebdavConfig,
|
||||
ServiceStatus,
|
||||
TauriResponse,
|
||||
UpdateCheck
|
||||
} from '../types'
|
||||
|
||||
export class TauriAPI {
|
||||
// openlist desktop service management
|
||||
static async checkServiceStatus(): Promise<boolean> {
|
||||
static async checkServiceStatus(): Promise<string> {
|
||||
return await invoke('check_service_status')
|
||||
}
|
||||
|
||||
@@ -37,10 +37,6 @@ export class TauriAPI {
|
||||
return await invoke('stop_service')
|
||||
}
|
||||
|
||||
static async restartOpenListService(): Promise<boolean> {
|
||||
return await invoke('restart_service')
|
||||
}
|
||||
|
||||
// http API management
|
||||
static async getProcessList(): Promise<ProcessStatus[]> {
|
||||
return await invoke('get_process_list')
|
||||
@@ -68,7 +64,7 @@ export class TauriAPI {
|
||||
return await invoke('create_openlist_core_process', { autoStart })
|
||||
}
|
||||
|
||||
static async getOpenListCoreStatus(): Promise<ServiceStatus> {
|
||||
static async getOpenListCoreStatus(): Promise<OpenListCoreStatus> {
|
||||
return await invoke('get_openlist_core_status')
|
||||
}
|
||||
|
||||
@@ -229,7 +225,7 @@ export class TauriAPI {
|
||||
|
||||
// Tray event listeners
|
||||
static async listenToTrayServiceActions(callback: (action: string) => void) {
|
||||
return await listen('tray-service-action', event => {
|
||||
return await listen('tray-core-action', event => {
|
||||
callback(event.payload as string)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="metrics" v-if="isCoreRunning">
|
||||
<span class="metric info">
|
||||
<Globe :size="14" />
|
||||
Port: {{ serviceStatus.port || 5244 }}
|
||||
Port: {{ openlistCoreStatus.port || 5244 }}
|
||||
</span>
|
||||
<span class="metric info">
|
||||
<Activity :size="14" />
|
||||
@@ -98,7 +98,7 @@ const tooltip = ref({
|
||||
})
|
||||
|
||||
const isCoreRunning = computed(() => store.isCoreRunning)
|
||||
const serviceStatus = computed(() => store.serviceStatus)
|
||||
const openlistCoreStatus = computed(() => store.openlistCoreStatus)
|
||||
|
||||
const avgResponseTime = computed(() => {
|
||||
if (dataPoints.value.length === 0) return 0
|
||||
@@ -228,7 +228,7 @@ onMounted(async () => {
|
||||
startTime.value = Date.now()
|
||||
}
|
||||
|
||||
monitoringInterval.value = window.setInterval(checkServiceHealth, 2000)
|
||||
monitoringInterval.value = window.setInterval(checkServiceHealth, (store.settings.app.monitor_interval || 5) * 1000)
|
||||
window.addEventListener('resize', updateChartSize)
|
||||
})
|
||||
|
||||
|
||||
@@ -75,11 +75,6 @@
|
||||
<input type="checkbox" v-model="settings.openlist.auto_launch" @change="handleAutoLaunchToggle" />
|
||||
<span class="toggle-text">{{ t('dashboard.quickActions.autoLaunch') }}</span>
|
||||
</label>
|
||||
|
||||
<label class="toggle-item">
|
||||
<input type="checkbox" v-model="settings.rclone.auto_mount" @change="saveSettings" />
|
||||
<span class="toggle-text">{{ t('dashboard.quickActions.autoMount') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -112,24 +107,26 @@ const serviceButtonIcon = computed(() => {
|
||||
|
||||
const serviceButtonText = computed(() => {
|
||||
if (loading.value) return t('dashboard.quickActions.processing')
|
||||
return isCoreRunning.value ? t('dashboard.quickActions.stopService') : t('dashboard.quickActions.startService')
|
||||
return isCoreRunning.value
|
||||
? t('dashboard.quickActions.stopOpenListCore')
|
||||
: t('dashboard.quickActions.startOpenListCore')
|
||||
})
|
||||
|
||||
const toggleCore = async () => {
|
||||
if (isCoreRunning.value) {
|
||||
await store.stopService()
|
||||
await store.stopOpenListCore()
|
||||
} else {
|
||||
await store.startService()
|
||||
await store.startOpenListCore()
|
||||
}
|
||||
}
|
||||
|
||||
const restartCore = async () => {
|
||||
await store.restartService()
|
||||
await store.restartOpenListCore()
|
||||
}
|
||||
|
||||
const openWebUI = () => {
|
||||
if (store.serviceUrl) {
|
||||
window.open(store.serviceUrl, '_blank')
|
||||
if (store.openListCoreUrl) {
|
||||
window.open(store.openListCoreUrl, '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +257,10 @@ const saveSettings = async () => {
|
||||
|
||||
onMounted(async () => {
|
||||
await rcloneStore.checkRcloneBackendStatus()
|
||||
statusCheckInterval = window.setInterval(rcloneStore.checkRcloneBackendStatus, 30000)
|
||||
statusCheckInterval = window.setInterval(
|
||||
rcloneStore.checkRcloneBackendStatus,
|
||||
(store.settings.app.monitor_interval || 5) * 1000
|
||||
)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
@click="installService"
|
||||
:disabled="actionLoading || serviceStatus === 'installed'"
|
||||
class="action-btn install-btn"
|
||||
v-if="serviceStatus !== 'running'"
|
||||
v-if="serviceStatus !== 'running' && serviceStatus !== 'stopped'"
|
||||
>
|
||||
<component :is="actionLoading && currentAction === 'install' ? LoaderIcon : Download" :size="16" />
|
||||
<span>{{
|
||||
@@ -31,17 +31,31 @@
|
||||
: t('dashboard.serviceManagement.install')
|
||||
}}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="startService"
|
||||
:disabled="actionLoading || serviceStatus !== 'installed'"
|
||||
:disabled="actionLoading || (serviceStatus !== 'installed' && serviceStatus !== 'stopped')"
|
||||
class="action-btn start-btn"
|
||||
v-if="serviceStatus === 'installed'"
|
||||
v-if="serviceStatus === 'installed' || serviceStatus === 'stopped'"
|
||||
>
|
||||
<component :is="actionLoading && currentAction === 'start' ? LoaderIcon : Play" :size="16" />
|
||||
<span>{{
|
||||
actionLoading && currentAction === 'start' ? t('common.loading') : t('dashboard.serviceManagement.start')
|
||||
}}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="stopService"
|
||||
:disabled="actionLoading"
|
||||
class="action-btn stop-btn"
|
||||
v-if="serviceStatus === 'running'"
|
||||
>
|
||||
<component :is="actionLoading && currentAction === 'stop' ? LoaderIcon : Stop" :size="16" />
|
||||
<span>{{
|
||||
actionLoading && currentAction === 'stop' ? t('common.loading') : t('dashboard.serviceManagement.stop')
|
||||
}}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="showUninstallDialog = true"
|
||||
:disabled="actionLoading"
|
||||
@@ -75,16 +89,29 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useTranslation } from '../../composables/useI18n'
|
||||
import { Download, Play, Trash2, Loader2 as LoaderIcon, CheckCircle2, XCircle, Circle, Server } from 'lucide-vue-next'
|
||||
import {
|
||||
Download,
|
||||
Play,
|
||||
Square as Stop,
|
||||
Trash2,
|
||||
Loader2 as LoaderIcon,
|
||||
CheckCircle2,
|
||||
XCircle,
|
||||
Circle,
|
||||
Server
|
||||
} from 'lucide-vue-next'
|
||||
import Card from '../ui/Card.vue'
|
||||
import ConfirmDialog from '../ui/ConfirmDialog.vue'
|
||||
import { TauriAPI } from '../../api/tauri'
|
||||
import { useRcloneStore } from '@/stores/rclone'
|
||||
import { useAppStore } from '../../stores/app'
|
||||
|
||||
const store = useAppStore()
|
||||
|
||||
const { t } = useTranslation()
|
||||
const rcloneStore = useRcloneStore()
|
||||
|
||||
const serviceStatus = ref<'not-installed' | 'installed' | 'running' | 'error'>('not-installed')
|
||||
const serviceStatus = ref<'not-installed' | 'installed' | 'running' | 'error' | 'stopped'>('not-installed')
|
||||
const actionLoading = ref(false)
|
||||
const currentAction = ref('')
|
||||
const showUninstallDialog = ref(false)
|
||||
@@ -98,6 +125,7 @@ const statusClass = computed(() => {
|
||||
case 'installed':
|
||||
return 'status-installed'
|
||||
case 'error':
|
||||
case 'stopped':
|
||||
return 'status-error'
|
||||
default:
|
||||
return 'status-not-installed'
|
||||
@@ -112,6 +140,8 @@ const statusText = computed(() => {
|
||||
return t('dashboard.serviceManagement.status.installed')
|
||||
case 'error':
|
||||
return t('dashboard.serviceManagement.status.error')
|
||||
case 'stopped':
|
||||
return t('dashboard.serviceManagement.status.stopped')
|
||||
default:
|
||||
return t('dashboard.serviceManagement.status.notInstalled')
|
||||
}
|
||||
@@ -125,6 +155,8 @@ const statusIcon = computed(() => {
|
||||
return Circle
|
||||
case 'error':
|
||||
return XCircle
|
||||
case 'stopped':
|
||||
return Stop
|
||||
default:
|
||||
return Server
|
||||
}
|
||||
@@ -133,12 +165,12 @@ const statusIcon = computed(() => {
|
||||
const checkServiceStatus = async () => {
|
||||
try {
|
||||
const status = await TauriAPI.checkServiceStatus()
|
||||
serviceStatus.value = status ? 'running' : 'not-installed'
|
||||
serviceStatus.value = status as 'not-installed' | 'installed' | 'running' | 'error' | 'stopped'
|
||||
return status
|
||||
} catch (error) {
|
||||
console.error('Failed to check service status:', error)
|
||||
serviceStatus.value = 'error'
|
||||
return false
|
||||
return 'error'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,8 +184,8 @@ const installService = async () => {
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 5000))
|
||||
const status = await checkServiceStatus()
|
||||
if (!status) {
|
||||
throw new Error('Service installation did not start correctly')
|
||||
if (status !== 'installed' && status !== 'running' && status !== 'stopped') {
|
||||
throw new Error('Service installation did not complete successfully')
|
||||
}
|
||||
try {
|
||||
await TauriAPI.createAndStartRcloneBackend()
|
||||
@@ -178,7 +210,6 @@ const startService = async () => {
|
||||
throw new Error('Service start failed')
|
||||
}
|
||||
serviceStatus.value = 'running'
|
||||
console.log('Service started successfully')
|
||||
} catch (error) {
|
||||
console.error('Failed to start service:', error)
|
||||
serviceStatus.value = 'error'
|
||||
@@ -188,6 +219,24 @@ const startService = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const stopService = async () => {
|
||||
actionLoading.value = true
|
||||
currentAction.value = 'stop'
|
||||
try {
|
||||
const result = await TauriAPI.stopOpenListService()
|
||||
if (!result) {
|
||||
throw new Error('Service stop failed')
|
||||
}
|
||||
await checkServiceStatus()
|
||||
} catch (error) {
|
||||
console.error('Failed to stop service:', error)
|
||||
serviceStatus.value = 'error'
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
currentAction.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const uninstallService = async () => {
|
||||
actionLoading.value = true
|
||||
currentAction.value = 'uninstall'
|
||||
@@ -218,7 +267,7 @@ const cancelUninstall = () => {
|
||||
|
||||
onMounted(async () => {
|
||||
await checkServiceStatus()
|
||||
statusCheckInterval = window.setInterval(checkServiceStatus, 30000)
|
||||
statusCheckInterval = window.setInterval(checkServiceStatus, (store.settings.app.monitor_interval || 5) * 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import { useAppStore } from '../stores/app'
|
||||
|
||||
export const useServiceActions = () => {
|
||||
export const useCoreActions = () => {
|
||||
const store = useAppStore()
|
||||
|
||||
const startService = async () => {
|
||||
const startOpenListCore = async () => {
|
||||
try {
|
||||
await store.startService()
|
||||
await store.startOpenListCore()
|
||||
} catch (error) {
|
||||
console.error('Failed to start service:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const stopService = async () => {
|
||||
const stopOpenListCore = async () => {
|
||||
try {
|
||||
await store.stopService()
|
||||
await store.startOpenListCore()
|
||||
} catch (error) {
|
||||
console.error('Failed to stop service:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const restartService = async () => {
|
||||
const restartOpenListCore = async () => {
|
||||
try {
|
||||
await store.restartService()
|
||||
await store.restartOpenListCore()
|
||||
} catch (error) {
|
||||
console.error('Failed to restart service:', error)
|
||||
throw error
|
||||
@@ -31,8 +31,8 @@ export const useServiceActions = () => {
|
||||
}
|
||||
|
||||
return {
|
||||
startService,
|
||||
stopService,
|
||||
restartService
|
||||
startOpenListCore,
|
||||
stopOpenListCore,
|
||||
restartOpenListCore
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@ import { onMounted, onUnmounted } from 'vue'
|
||||
|
||||
import { TauriAPI } from '../api/tauri'
|
||||
import { useAppStore } from '../stores/app'
|
||||
import { useServiceActions } from './useServiceActions'
|
||||
import { useCoreActions } from './useCoreActions'
|
||||
|
||||
export const useTray = () => {
|
||||
const { startService, stopService, restartService } = useServiceActions()
|
||||
const { startOpenListCore, stopOpenListCore, restartOpenListCore } = useCoreActions()
|
||||
const store = useAppStore()
|
||||
|
||||
let unlistenTrayActions: (() => void) | null = null
|
||||
@@ -16,27 +16,28 @@ export const useTray = () => {
|
||||
console.error('Failed to update tray menu:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleTrayServiceAction = async (action: string) => {
|
||||
console.log('Tray service action:', action)
|
||||
console.log('Tray core action:', action)
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'start':
|
||||
await startService()
|
||||
await startOpenListCore()
|
||||
setTimeout(async () => {
|
||||
await updateTrayMenu(store.serviceStatus.running)
|
||||
await updateTrayMenu(store.openlistCoreStatus.running)
|
||||
}, 5000)
|
||||
break
|
||||
case 'stop':
|
||||
await stopService()
|
||||
await stopOpenListCore()
|
||||
setTimeout(async () => {
|
||||
await updateTrayMenu(store.serviceStatus.running)
|
||||
await updateTrayMenu(store.openlistCoreStatus.running)
|
||||
}, 5000)
|
||||
break
|
||||
case 'restart':
|
||||
await restartService()
|
||||
await restartOpenListCore()
|
||||
setTimeout(async () => {
|
||||
await updateTrayMenu(store.serviceStatus.running)
|
||||
await updateTrayMenu(store.openlistCoreStatus.running)
|
||||
}, 5000)
|
||||
break
|
||||
default:
|
||||
@@ -45,7 +46,7 @@ export const useTray = () => {
|
||||
} catch (error) {
|
||||
console.error(`Failed to execute tray action '${action}':`, error)
|
||||
setTimeout(async () => {
|
||||
await updateTrayMenu(store.serviceStatus.running)
|
||||
await updateTrayMenu(store.openlistCoreStatus.running)
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
@@ -53,7 +54,7 @@ export const useTray = () => {
|
||||
try {
|
||||
unlistenTrayActions = await TauriAPI.listenToTrayServiceActions(handleTrayServiceAction)
|
||||
|
||||
await TauriAPI.forceUpdateTrayMenu(store.serviceStatus.running)
|
||||
await TauriAPI.forceUpdateTrayMenu(store.openlistCoreStatus.running)
|
||||
console.log('Tray listeners initialized and menu updated')
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize tray listeners:', error)
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
"openlistService": "OpenList Core",
|
||||
"rclone": "RClone",
|
||||
"quickSettings": "Quick Settings",
|
||||
"startService": "Start Core",
|
||||
"stopService": "Stop Core",
|
||||
"startOpenListCore": "Start Core",
|
||||
"stopOpenListCore": "Stop Core",
|
||||
"restart": "Restart",
|
||||
"openWeb": "Web UI",
|
||||
"configRclone": "Configure RClone",
|
||||
@@ -86,6 +86,7 @@
|
||||
"running": "Running",
|
||||
"installed": "Installed",
|
||||
"notInstalled": "Not Installed",
|
||||
"stopped": "Stopped",
|
||||
"error": "Error"
|
||||
},
|
||||
"confirmUninstall": {
|
||||
@@ -255,7 +256,7 @@
|
||||
}
|
||||
},
|
||||
"autoStartApp": {
|
||||
"title": "Auto-launch on startup",
|
||||
"title": "Auto-launch on startup(Immediate Effect)",
|
||||
"subtitle": "Automatically start OpenList Desktop application when the system starts",
|
||||
"description": "Automatically start OpenList service when the application launches"
|
||||
},
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
"openlistService": "OpenList 核心",
|
||||
"rclone": "RClone",
|
||||
"quickSettings": "快速设置",
|
||||
"startService": "启动核心",
|
||||
"stopService": "停止核心",
|
||||
"startOpenListCore": "启动核心",
|
||||
"stopOpenListCore": "停止核心",
|
||||
"restart": "重启",
|
||||
"openWeb": "网页界面",
|
||||
"configRclone": "配置 RClone",
|
||||
@@ -86,6 +86,7 @@
|
||||
"running": "运行中",
|
||||
"installed": "已安装",
|
||||
"notInstalled": "未安装",
|
||||
"stopped": "已停止",
|
||||
"error": "错误"
|
||||
},
|
||||
"confirmUninstall": {
|
||||
@@ -255,7 +256,7 @@
|
||||
}
|
||||
},
|
||||
"autoStartApp": {
|
||||
"title": "开机自动启动应用",
|
||||
"title": "开机自动启动应用(立即生效)",
|
||||
"subtitle": "在系统启动时自动启动 OpenList 桌面应用",
|
||||
"description": "在系统启动时自动启动 OpenList 桌面应用"
|
||||
},
|
||||
|
||||
@@ -5,11 +5,11 @@ import { TauriAPI } from '../api/tauri'
|
||||
import type {
|
||||
FileItem,
|
||||
MergedSettings,
|
||||
OpenListCoreStatus,
|
||||
ProcessConfig,
|
||||
RcloneFormConfig,
|
||||
RcloneMountInfo,
|
||||
RcloneWebdavConfig,
|
||||
ServiceStatus,
|
||||
UpdateCheck
|
||||
} from '../types'
|
||||
|
||||
@@ -22,20 +22,16 @@ export const useAppStore = defineStore('app', () => {
|
||||
ssl_enabled: false
|
||||
},
|
||||
rclone: {
|
||||
config: {}, // Flexible JSON object for rclone configuration
|
||||
flags: [],
|
||||
auto_mount: false
|
||||
config: {} // Flexible JSON object for rclone configuration
|
||||
},
|
||||
app: {
|
||||
theme: 'light',
|
||||
monitor_interval: 5000,
|
||||
service_api_token: 'yeM6PCcZGaCpapyBKAbjTp2YAhcku6cUr',
|
||||
service_port: 53211,
|
||||
auto_update_enabled: true
|
||||
}
|
||||
})
|
||||
|
||||
const serviceStatus = ref<ServiceStatus>({
|
||||
const openlistCoreStatus = ref<OpenListCoreStatus>({
|
||||
running: false
|
||||
})
|
||||
|
||||
@@ -305,10 +301,10 @@ export const useAppStore = defineStore('app', () => {
|
||||
const tutorialStep = ref(0)
|
||||
const tutorialSkipped = ref(false)
|
||||
|
||||
const isCoreRunning = computed(() => serviceStatus.value.running)
|
||||
const serviceUrl = computed(() => {
|
||||
const isCoreRunning = computed(() => openlistCoreStatus.value.running)
|
||||
const openListCoreUrl = computed(() => {
|
||||
const protocol = settings.value.openlist.ssl_enabled ? 'https' : 'http'
|
||||
return `${protocol}://localhost:${serviceStatus.value.port}`
|
||||
return `${protocol}://localhost:${openlistCoreStatus.value.port}`
|
||||
})
|
||||
|
||||
async function loadSettings() {
|
||||
@@ -366,7 +362,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function startService() {
|
||||
async function startOpenListCore() {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
@@ -398,9 +394,9 @@ export const useAppStore = defineStore('app', () => {
|
||||
openlistProcessId.value = processId
|
||||
await refreshServiceStatus()
|
||||
|
||||
await TauriAPI.updateTrayMenu(serviceStatus.value.running)
|
||||
await TauriAPI.updateTrayMenu(openlistCoreStatus.value.running)
|
||||
} catch (err: any) {
|
||||
serviceStatus.value = { running: false }
|
||||
openlistCoreStatus.value = { running: false }
|
||||
let errorMessage = 'Failed to start service'
|
||||
const formattedError = formatError(err)
|
||||
if (formattedError) {
|
||||
@@ -428,12 +424,12 @@ export const useAppStore = defineStore('app', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function stopService() {
|
||||
async function stopOpenListCore() {
|
||||
try {
|
||||
loading.value = true
|
||||
const id = await getOpenListProcessId()
|
||||
if (!id) {
|
||||
serviceStatus.value = { running: false }
|
||||
openlistCoreStatus.value = { running: false }
|
||||
await TauriAPI.updateTrayMenu(false)
|
||||
return
|
||||
}
|
||||
@@ -443,7 +439,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
throw new Error('Failed to stop OpenList Core service - service returned false')
|
||||
}
|
||||
|
||||
serviceStatus.value = { running: false }
|
||||
openlistCoreStatus.value = { running: false }
|
||||
await TauriAPI.updateTrayMenu(false)
|
||||
} catch (err: any) {
|
||||
const errorMessage = `Failed to stop service: ${formatError(err)}`
|
||||
@@ -478,12 +474,12 @@ export const useAppStore = defineStore('app', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function restartService() {
|
||||
async function restartOpenListCore() {
|
||||
try {
|
||||
loading.value = true
|
||||
const id = await getOpenListProcessId()
|
||||
if (!id) {
|
||||
serviceStatus.value = { running: false }
|
||||
openlistCoreStatus.value = { running: false }
|
||||
await TauriAPI.updateTrayMenu(false)
|
||||
return
|
||||
}
|
||||
@@ -492,14 +488,14 @@ export const useAppStore = defineStore('app', () => {
|
||||
throw new Error('Failed to restart OpenList Core service - service returned false')
|
||||
}
|
||||
await refreshServiceStatus()
|
||||
await TauriAPI.updateTrayMenu(serviceStatus.value.running)
|
||||
await TauriAPI.updateTrayMenu(openlistCoreStatus.value.running)
|
||||
} catch (err: any) {
|
||||
const errorMessage = `Failed to restart service: ${formatError(err)}`
|
||||
error.value = errorMessage
|
||||
console.error('Failed to restart service:', err)
|
||||
try {
|
||||
await refreshServiceStatus()
|
||||
await safeUpdateTrayMenu(serviceStatus.value.running)
|
||||
await safeUpdateTrayMenu(openlistCoreStatus.value.running)
|
||||
} catch (refreshErr) {
|
||||
console.error('Failed to refresh service status after restart failure:', refreshErr)
|
||||
}
|
||||
@@ -512,14 +508,14 @@ export const useAppStore = defineStore('app', () => {
|
||||
async function refreshServiceStatus() {
|
||||
try {
|
||||
const status = await TauriAPI.getOpenListCoreStatus()
|
||||
const statusChanged = serviceStatus.value.running !== status.running
|
||||
serviceStatus.value = status
|
||||
const statusChanged = openlistCoreStatus.value.running !== status.running
|
||||
openlistCoreStatus.value = status
|
||||
if (statusChanged) {
|
||||
await TauriAPI.updateTrayMenuDelayed(status.running)
|
||||
}
|
||||
} catch (err) {
|
||||
const wasRunning = serviceStatus.value.running
|
||||
serviceStatus.value = { running: false }
|
||||
const wasRunning = openlistCoreStatus.value.running
|
||||
openlistCoreStatus.value = { running: false }
|
||||
if (wasRunning) {
|
||||
await TauriAPI.updateTrayMenuDelayed(false)
|
||||
}
|
||||
@@ -612,7 +608,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
async function autoStartServiceIfEnabled() {
|
||||
try {
|
||||
if (settings.value.openlist.auto_launch) {
|
||||
await startService()
|
||||
await startOpenListCore()
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to auto-start service:', err)
|
||||
@@ -747,7 +743,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
fullRcloneConfigs,
|
||||
|
||||
settings,
|
||||
serviceStatus,
|
||||
openlistCoreStatus,
|
||||
logs,
|
||||
files,
|
||||
currentPath,
|
||||
@@ -761,14 +757,15 @@ export const useAppStore = defineStore('app', () => {
|
||||
tutorialSkipped,
|
||||
|
||||
isCoreRunning,
|
||||
serviceUrl,
|
||||
openListCoreUrl,
|
||||
|
||||
loadSettings,
|
||||
saveSettings,
|
||||
resetSettings,
|
||||
startService,
|
||||
stopService,
|
||||
restartService,
|
||||
|
||||
startOpenListCore,
|
||||
stopOpenListCore,
|
||||
restartOpenListCore,
|
||||
enableAutoLaunch,
|
||||
refreshServiceStatus,
|
||||
loadLogs,
|
||||
|
||||
@@ -7,8 +7,6 @@ export interface OpenListCoreConfig {
|
||||
|
||||
export interface RcloneConfig {
|
||||
config?: any // Flexible JSON object for rclone configuration
|
||||
flags: string[]
|
||||
auto_mount: boolean
|
||||
}
|
||||
|
||||
export interface RcloneWebdavConfig {
|
||||
@@ -65,8 +63,6 @@ export interface RcloneMountRequest {
|
||||
export interface AppConfig {
|
||||
theme?: 'light' | 'dark' | 'auto'
|
||||
monitor_interval?: number
|
||||
service_api_token?: string
|
||||
service_port?: number
|
||||
auto_update_enabled?: boolean
|
||||
}
|
||||
|
||||
@@ -77,7 +73,7 @@ export interface MergedSettings {
|
||||
app: AppConfig
|
||||
}
|
||||
|
||||
export interface ServiceStatus {
|
||||
export interface OpenListCoreStatus {
|
||||
running: boolean
|
||||
pid?: number
|
||||
port?: number
|
||||
@@ -98,7 +94,7 @@ export interface FileItem {
|
||||
}
|
||||
|
||||
export interface AppState {
|
||||
serviceStatus: ServiceStatus
|
||||
serviceStatus: OpenListCoreStatus
|
||||
mountStatus: MountStatus
|
||||
logs: string[]
|
||||
settings: MergedSettings
|
||||
|
||||
@@ -327,7 +327,7 @@ onMounted(async () => {
|
||||
await scrollToBottom()
|
||||
}
|
||||
}
|
||||
}, 2000)
|
||||
}, (store.settings.app.monitor_interval || 5) * 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -197,17 +197,13 @@ const deleteConfig = async (config: RcloneFormConfig) => {
|
||||
}
|
||||
}
|
||||
|
||||
const refreshMounts = async () => {
|
||||
try {
|
||||
await store.loadMountInfos()
|
||||
} catch (error: any) {
|
||||
console.error(error.message || t('mount.messages.failedToRefresh'))
|
||||
}
|
||||
}
|
||||
|
||||
const startBackend = async () => {
|
||||
try {
|
||||
await rcloneStore.startRcloneBackend()
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
await rcloneStore.checkRcloneBackendStatus()
|
||||
await store.loadRemoteConfigs()
|
||||
await store.loadMountInfos()
|
||||
} catch (error: any) {
|
||||
console.error(error.message || t('mount.messages.failedToStartService'))
|
||||
}
|
||||
@@ -272,7 +268,8 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
addNewConfig()
|
||||
} else if (ctrl && key === 'r') {
|
||||
event.preventDefault()
|
||||
refreshMounts()
|
||||
store.loadRemoteConfigs()
|
||||
store.loadMountInfos()
|
||||
} else if (key === 'Escape') {
|
||||
event.preventDefault()
|
||||
if (showAddForm.value) {
|
||||
@@ -304,11 +301,11 @@ onMounted(async () => {
|
||||
document.addEventListener('keydown', handleKeydown)
|
||||
await rcloneStore.checkRcloneBackendStatus()
|
||||
await store.loadRemoteConfigs()
|
||||
await refreshMounts()
|
||||
mountRefreshInterval = setInterval(refreshMounts, 15000)
|
||||
await store.loadMountInfos()
|
||||
mountRefreshInterval = setInterval(store.loadMountInfos, (store.settings.app.monitor_interval || 5) * 1000)
|
||||
backendStatusCheckInterval = setInterval(() => {
|
||||
rcloneStore.checkRcloneBackendStatus()
|
||||
}, 5000)
|
||||
}, (store.settings.app.monitor_interval || 5) * 1000)
|
||||
await rcloneStore.init()
|
||||
})
|
||||
|
||||
@@ -396,7 +393,7 @@ onUnmounted(() => {
|
||||
<option value="unmounted">{{ t('mount.status.unmounted') }}</option>
|
||||
<option value="error">{{ t('mount.status.error') }}</option>
|
||||
</select>
|
||||
<button @click="refreshMounts" class="refresh-btn" :disabled="rcloneStore.loading">
|
||||
<button @click="store.loadMountInfos" class="refresh-btn" :disabled="rcloneStore.loading">
|
||||
<RefreshCw class="refresh-icon" :class="{ spinning: rcloneStore.loading }" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -3,18 +3,7 @@ import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useAppStore } from '../stores/app'
|
||||
import { useTranslation } from '../composables/useI18n'
|
||||
import {
|
||||
Settings,
|
||||
Server,
|
||||
HardDrive,
|
||||
Save,
|
||||
RotateCcw,
|
||||
AlertCircle,
|
||||
CheckCircle,
|
||||
Plus,
|
||||
Trash2,
|
||||
Play
|
||||
} from 'lucide-vue-next'
|
||||
import { Settings, Server, HardDrive, Save, RotateCcw, AlertCircle, CheckCircle, Play } from 'lucide-vue-next'
|
||||
import { enable, isEnabled, disable } from '@tauri-apps/plugin-autostart'
|
||||
|
||||
const store = useAppStore()
|
||||
@@ -31,13 +20,15 @@ const openlistCoreSettings = reactive({ ...store.settings.openlist })
|
||||
const rcloneSettings = reactive({ ...store.settings.rclone })
|
||||
const appSettings = reactive({ ...store.settings.app })
|
||||
|
||||
const isOpenListPortChanged = computed(() => {
|
||||
return openlistCoreSettings.port !== store.settings.openlist.port
|
||||
})
|
||||
|
||||
watch(autoStartApp, async newValue => {
|
||||
if (newValue) {
|
||||
await enable()
|
||||
console.log(`registered for autostart? ${await isEnabled()}`)
|
||||
} else {
|
||||
await disable()
|
||||
console.log(`registered for autostart? ${await isEnabled()}`)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -75,15 +66,11 @@ onMounted(async () => {
|
||||
if (openlistCoreSettings.ssl_enabled === undefined) openlistCoreSettings.ssl_enabled = false
|
||||
|
||||
if (!rcloneSettings.config) rcloneSettings.config = {}
|
||||
if (!rcloneSettings.flags) rcloneSettings.flags = []
|
||||
if (rcloneSettings.auto_mount === undefined) rcloneSettings.auto_mount = false
|
||||
|
||||
rcloneConfigJson.value = JSON.stringify(rcloneSettings.config, null, 2)
|
||||
|
||||
if (!appSettings.theme) appSettings.theme = 'light'
|
||||
|
||||
if (!appSettings.monitor_interval) appSettings.monitor_interval = 5
|
||||
if (!appSettings.service_api_token) appSettings.service_api_token = 'yeM6PCcZGaCpapyBKAbjTp2YAhcku6cUr'
|
||||
if (!appSettings.service_port) appSettings.service_port = 53211
|
||||
if (appSettings.auto_update_enabled === undefined) appSettings.auto_update_enabled = true
|
||||
})
|
||||
|
||||
@@ -158,14 +145,6 @@ const handleReset = async () => {
|
||||
messageType.value = 'error'
|
||||
}
|
||||
}
|
||||
|
||||
const addRcloneFlag = () => {
|
||||
rcloneSettings.flags.push('')
|
||||
}
|
||||
|
||||
const removeRcloneFlag = (index: number) => {
|
||||
rcloneSettings.flags.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -270,44 +249,6 @@ const removeRcloneFlag = (index: number) => {
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'rclone'" class="tab-content">
|
||||
<div class="settings-section">
|
||||
<h2>{{ t('settings.rclone.config.title') }}</h2>
|
||||
<p>{{ t('settings.rclone.config.subtitle') }}</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="switch-label">
|
||||
<input v-model="rcloneSettings.auto_mount" type="checkbox" class="switch-input" />
|
||||
<span class="switch-slider"></span>
|
||||
<div class="switch-content">
|
||||
<span class="switch-title">{{ t('settings.rclone.mount.autoMount.title') }}</span>
|
||||
<span class="switch-description">{{ t('settings.rclone.mount.autoMount.description') }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h2>{{ t('settings.rclone.flags.title') }}</h2>
|
||||
<p>{{ t('settings.rclone.flags.subtitle') }}</p>
|
||||
|
||||
<div class="flags-container">
|
||||
<div v-for="(_, index) in rcloneSettings.flags" :key="index" class="flag-item">
|
||||
<input
|
||||
v-model="rcloneSettings.flags[index]"
|
||||
type="text"
|
||||
class="form-input"
|
||||
:placeholder="t('settings.rclone.flags.placeholder')"
|
||||
/>
|
||||
<button @click="removeRcloneFlag(index)" class="remove-btn">
|
||||
<Trash2 :size="16" />
|
||||
</button>
|
||||
</div>
|
||||
<button @click="addRcloneFlag" class="add-flag-btn">
|
||||
<Plus :size="16" />
|
||||
{{ t('settings.rclone.flags.add') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h2>{{ t('settings.rclone.config.title') }}</h2>
|
||||
<p>{{ t('settings.rclone.config.subtitle') }}</p>
|
||||
@@ -413,37 +354,6 @@ const removeRcloneFlag = (index: number) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h2>{{ t('settings.app.service.title') }}</h2>
|
||||
<p>{{ t('settings.app.service.subtitle') }}</p>
|
||||
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>{{ t('settings.app.service.port.label') }}</label>
|
||||
<input
|
||||
v-model.number="appSettings.service_port"
|
||||
type="number"
|
||||
class="form-input"
|
||||
:placeholder="t('settings.app.service.port.placeholder')"
|
||||
min="1"
|
||||
max="65535"
|
||||
/>
|
||||
<small>{{ t('settings.app.service.port.help') }}</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ t('settings.app.service.apiToken.label') }}</label>
|
||||
<input
|
||||
v-model="appSettings.service_api_token"
|
||||
type="password"
|
||||
class="form-input"
|
||||
:placeholder="t('settings.app.service.apiToken.placeholder')"
|
||||
/>
|
||||
<small>{{ t('settings.app.service.apiToken.help') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user