mirror of
https://github.com/OpenListTeam/OpenList-Desktop.git
synced 2025-11-24 19:12:59 +08:00
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -35,6 +35,7 @@
|
||||
"src/i18n",
|
||||
"src/i18n/locales"
|
||||
],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
// "rust-analyzer.cargo.target": "x86_64-unknown-linux-gnu"
|
||||
//"rust-analyzer.cargo.target": "x86_64-pc-windows-msvc"
|
||||
// "rust-analyzer.cargo.target": "x86_64-apple-darwin",
|
||||
|
||||
@@ -245,7 +245,6 @@ winget install OpenListTeam.OpenListDesktop
|
||||
"data_dir": "",
|
||||
"auto_launch": true,
|
||||
"ssl_enabled": false,
|
||||
"admin_password": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -266,6 +265,7 @@ winget install OpenListTeam.OpenListDesktop
|
||||
"extraFlags": ["--vfs-cache-mode", "full"]
|
||||
}
|
||||
},
|
||||
"api_port": 45572
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -280,6 +280,8 @@ winget install OpenListTeam.OpenListDesktop
|
||||
"gh_proxy": "https://ghproxy.com/",
|
||||
"gh_proxy_api": false,
|
||||
"open_links_in_browser": true,
|
||||
"admin_password": "",
|
||||
"show_window_on_startup": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -244,8 +244,7 @@ Add custom Rclone flags for optimal performance:
|
||||
"port": 5244,
|
||||
"data_dir": "",
|
||||
"auto_launch": true,
|
||||
"ssl_enabled": false,
|
||||
"admin_password": ""
|
||||
"ssl_enabled": false
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -266,6 +265,7 @@ Add custom Rclone flags for optimal performance:
|
||||
"extraFlags": ["--vfs-cache-mode", "full"]
|
||||
}
|
||||
},
|
||||
"api_port": 45572
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -280,6 +280,8 @@ Add custom Rclone flags for optimal performance:
|
||||
"gh_proxy": "https://ghproxy.com/",
|
||||
"gh_proxy_api": false,
|
||||
"open_links_in_browser": true,
|
||||
"admin_password": "",
|
||||
"show_window_on_startup": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -6,6 +6,7 @@ use tokio::time::{Duration, sleep};
|
||||
|
||||
use crate::cmd::http_api::{delete_process, get_process_list, start_process, stop_process};
|
||||
use crate::cmd::openlist_core::create_openlist_core_process;
|
||||
use crate::cmd::rclone_core::create_rclone_backend_process;
|
||||
use crate::conf::config::MergedSettings;
|
||||
use crate::object::structs::AppState;
|
||||
use crate::utils::path::{app_config_file_path, get_default_openlist_data_dir};
|
||||
@@ -88,6 +89,23 @@ async fn recreate_openlist_core_process(state: State<'_, AppState>) -> Result<()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recreate_rclone_backend_process(state: State<'_, AppState>) -> Result<(), String> {
|
||||
let procs = get_process_list(state.clone()).await?;
|
||||
if let Some(proc) = procs
|
||||
.into_iter()
|
||||
.find(|p| p.config.name == "single_rclone_backend_process")
|
||||
{
|
||||
let id = proc.config.id.clone();
|
||||
let _ = stop_process(id.clone(), state.clone()).await;
|
||||
sleep(Duration::from_millis(1000)).await;
|
||||
let _ = delete_process(id, state.clone()).await;
|
||||
sleep(Duration::from_millis(1000)).await;
|
||||
|
||||
create_rclone_backend_process(state.clone()).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn load_settings(state: State<'_, AppState>) -> Result<Option<MergedSettings>, String> {
|
||||
state.load_settings()?;
|
||||
@@ -111,12 +129,18 @@ pub async fn save_settings_with_update_port(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<bool, String> {
|
||||
let old_settings = state.get_settings();
|
||||
let needs_process_recreation = if let Some(old) = old_settings {
|
||||
let needs_openlist_recreation = if let Some(old) = &old_settings {
|
||||
old.openlist.data_dir != settings.openlist.data_dir
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let needs_rclone_recreation = if let Some(old) = &old_settings {
|
||||
old.rclone.api_port != settings.rclone.api_port
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
state.update_settings(settings.clone());
|
||||
persist_app_settings(&settings)?;
|
||||
let data_dir = if settings.openlist.data_dir.is_empty() {
|
||||
@@ -126,7 +150,7 @@ pub async fn save_settings_with_update_port(
|
||||
};
|
||||
update_data_config(settings.openlist.port, data_dir)?;
|
||||
|
||||
if needs_process_recreation {
|
||||
if needs_openlist_recreation {
|
||||
if let Err(e) = recreate_openlist_core_process(state.clone()).await {
|
||||
log::error!("{e}");
|
||||
return Err(e);
|
||||
@@ -142,6 +166,16 @@ pub async fn save_settings_with_update_port(
|
||||
log::info!("Settings saved and OpenList core restarted with new port successfully");
|
||||
}
|
||||
|
||||
if needs_rclone_recreation {
|
||||
if let Err(e) = recreate_rclone_backend_process(state.clone()).await {
|
||||
log::error!("Failed to recreate rclone backend process: {e}");
|
||||
return Err(format!("Failed to recreate rclone backend process: {e}"));
|
||||
}
|
||||
log::info!("Rclone backend process recreated with new API port successfully");
|
||||
} else {
|
||||
log::info!("Settings saved successfully (no rclone port change detected)");
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ use crate::object::structs::AppState;
|
||||
use crate::utils::api::{CreateProcessResponse, ProcessConfig, get_api_key, get_server_port};
|
||||
use crate::utils::path::{get_app_logs_dir, get_rclone_binary_path, get_rclone_config_path};
|
||||
|
||||
// use 45572 due to the reserved port on Windows
|
||||
pub const RCLONE_API_BASE: &str = "http://127.0.0.1:45572";
|
||||
// admin:admin base64 encoded
|
||||
pub const RCLONE_AUTH: &str = "Basic YWRtaW46YWRtaW4=";
|
||||
|
||||
@@ -33,7 +31,7 @@ pub async fn create_and_start_rclone_backend(
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_rclone_backend_process(
|
||||
_state: State<'_, AppState>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProcessConfig, String> {
|
||||
let binary_path =
|
||||
get_rclone_binary_path().map_err(|e| format!("Failed to get rclone binary path: {e}"))?;
|
||||
@@ -44,6 +42,12 @@ pub async fn create_rclone_backend_process(
|
||||
let log_file_path = log_file_path.join("process_rclone.log");
|
||||
let api_key = get_api_key();
|
||||
let port = get_server_port();
|
||||
|
||||
let rclone_port = state
|
||||
.get_settings()
|
||||
.map(|settings| settings.rclone.api_port)
|
||||
.unwrap_or(45572);
|
||||
|
||||
let config = ProcessConfig {
|
||||
id: "rclone_backend".into(),
|
||||
name: "single_rclone_backend_process".into(),
|
||||
@@ -57,7 +61,7 @@ pub async fn create_rclone_backend_process(
|
||||
"--rc-pass".into(),
|
||||
"admin".into(),
|
||||
"--rc-addr".into(),
|
||||
format!("127.0.0.1:45572"),
|
||||
format!("127.0.0.1:{}", rclone_port),
|
||||
"--rc-web-gui-no-open-browser".into(),
|
||||
],
|
||||
log_file: log_file_path.to_string_lossy().into_owned(),
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde_json::{Value, json};
|
||||
use tauri::State;
|
||||
|
||||
use super::http_api::get_process_list;
|
||||
use super::rclone_core::{RCLONE_API_BASE, RCLONE_AUTH};
|
||||
use super::rclone_core::RCLONE_AUTH;
|
||||
use crate::conf::rclone::{RcloneCreateRemoteRequest, RcloneMountRequest, RcloneWebdavConfig};
|
||||
use crate::object::structs::{
|
||||
AppState, RcloneMountInfo, RcloneMountListResponse, RcloneRemoteListResponse,
|
||||
@@ -16,14 +16,24 @@ use crate::utils::api::{CreateProcessResponse, ProcessConfig, get_api_key, get_s
|
||||
use crate::utils::args::split_args_vec;
|
||||
use crate::utils::path::{get_app_logs_dir, get_rclone_binary_path, get_rclone_config_path};
|
||||
|
||||
fn get_rclone_api_base_url(state: &State<AppState>) -> String {
|
||||
let port = state
|
||||
.get_settings()
|
||||
.map(|settings| settings.rclone.api_port)
|
||||
.unwrap_or(45572);
|
||||
format!("http://127.0.0.1:{}", port)
|
||||
}
|
||||
|
||||
struct RcloneApi {
|
||||
client: Client,
|
||||
api_base: String,
|
||||
}
|
||||
|
||||
impl RcloneApi {
|
||||
fn new() -> Self {
|
||||
fn new(api_base: String) -> Self {
|
||||
Self {
|
||||
client: Client::new(),
|
||||
api_base,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +42,7 @@ impl RcloneApi {
|
||||
endpoint: &str,
|
||||
body: Option<Value>,
|
||||
) -> Result<T, String> {
|
||||
let url = format!("{RCLONE_API_BASE}/{endpoint}");
|
||||
let url = format!("{}/{endpoint}", self.api_base);
|
||||
let mut req = self.client.post(&url).header("Authorization", RCLONE_AUTH);
|
||||
if let Some(b) = body {
|
||||
req = req.json(&b).header("Content-Type", "application/json");
|
||||
@@ -53,7 +63,7 @@ impl RcloneApi {
|
||||
}
|
||||
|
||||
async fn post_text(&self, endpoint: &str) -> Result<String, String> {
|
||||
let url = format!("{RCLONE_API_BASE}/{endpoint}");
|
||||
let url = format!("{}/{endpoint}", self.api_base);
|
||||
let resp = self
|
||||
.client
|
||||
.post(&url)
|
||||
@@ -76,9 +86,9 @@ impl RcloneApi {
|
||||
#[tauri::command]
|
||||
pub async fn rclone_list_config(
|
||||
remote_type: String,
|
||||
_state: State<'_, AppState>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Value, String> {
|
||||
let api = RcloneApi::new();
|
||||
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||
let text = api.post_text("config/dump").await?;
|
||||
let all: Value = serde_json::from_str(&text).map_err(|e| format!("Invalid JSON: {e}"))?;
|
||||
let remotes = match (remote_type.as_str(), all.as_object()) {
|
||||
@@ -101,15 +111,17 @@ pub async fn rclone_list_config(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn rclone_list_remotes() -> Result<Vec<String>, String> {
|
||||
let api = RcloneApi::new();
|
||||
pub async fn rclone_list_remotes(state: State<'_, AppState>) -> Result<Vec<String>, String> {
|
||||
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||
let resp: RcloneRemoteListResponse = api.post_json("config/listremotes", None).await?;
|
||||
Ok(resp.remotes)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn rclone_list_mounts() -> Result<RcloneMountListResponse, String> {
|
||||
let api = RcloneApi::new();
|
||||
pub async fn rclone_list_mounts(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<RcloneMountListResponse, String> {
|
||||
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||
api.post_json("mount/listmounts", None).await
|
||||
}
|
||||
|
||||
@@ -118,9 +130,9 @@ pub async fn rclone_create_remote(
|
||||
name: String,
|
||||
r#type: String,
|
||||
config: RcloneWebdavConfig,
|
||||
_state: State<'_, AppState>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<bool, String> {
|
||||
let api = RcloneApi::new();
|
||||
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||
let req = RcloneCreateRemoteRequest {
|
||||
name,
|
||||
r#type,
|
||||
@@ -136,9 +148,9 @@ pub async fn rclone_update_remote(
|
||||
name: String,
|
||||
r#type: String,
|
||||
config: RcloneWebdavConfig,
|
||||
_state: State<'_, AppState>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<bool, String> {
|
||||
let api = RcloneApi::new();
|
||||
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||
let body = json!({ "name": name, "type": r#type, "parameters": config });
|
||||
api.post_json::<Value>("config/update", Some(body))
|
||||
.await
|
||||
@@ -148,9 +160,9 @@ pub async fn rclone_update_remote(
|
||||
#[tauri::command]
|
||||
pub async fn rclone_delete_remote(
|
||||
name: String,
|
||||
_state: State<'_, AppState>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<bool, String> {
|
||||
let api = RcloneApi::new();
|
||||
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||
let body = json!({ "name": name });
|
||||
api.post_json::<Value>("config/delete", Some(body))
|
||||
.await
|
||||
@@ -160,9 +172,9 @@ pub async fn rclone_delete_remote(
|
||||
#[tauri::command]
|
||||
pub async fn rclone_mount_remote(
|
||||
mount_request: RcloneMountRequest,
|
||||
_state: State<'_, AppState>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<bool, String> {
|
||||
let api = RcloneApi::new();
|
||||
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||
api.post_json::<Value>("mount/mount", Some(json!(mount_request)))
|
||||
.await
|
||||
.map(|_| true)
|
||||
@@ -171,9 +183,9 @@ pub async fn rclone_mount_remote(
|
||||
#[tauri::command]
|
||||
pub async fn rclone_unmount_remote(
|
||||
mount_point: String,
|
||||
_state: State<'_, AppState>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<bool, String> {
|
||||
let api = RcloneApi::new();
|
||||
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||
api.post_json::<Value>("mount/unmount", Some(json!({ "mountPoint": mount_point })))
|
||||
.await
|
||||
.map(|_| true)
|
||||
|
||||
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct RcloneConfig {
|
||||
pub config: serde_json::Value,
|
||||
pub api_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -45,6 +46,7 @@ impl RcloneConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
config: serde_json::Value::Object(Default::default()),
|
||||
api_port: 45572,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +178,15 @@
|
||||
},
|
||||
"rclone": {
|
||||
"subtitle": "Configure remote storage connections",
|
||||
"api": {
|
||||
"title": "API Configuration",
|
||||
"subtitle": "Configure the Rclone API server settings",
|
||||
"port": {
|
||||
"label": "API Port",
|
||||
"placeholder": "45572",
|
||||
"help": "Port number for the Rclone API server (1-65535). Default: 45572"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "Remote Storage",
|
||||
"subtitle": "Configure rclone for remote storage access",
|
||||
|
||||
@@ -178,6 +178,15 @@
|
||||
},
|
||||
"rclone": {
|
||||
"subtitle": "配置远程存储连接",
|
||||
"api": {
|
||||
"title": "API 配置",
|
||||
"subtitle": "配置 Rclone API 服务器设置",
|
||||
"port": {
|
||||
"label": "API 端口",
|
||||
"placeholder": "45572",
|
||||
"help": "Rclone API 服务器的端口号 (1-65535)。默认:45572"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "远程存储",
|
||||
"subtitle": "配置 rclone 远程存储访问",
|
||||
|
||||
@@ -8,14 +8,15 @@ type ActionFn<T = any> = () => Promise<T>
|
||||
export const useAppStore = defineStore('app', () => {
|
||||
const settings = ref<MergedSettings>({
|
||||
openlist: { port: 5244, data_dir: '', auto_launch: false, ssl_enabled: false },
|
||||
rclone: { config: {} },
|
||||
rclone: { config: {}, api_port: 45572 },
|
||||
app: {
|
||||
theme: 'light',
|
||||
auto_update_enabled: true,
|
||||
gh_proxy: '',
|
||||
gh_proxy_api: false,
|
||||
open_links_in_browser: false,
|
||||
admin_password: undefined
|
||||
admin_password: undefined,
|
||||
show_window_on_startup: false
|
||||
}
|
||||
})
|
||||
const openlistCoreStatus = ref<OpenListCoreStatus>({ running: false })
|
||||
|
||||
1
src/types/types.d.ts
vendored
1
src/types/types.d.ts
vendored
@@ -13,6 +13,7 @@ interface OpenListCoreConfig {
|
||||
|
||||
interface RcloneConfig {
|
||||
config?: any // Flexible JSON object for rclone configuration
|
||||
api_port: number // Port for the Rclone API server
|
||||
}
|
||||
|
||||
interface RcloneWebdavConfig {
|
||||
|
||||
@@ -41,6 +41,7 @@ const rcloneSettings = reactive({ ...appStore.settings.rclone })
|
||||
const appSettings = reactive({ ...appStore.settings.app })
|
||||
let originalOpenlistPort = openlistCoreSettings.port || 5244
|
||||
let originalDataDir = openlistCoreSettings.data_dir
|
||||
let originalRcloneApiPort = rcloneSettings.api_port || 45572
|
||||
let originalAdminPassword = appStore.settings.app.admin_password || ''
|
||||
|
||||
watch(autoStartApp, async newValue => {
|
||||
@@ -85,6 +86,7 @@ onMounted(async () => {
|
||||
if (openlistCoreSettings.ssl_enabled === undefined) openlistCoreSettings.ssl_enabled = false
|
||||
|
||||
if (!rcloneSettings.config) rcloneSettings.config = {}
|
||||
if (!rcloneSettings.api_port) rcloneSettings.api_port = 45572
|
||||
|
||||
rcloneConfigJson.value = JSON.stringify(rcloneSettings.config, null, 2)
|
||||
if (!appSettings.theme) appSettings.theme = 'light'
|
||||
@@ -97,6 +99,7 @@ onMounted(async () => {
|
||||
if (!appSettings.admin_password) appSettings.admin_password = ''
|
||||
originalOpenlistPort = openlistCoreSettings.port || 5244
|
||||
originalDataDir = openlistCoreSettings.data_dir
|
||||
originalRcloneApiPort = rcloneSettings.api_port || 45572
|
||||
|
||||
// Load current admin password
|
||||
await loadCurrentAdminPassword()
|
||||
@@ -139,7 +142,11 @@ const handleSave = async () => {
|
||||
|
||||
const needsPasswordUpdate = originalAdminPassword !== appSettings.admin_password && appSettings.admin_password
|
||||
|
||||
if (originalOpenlistPort !== openlistCoreSettings.port || originalDataDir !== openlistCoreSettings.data_dir) {
|
||||
if (
|
||||
originalOpenlistPort !== openlistCoreSettings.port ||
|
||||
originalDataDir !== openlistCoreSettings.data_dir ||
|
||||
originalRcloneApiPort !== rcloneSettings.api_port
|
||||
) {
|
||||
await appStore.saveSettingsWithCoreUpdate()
|
||||
} else {
|
||||
await appStore.saveSettings()
|
||||
@@ -161,6 +168,7 @@ const handleSave = async () => {
|
||||
}
|
||||
|
||||
originalOpenlistPort = openlistCoreSettings.port || 5244
|
||||
originalRcloneApiPort = rcloneSettings.api_port || 45572
|
||||
originalDataDir = openlistCoreSettings.data_dir
|
||||
} catch (error) {
|
||||
message.value = t('settings.saveFailed')
|
||||
@@ -463,6 +471,26 @@ const loadCurrentAdminPassword = async () => {
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'rclone'" class="tab-content">
|
||||
<div class="settings-section">
|
||||
<h2>{{ t('settings.rclone.api.title') }}</h2>
|
||||
<p>{{ t('settings.rclone.api.subtitle') }}</p>
|
||||
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>{{ t('settings.rclone.api.port.label') }}</label>
|
||||
<input
|
||||
v-model.number="rcloneSettings.api_port"
|
||||
type="number"
|
||||
class="form-input"
|
||||
:placeholder="t('settings.rclone.api.port.placeholder')"
|
||||
min="1"
|
||||
max="65535"
|
||||
/>
|
||||
<small>{{ t('settings.rclone.api.port.help') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h2>{{ t('settings.rclone.config.title') }}</h2>
|
||||
<p>{{ t('settings.rclone.config.subtitle') }}</p>
|
||||
|
||||
Reference in New Issue
Block a user