mirror of
https://github.com/OpenListTeam/OpenList-Desktop.git
synced 2025-11-25 11:18:32 +08:00
feat: optimize admin password management and add reset
This commit is contained in:
@@ -28,7 +28,7 @@ if (!getOpenlistArchMap[platformArch]) {
|
||||
}
|
||||
|
||||
// Rclone version management
|
||||
let rcloneVersion = 'v1.70.1'
|
||||
let rcloneVersion = 'v1.70.3'
|
||||
const rcloneVersionUrl = 'https://github.com/rclone/rclone/releases/latest/download/version.txt'
|
||||
|
||||
async function getLatestRcloneVersion() {
|
||||
@@ -42,7 +42,7 @@ async function getLatestRcloneVersion() {
|
||||
}
|
||||
|
||||
// openlist version management
|
||||
let openlistVersion = 'v4.0.3'
|
||||
let openlistVersion = 'v4.0.8'
|
||||
|
||||
async function getLatestOpenlistVersion() {
|
||||
try {
|
||||
@@ -51,7 +51,7 @@ async function getLatestOpenlistVersion() {
|
||||
getFetchOptions()
|
||||
)
|
||||
const data = await response.json()
|
||||
openlistVersion = data.tag_name || 'v4.0.3'
|
||||
openlistVersion = data.tag_name || 'v4.0.8'
|
||||
console.log(`Latest OpenList version: ${openlistVersion}`)
|
||||
} catch (error) {
|
||||
console.log('Error fetching latest OpenList version:', error.message)
|
||||
|
||||
@@ -1,16 +1,103 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use tauri::State;
|
||||
|
||||
use crate::object::structs::AppState;
|
||||
|
||||
static ADMIN_PWD_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"Successfully created the admin user and the initial password is: (\w+)")
|
||||
.expect("Invalid regex pattern")
|
||||
});
|
||||
fn generate_random_password() -> String {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
|
||||
if let Ok(duration) = SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
duration.as_nanos().hash(&mut hasher);
|
||||
}
|
||||
|
||||
std::process::id().hash(&mut hasher);
|
||||
|
||||
let dummy = [1, 2, 3];
|
||||
(dummy.as_ptr() as usize).hash(&mut hasher);
|
||||
|
||||
let hash = hasher.finish();
|
||||
|
||||
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
let mut password = String::new();
|
||||
let mut current_hash = hash;
|
||||
|
||||
for _ in 0..16 {
|
||||
let index = (current_hash % chars.len() as u64) as usize;
|
||||
password.push(chars.chars().nth(index).unwrap());
|
||||
current_hash = current_hash.wrapping_mul(1103515245).wrapping_add(12345);
|
||||
}
|
||||
|
||||
password
|
||||
}
|
||||
|
||||
async fn execute_openlist_admin_set(
|
||||
password: &str,
|
||||
state: &State<'_, AppState>,
|
||||
) -> Result<(), String> {
|
||||
let exe_path =
|
||||
env::current_exe().map_err(|e| format!("Failed to determine executable path: {e}"))?;
|
||||
let app_dir = exe_path
|
||||
.parent()
|
||||
.ok_or("Executable has no parent directory")?;
|
||||
|
||||
let possible_names = ["openlist", "openlist.exe"];
|
||||
|
||||
let mut openlist_exe = None;
|
||||
for name in &possible_names {
|
||||
let exe_path = app_dir.join(name);
|
||||
if exe_path.exists() {
|
||||
openlist_exe = Some(exe_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let openlist_exe = openlist_exe.ok_or_else(|| {
|
||||
format!(
|
||||
"OpenList executable not found. Searched for: {:?} in {}",
|
||||
possible_names,
|
||||
app_dir.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
log::info!(
|
||||
"Setting new admin password using: {}",
|
||||
openlist_exe.display()
|
||||
);
|
||||
|
||||
let mut cmd = Command::new(&openlist_exe);
|
||||
cmd.args(["admin", "set", password]);
|
||||
|
||||
if let Some(settings) = state.get_settings()
|
||||
&& !settings.openlist.data_dir.is_empty()
|
||||
{
|
||||
cmd.arg("--data");
|
||||
cmd.arg(&settings.openlist.data_dir);
|
||||
log::info!("Using data directory: {}", settings.openlist.data_dir);
|
||||
}
|
||||
log::info!("Executing command: {cmd:?}");
|
||||
let output = cmd
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to execute openlist command: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
log::error!("OpenList admin set command failed. stdout: {stdout}, stderr: {stderr}");
|
||||
return Err(format!("OpenList admin set command failed: {stderr}"));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
log::info!("Successfully set admin password. Output: {stdout}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_log_paths(source: Option<&str>, data_dir: Option<&str>) -> Result<Vec<PathBuf>, String> {
|
||||
let exe_path =
|
||||
@@ -45,20 +132,78 @@ fn resolve_log_paths(source: Option<&str>, data_dir: Option<&str>) -> Result<Vec
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_admin_password(state: State<'_, AppState>) -> Result<String, String> {
|
||||
let data_dir = state
|
||||
.get_settings()
|
||||
.map(|s| s.openlist.data_dir)
|
||||
.filter(|d| !d.is_empty());
|
||||
if let Some(settings) = state.get_settings()
|
||||
&& let Some(ref stored_password) = settings.app.admin_password
|
||||
&& !stored_password.is_empty()
|
||||
{
|
||||
log::info!("Found admin password in local settings");
|
||||
return Ok(stored_password.clone());
|
||||
}
|
||||
|
||||
let paths = resolve_log_paths(Some("openlist_core"), data_dir.as_deref())?;
|
||||
let content =
|
||||
std::fs::read_to_string(&paths[0]).map_err(|e| format!("Failed to read log file: {e}"))?;
|
||||
let new_password = generate_random_password();
|
||||
|
||||
ADMIN_PWD_REGEX
|
||||
.captures_iter(&content)
|
||||
.filter_map(|cap| cap.get(1).map(|m| m.as_str().to_string()))
|
||||
.last()
|
||||
.ok_or_else(|| "No admin password found in logs".into())
|
||||
if let Err(e) = execute_openlist_admin_set(&new_password, &state).await {
|
||||
return Err(format!("Failed to set new admin password: {e}"));
|
||||
}
|
||||
|
||||
log::info!("Successfully generated and set new admin password");
|
||||
|
||||
if let Some(mut settings) = state.get_settings() {
|
||||
settings.app.admin_password = Some(new_password.clone());
|
||||
state.update_settings(settings.clone());
|
||||
|
||||
if let Err(e) = settings.save() {
|
||||
log::warn!("Failed to save new admin password to settings: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(new_password)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn reset_admin_password(state: State<'_, AppState>) -> Result<String, String> {
|
||||
log::info!("Forcing admin password reset");
|
||||
let new_password = generate_random_password();
|
||||
if let Err(e) = execute_openlist_admin_set(&new_password, &state).await {
|
||||
return Err(format!("Failed to set new admin password: {e}"));
|
||||
}
|
||||
log::info!("Successfully generated and set new admin password via force reset");
|
||||
|
||||
if let Some(mut settings) = state.get_settings() {
|
||||
settings.app.admin_password = Some(new_password.clone());
|
||||
state.update_settings(settings.clone());
|
||||
|
||||
if let Err(e) = settings.save() {
|
||||
log::warn!("Failed to save new admin password to settings: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(new_password)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_admin_password(
|
||||
password: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<String, String> {
|
||||
log::info!("Setting custom admin password");
|
||||
|
||||
if let Err(e) = execute_openlist_admin_set(&password, &state).await {
|
||||
return Err(format!("Failed to set admin password: {e}"));
|
||||
}
|
||||
|
||||
log::info!("Successfully set custom admin password");
|
||||
|
||||
if let Some(mut settings) = state.get_settings() {
|
||||
settings.app.admin_password = Some(password.clone());
|
||||
state.update_settings(settings.clone());
|
||||
|
||||
if let Err(e) = settings.save() {
|
||||
log::warn!("Failed to save admin password to settings: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(password)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -7,6 +7,7 @@ pub struct AppConfig {
|
||||
pub gh_proxy: Option<String>,
|
||||
pub gh_proxy_api: Option<bool>,
|
||||
pub open_links_in_browser: Option<bool>,
|
||||
pub admin_password: Option<String>,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
@@ -17,6 +18,7 @@ impl AppConfig {
|
||||
gh_proxy: None,
|
||||
gh_proxy_api: Some(false),
|
||||
open_links_in_browser: Some(false),
|
||||
admin_password: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ use cmd::firewall::{add_firewall_rule, check_firewall_rule, remove_firewall_rule
|
||||
use cmd::http_api::{
|
||||
delete_process, get_process_list, restart_process, start_process, stop_process, update_process,
|
||||
};
|
||||
use cmd::logs::{clear_logs, get_admin_password, get_logs};
|
||||
use cmd::logs::{
|
||||
clear_logs, get_admin_password, get_logs, reset_admin_password, set_admin_password,
|
||||
};
|
||||
use cmd::openlist_core::{create_openlist_core_process, get_openlist_core_status};
|
||||
use cmd::os_operate::{
|
||||
get_available_versions, list_files, open_file, open_folder, open_url, open_url_in_browser,
|
||||
@@ -148,6 +150,8 @@ pub fn run() {
|
||||
get_logs,
|
||||
clear_logs,
|
||||
get_admin_password,
|
||||
reset_admin_password,
|
||||
set_admin_password,
|
||||
get_binary_version,
|
||||
select_directory,
|
||||
get_available_versions,
|
||||
|
||||
@@ -79,7 +79,9 @@ export class TauriAPI {
|
||||
call('get_logs', { source: src }),
|
||||
clear: (src?: 'openlist' | 'rclone' | 'app' | 'openlist_core'): Promise<boolean> =>
|
||||
call('clear_logs', { source: src }),
|
||||
adminPassword: (): Promise<string> => call('get_admin_password')
|
||||
adminPassword: (): Promise<string> => call('get_admin_password'),
|
||||
resetAdminPassword: (): Promise<string> => call('reset_admin_password'),
|
||||
setAdminPassword: (password: string): Promise<string> => call('set_admin_password', { password })
|
||||
}
|
||||
|
||||
// --- Binary management ---
|
||||
|
||||
@@ -27,12 +27,20 @@
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="showAdminPassword"
|
||||
@click="copyAdminPassword"
|
||||
class="action-btn password-btn icon-only-btn"
|
||||
:title="t('dashboard.quickActions.showAdminPassword')"
|
||||
:title="t('dashboard.quickActions.copyAdminPassword')"
|
||||
>
|
||||
<Key :size="16" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="resetAdminPassword"
|
||||
class="action-btn reset-password-btn icon-only-btn"
|
||||
:title="t('dashboard.quickActions.resetAdminPassword')"
|
||||
>
|
||||
<RotateCcw :size="16" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -164,7 +172,7 @@ const viewMounts = () => {
|
||||
router.push({ name: 'Mount' })
|
||||
}
|
||||
|
||||
const showAdminPassword = async () => {
|
||||
const copyAdminPassword = async () => {
|
||||
try {
|
||||
const password = await appStore.getAdminPassword()
|
||||
if (password) {
|
||||
@@ -237,6 +245,15 @@ const showAdminPassword = async () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get admin password:', error)
|
||||
showNotification('error', 'Failed to get admin password. Please check the logs.')
|
||||
}
|
||||
}
|
||||
|
||||
const resetAdminPassword = async () => {
|
||||
try {
|
||||
const newPassword = await appStore.resetAdminPassword()
|
||||
if (newPassword) {
|
||||
await navigator.clipboard.writeText(newPassword)
|
||||
|
||||
const notification = document.createElement('div')
|
||||
notification.innerHTML = `
|
||||
@@ -244,7 +261,7 @@ const showAdminPassword = async () => {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: linear-gradient(135deg, rgb(239, 68, 68), rgb(220, 38, 38));
|
||||
background: linear-gradient(135deg, rgb(16, 185, 129), rgb(5, 150, 105));
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
@@ -252,12 +269,13 @@ const showAdminPassword = async () => {
|
||||
z-index: 10000;
|
||||
font-weight: 500;
|
||||
max-width: 300px;
|
||||
word-break: break-all;
|
||||
">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<div style="font-size: 18px;">✗</div>
|
||||
<div style="font-size: 18px;">✓</div>
|
||||
<div>
|
||||
<div style="font-size: 14px; margin-bottom: 4px;">Failed to get admin password</div>
|
||||
<div style="font-size: 12px; opacity: 0.9;">Please check the logs.</div>
|
||||
<div style="font-size: 14px; margin-bottom: 4px;">Admin password reset and copied!</div>
|
||||
<div style="font-size: 12px; opacity: 0.9; font-family: monospace;">${newPassword}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -269,6 +287,12 @@ const showAdminPassword = async () => {
|
||||
notification.parentNode.removeChild(notification)
|
||||
}
|
||||
}, 4000)
|
||||
} else {
|
||||
showNotification('error', 'Failed to reset admin password. Please check the logs.')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to reset admin password:', error)
|
||||
showNotification('error', 'Failed to reset admin password. Please check the logs.')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,6 +566,12 @@ onUnmounted(() => {
|
||||
border-color: rgba(147, 51, 234, 0.3);
|
||||
}
|
||||
|
||||
.reset-password-btn:hover:not(:disabled) {
|
||||
background: rgb(239, 68, 68);
|
||||
color: white;
|
||||
border-color: rgba(220, 38, 38, 0.3);
|
||||
}
|
||||
|
||||
.service-indicator-btn {
|
||||
background: var(--color-surface);
|
||||
border-color: var(--color-border-secondary);
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"stopRclone": "Stop RClone",
|
||||
"manageMounts": "Manage Mounts",
|
||||
"autoLaunch": "Auto Launch Core(not app)",
|
||||
"showAdminPassword": "Show/Copy admin password from logs",
|
||||
"copyAdminPassword": "Copy admin password",
|
||||
"resetAdminPassword": "Reset admin password",
|
||||
"firewall": {
|
||||
"enable": "Allow Port",
|
||||
"disable": "Remove Port Allow",
|
||||
@@ -150,6 +151,19 @@
|
||||
"title": "Auto-launch on startup",
|
||||
"description": "Automatically start OpenList service when the application launches"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"title": "Admin Password",
|
||||
"subtitle": "Manage the admin password for OpenList Core web interface",
|
||||
"currentPassword": "Admin Password",
|
||||
"passwordPlaceholder": "Enter admin password or click reset to generate",
|
||||
"resetTitle": "Reset admin password to a new random value",
|
||||
"confirmReset": "Are you sure you want to reset the admin password? This will generate a new password and update the OpenList Core configuration.",
|
||||
"resetSuccess": "Admin password reset successfully! New password has been generated and saved.",
|
||||
"resetFailed": "Failed to reset admin password. Please check the logs for more details.",
|
||||
"passwordUpdated": "Admin password updated successfully!",
|
||||
"passwordUpdateFailed": "Failed to update admin password. Please check the logs for more details.",
|
||||
"help": "Enter a custom admin password or click the reset button to generate a new random password. Click 'Save Changes' to apply the password to OpenList Core."
|
||||
}
|
||||
},
|
||||
"rclone": {
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"stopRclone": "停止 RClone",
|
||||
"manageMounts": "管理挂载",
|
||||
"autoLaunch": "自动启动核心(非桌面app)",
|
||||
"showAdminPassword": "显示/复制日志中的管理员密码",
|
||||
"copyAdminPassword": "复制管理员密码",
|
||||
"resetAdminPassword": "重置管理员密码",
|
||||
"firewall": {
|
||||
"enable": "放行端口",
|
||||
"disable": "移除端口放行",
|
||||
@@ -150,6 +151,19 @@
|
||||
"title": "开机自启",
|
||||
"description": "应用程序启动时自动启动 OpenList 服务"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"title": "管理员密码",
|
||||
"subtitle": "管理 OpenList 核心网页界面的管理员密码",
|
||||
"currentPassword": "管理员密码",
|
||||
"passwordPlaceholder": "输入管理员密码或点击重置生成",
|
||||
"resetTitle": "重置管理员密码为新的随机值",
|
||||
"confirmReset": "您确定要重置管理员密码吗?这将生成一个新密码并更新 OpenList 核心配置。",
|
||||
"resetSuccess": "管理员密码重置成功!已生成新密码并保存。",
|
||||
"resetFailed": "重置管理员密码失败。请查看日志了解详细信息。",
|
||||
"passwordUpdated": "管理员密码更新成功!",
|
||||
"passwordUpdateFailed": "更新管理员密码失败。请查看日志了解详细信息。",
|
||||
"help": "输入自定义管理员密码或点击重置按钮生成新的随机密码。点击'保存更改'将密码应用到 OpenList 核心。"
|
||||
}
|
||||
},
|
||||
"rclone": {
|
||||
|
||||
@@ -9,7 +9,14 @@ export const useAppStore = defineStore('app', () => {
|
||||
const settings = ref<MergedSettings>({
|
||||
openlist: { port: 5244, data_dir: '', auto_launch: false, ssl_enabled: false },
|
||||
rclone: { config: {} },
|
||||
app: { theme: 'light', auto_update_enabled: true, gh_proxy: '', gh_proxy_api: false, open_links_in_browser: false }
|
||||
app: {
|
||||
theme: 'light',
|
||||
auto_update_enabled: true,
|
||||
gh_proxy: '',
|
||||
gh_proxy_api: false,
|
||||
open_links_in_browser: false,
|
||||
admin_password: undefined
|
||||
}
|
||||
})
|
||||
const openlistCoreStatus = ref<OpenListCoreStatus>({ running: false })
|
||||
const remoteConfigs = ref<IRemoteConfig>({})
|
||||
@@ -698,6 +705,36 @@ export const useAppStore = defineStore('app', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function resetAdminPassword(): Promise<string | null> {
|
||||
try {
|
||||
const newPassword = await TauriAPI.logs.resetAdminPassword()
|
||||
|
||||
if (newPassword) {
|
||||
settings.value.app.admin_password = newPassword
|
||||
await saveSettings()
|
||||
}
|
||||
|
||||
return newPassword
|
||||
} catch (err) {
|
||||
console.error('Failed to reset admin password:', err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function setAdminPassword(password: string): Promise<boolean> {
|
||||
try {
|
||||
await TauriAPI.logs.setAdminPassword(password)
|
||||
|
||||
settings.value.app.admin_password = password
|
||||
await saveSettings()
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Failed to set admin password:', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Update management functions
|
||||
function setUpdateAvailable(available: boolean, updateInfo?: UpdateCheck) {
|
||||
updateAvailable.value = available
|
||||
@@ -756,6 +793,8 @@ export const useAppStore = defineStore('app', () => {
|
||||
clearError,
|
||||
init,
|
||||
getAdminPassword,
|
||||
resetAdminPassword,
|
||||
setAdminPassword,
|
||||
|
||||
setTheme,
|
||||
toggleTheme,
|
||||
|
||||
1
src/types/types.d.ts
vendored
1
src/types/types.d.ts
vendored
@@ -49,6 +49,7 @@ interface AppConfig {
|
||||
gh_proxy?: string
|
||||
gh_proxy_api?: boolean
|
||||
open_links_in_browser?: boolean
|
||||
admin_password?: string
|
||||
}
|
||||
|
||||
interface MergedSettings {
|
||||
|
||||
@@ -16,6 +16,7 @@ const messageType = ref<'success' | 'error' | 'info'>('info')
|
||||
const activeTab = ref('openlist')
|
||||
const rcloneConfigJson = ref('')
|
||||
const autoStartApp = ref(false)
|
||||
const isResettingPassword = ref(false)
|
||||
|
||||
const openlistCoreSettings = reactive({ ...appStore.settings.openlist })
|
||||
const rcloneSettings = reactive({ ...appStore.settings.rclone })
|
||||
@@ -73,8 +74,12 @@ onMounted(async () => {
|
||||
if (!appSettings.gh_proxy) appSettings.gh_proxy = ''
|
||||
if (appSettings.gh_proxy_api === undefined) appSettings.gh_proxy_api = false
|
||||
if (appSettings.open_links_in_browser === undefined) appSettings.open_links_in_browser = false
|
||||
if (!appSettings.admin_password) appSettings.admin_password = ''
|
||||
originalOpenlistPort = openlistCoreSettings.port || 5244
|
||||
originalDataDir = openlistCoreSettings.data_dir
|
||||
|
||||
// Load current admin password
|
||||
await loadCurrentAdminPassword()
|
||||
})
|
||||
|
||||
const hasUnsavedChanges = computed(() => {
|
||||
@@ -111,17 +116,33 @@ const handleSave = async () => {
|
||||
appStore.settings.openlist = { ...openlistCoreSettings }
|
||||
appStore.settings.rclone = { ...rcloneSettings }
|
||||
appStore.settings.app = { ...appSettings }
|
||||
|
||||
const originalAdminPassword = appStore.settings.app.admin_password
|
||||
const needsPasswordUpdate = originalAdminPassword !== appSettings.admin_password && appSettings.admin_password
|
||||
|
||||
if (originalOpenlistPort !== openlistCoreSettings.port || originalDataDir !== openlistCoreSettings.data_dir) {
|
||||
await appStore.saveSettingsWithCoreUpdate()
|
||||
} else {
|
||||
await appStore.saveSettings()
|
||||
}
|
||||
|
||||
originalOpenlistPort = openlistCoreSettings.port || 5244
|
||||
originalDataDir = openlistCoreSettings.data_dir
|
||||
|
||||
if (needsPasswordUpdate) {
|
||||
try {
|
||||
await appStore.setAdminPassword(appSettings.admin_password!)
|
||||
message.value = t('settings.service.admin.passwordUpdated')
|
||||
messageType.value = 'success'
|
||||
} catch (error) {
|
||||
console.error('Failed to update admin password:', error)
|
||||
message.value = t('settings.service.admin.passwordUpdateFailed')
|
||||
messageType.value = 'error'
|
||||
}
|
||||
} else {
|
||||
message.value = t('settings.saved')
|
||||
messageType.value = 'success'
|
||||
}
|
||||
|
||||
originalOpenlistPort = openlistCoreSettings.port || 5244
|
||||
originalDataDir = openlistCoreSettings.data_dir
|
||||
} catch (error) {
|
||||
message.value = t('settings.saveFailed')
|
||||
messageType.value = 'error'
|
||||
@@ -177,6 +198,42 @@ const handleSelectDataDir = async () => {
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
const handleResetAdminPassword = async () => {
|
||||
isResettingPassword.value = true
|
||||
try {
|
||||
const newPassword = await appStore.resetAdminPassword()
|
||||
if (newPassword) {
|
||||
appSettings.admin_password = newPassword
|
||||
message.value = t('settings.service.admin.resetSuccess')
|
||||
messageType.value = 'success'
|
||||
await navigator.clipboard.writeText(newPassword)
|
||||
} else {
|
||||
message.value = t('settings.service.admin.resetFailed')
|
||||
messageType.value = 'error'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to reset admin password:', error)
|
||||
message.value = t('settings.service.admin.resetFailed')
|
||||
messageType.value = 'error'
|
||||
} finally {
|
||||
isResettingPassword.value = false
|
||||
setTimeout(() => {
|
||||
message.value = ''
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
const loadCurrentAdminPassword = async () => {
|
||||
try {
|
||||
const password = await appStore.getAdminPassword()
|
||||
if (password) {
|
||||
appSettings.admin_password = password
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load admin password:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -288,6 +345,33 @@ const handleSelectDataDir = async () => {
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h2>{{ t('settings.service.admin.title') }}</h2>
|
||||
<p>{{ t('settings.service.admin.subtitle') }}</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ t('settings.service.admin.currentPassword') }}</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="appSettings.admin_password"
|
||||
type="text"
|
||||
class="form-input"
|
||||
:placeholder="t('settings.service.admin.passwordPlaceholder')"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@click="handleResetAdminPassword"
|
||||
:disabled="isResettingPassword"
|
||||
class="input-addon-btn reset-password-btn"
|
||||
:title="t('settings.service.admin.resetTitle')"
|
||||
>
|
||||
<RotateCcw :size="16" />
|
||||
</button>
|
||||
</div>
|
||||
<small>{{ t('settings.service.admin.help') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'rclone'" class="tab-content">
|
||||
|
||||
@@ -384,6 +384,22 @@
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.input-addon-btn.reset-password-btn {
|
||||
background: var(--color-error-background, #fef2f2);
|
||||
color: var(--color-error, #dc2626);
|
||||
border-color: var(--color-error-border, #fecaca);
|
||||
}
|
||||
|
||||
.input-addon-btn.reset-password-btn:hover:not(:disabled) {
|
||||
background: var(--color-error, #dc2626);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.input-addon-btn.reset-password-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Switch */
|
||||
.switch-label {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user