From 8ab55bc93cf562f8f4c995c45168ba98f8c9becc Mon Sep 17 00:00:00 2001 From: Minoricew <154642983+Minoricew@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:51:54 +0800 Subject: [PATCH] [Feat] Further PLS ctrl & childProc perf improvements --- .gitignore | 4 - .../init/main/ipcModules/plsIpcHandler.js | 127 ++++- src/aura/init/shared/configManager.js | 144 +++++- src/aura/init/shared/registryManager.js | 309 +++++++++-- src/aura/types/shared/global.d.ts | 1 + src/aura/types/shared/pls/status.d.ts | 4 + src/aura/ui/composables/plsConfigManager.js | 2 +- src/aura/ui/composables/settingsRenderer.js | 485 +++++++++--------- src/aura/ui/css/form.css | 2 +- src/aura/ui/css/global.css | 4 + src/aura/ui/js/plsConnectionManager.js | 127 ++++- src/aura/ui/js/plsListener.js | 23 + src/aura/ui/pages/config/config.html | 15 + .../behaviourCtrl/plsStatus.css | 59 +++ .../behaviourCtrl/plsStatus.html | 44 ++ .../configSubPages/behaviourCtrl/plsStatus.js | 358 ++++++++++++- .../behaviourCtrl/settings/basic.js | 167 ++++-- .../preferences/settings/aura.js | 10 +- src/aura/ui/pls/routes/basic.js | 19 +- 19 files changed, 1531 insertions(+), 373 deletions(-) diff --git a/.gitignore b/.gitignore index dd70d5d..67c28fb 100755 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,3 @@ desktop.ini # Editor Files .vscode/ - -# Misc -*.bak -*.log diff --git a/src/aura/init/main/ipcModules/plsIpcHandler.js b/src/aura/init/main/ipcModules/plsIpcHandler.js index 638038a..20cbb05 100755 --- a/src/aura/init/main/ipcModules/plsIpcHandler.js +++ b/src/aura/init/main/ipcModules/plsIpcHandler.js @@ -2,10 +2,64 @@ const __SCOPE = "main"; -const os = require("os"); +const { exec } = require("child_process"); const fs = require("fs"); const path = require("path"); +const functions = { + querySvcDetail: ( + /** @type {string} */ svcName, + /** @type {string} */ findTarget + ) => { + return new Promise((resolve) => { + exec( + `sc query ${svcName}`, + { encoding: "utf8" }, + (error, stdout, stderr) => { + if (error) { + resolve({ + success: true, + result: false, + }); + return; + } + if (stdout.includes(findTarget)) { + resolve({ + success: true, + result: true, + }); + } else { + resolve({ + success: true, + result: false, + }); + } + } + ); + }); + }, + + /** + * + * @param {string} logHeader + * @param {string} binPath + * @param {string} command + * @returns {Promise<{ success: boolean, errorObj?: Error }>} + */ + execCommand: (logHeader, binPath, command) => { + return new Promise((resolve) => { + exec(`"${binPath}" ${command}`, (error, stdout, stderr) => { + if (error) { + console.error(`${logHeader} Failed to execute command:`, error); + resolve({ success: false, errorObj: error }); + return; + } + resolve({ success: true }); + }); + }); + }, +}; + /** * * @param {import("../../../types/main/electron").AuraIPCMain} ipcMain @@ -13,6 +67,10 @@ const path = require("path"); const applyPlsIpcHandler = (ipcMain) => { const methodBase = "$aura.pls"; + const PLS_INSTALL_DIR = path.join("C:\\Program Files", "HugoAura PLS"); + const PLS_BIN_PATH = path.join(PLS_INSTALL_DIR, "bin", "HugoAura-PLS.exe"); + const PLS_SVC_NAME = "HugoAuraPLS"; + const isPlsDetached = process.argv.includes("--pls-detach"); global.__HUGO_AURA__.plsStats = { @@ -26,15 +84,14 @@ const applyPlsIpcHandler = (ipcMain) => { }; ipcMain.handle( - `${methodBase}.getPlsFolderExists`, + `${methodBase}.getPlsBinExists`, /** * * @returns {{ success: boolean; data: { isExists: boolean }; error?: Error }} */ (_event, _arg) => { - const plsFolderPath = path.join("C:\\Program Files", "HugoAura PLS"); try { - const result = fs.existsSync(plsFolderPath); + const result = fs.existsSync(PLS_BIN_PATH); return { success: true, data: { isExists: result }, @@ -189,6 +246,68 @@ const applyPlsIpcHandler = (ipcMain) => { } ); + ipcMain.handle( + `${methodBase}.plsLifecycleQuery`, + /** + * + * @param {import("electron").IpcMainInvokeEvent} _event + * @param {{ target: import("../../../types/shared/pls/status").PLSLifecycleType }} arg + * @returns {Promise<{ success: boolean, result: boolean }>} + */ + async (_event, arg) => { + switch (arg.target) { + case "isDetached": + return { success: true, result: isPlsDetached }; + case "isSvcInstalled": + return await functions.querySvcDetail(PLS_SVC_NAME, "SERVICE_NAME"); + case "isSvcStart": + return await functions.querySvcDetail(PLS_SVC_NAME, "RUNNING"); + default: + console.warn( + `[HugoAura / IPC / PLS] Invalid arg.target: ${arg.target}` + ); + return { + success: false, + result: false, + }; + } + } + ); + + ipcMain.handle( + `${methodBase}.plsLifecycleControl`, + /** + * + * @param {*} _event + * @param {{ target: import("../../../types/shared/pls/status").PLSLifecycleControlType }} arg + * @returns {Promise<{ success: boolean, errorObj?: Error }>} + */ + async (_event, arg) => { + const logHeader = "[HugoAura / IPC / PLS] "; + + if (!global.__HUGO_AURA__.plsStats?.installed) { + return { success: false, errorObj: new Error("PLS not installed") }; + } + + switch (arg.target) { + case "instSvc": + return await functions.execCommand( + logHeader, + PLS_BIN_PATH, + "install" + ); + case "rmSvc": + return await functions.execCommand(logHeader, PLS_BIN_PATH, "remove"); + case "startSvc": + return await functions.execCommand(logHeader, PLS_BIN_PATH, "start"); + case "stopSvc": + return await functions.execCommand(logHeader, PLS_BIN_PATH, "stop"); + default: + return { success: false, errorObj: new Error("Method not found") }; + } + } + ); + ipcMain.handle( `${methodBase}.retryPlsConnect`, /** diff --git a/src/aura/init/shared/configManager.js b/src/aura/init/shared/configManager.js index 5ba180a..cc66709 100755 --- a/src/aura/init/shared/configManager.js +++ b/src/aura/init/shared/configManager.js @@ -164,12 +164,12 @@ class ConfigManager { /** * * @param {Record} config - * @returns {boolean} + * @returns {Promise} */ - writeConfig(config) { + async writeConfig(config) { try { if (this.useEncConfig) { - const hashedPasswdResultObj = this.retrieveEncPassword(); + const hashedPasswdResultObj = await this.retrieveEncPasswordAsync(); if (hashedPasswdResultObj.success && hashedPasswdResultObj.data) { this.encryptConfig(config, hashedPasswdResultObj.data); } else { @@ -261,7 +261,7 @@ class ConfigManager { * * @param {SHA256EncryptedPassword} password */ - saveEncPassword(password) { + async saveEncPassword(password) { let macAddr = this.priv_getMacAddr(); let fallbackToStaticKey = false; @@ -290,25 +290,25 @@ class ConfigManager { const ivHex = iv.toString("hex"); const saltHex = randomSalt.toString("hex"); - registryManager.createOrUpdateRegKey( + await registryManager.createOrUpdateRegKey( LMAK_SETTINGS_BASE, "LMAK_Value", encryptedPassword, true ); - registryManager.createOrUpdateRegKey( + await registryManager.createOrUpdateRegKey( LMAK_SETTINGS_BASE, "LMAK_IV", ivHex, true ); - registryManager.createOrUpdateRegKey( + await registryManager.createOrUpdateRegKey( LMAK_SETTINGS_BASE, "LMAK_Salt", saltHex, true ); - registryManager.createOrUpdateRegKey( + await registryManager.createOrUpdateRegKey( LMAK_SETTINGS_BASE, "LMAK_AuthTag", authTagHex, @@ -316,7 +316,7 @@ class ConfigManager { ); if (fallbackToStaticKey) { - registryManager.createOrUpdateRegKey( + await registryManager.createOrUpdateRegKey( LMAK_SETTINGS_BASE, "LMAK_FakeMac", macAddr, @@ -329,22 +329,22 @@ class ConfigManager { retrieveEncPassword() { try { - const authTagHex = registryManager.readRegKey( + const authTagHex = registryManager.readRegKeySync( LMAK_SETTINGS_BASE, "LMAK_AuthTag", true )?.data; - const ivHex = registryManager.readRegKey( + const ivHex = registryManager.readRegKeySync( LMAK_SETTINGS_BASE, "LMAK_IV", true )?.data; - const saltHex = registryManager.readRegKey( + const saltHex = registryManager.readRegKeySync( LMAK_SETTINGS_BASE, "LMAK_Salt", true )?.data; - const encPasswdHex = registryManager.readRegKey( + const encPasswdHex = registryManager.readRegKeySync( LMAK_SETTINGS_BASE, "LMAK_Value", true @@ -353,7 +353,7 @@ class ConfigManager { let macAddr = null; try { - macAddr = registryManager.readRegKey( + macAddr = registryManager.readRegKeySync( LMAK_SETTINGS_BASE, "LMAK_FakeMac", true @@ -433,8 +433,112 @@ class ConfigManager { } } - clearEncPasswdRegKey() { - registryManager.delRegKey(LMAK_SETTINGS_BASE, null); + async retrieveEncPasswordAsync() { + try { + const authTagHex = ( + await registryManager.readRegKey( + LMAK_SETTINGS_BASE, + "LMAK_AuthTag", + true + ) + ).data; + const ivHex = ( + await registryManager.readRegKey(LMAK_SETTINGS_BASE, "LMAK_IV", true) + ).data; + const saltHex = ( + await registryManager.readRegKey(LMAK_SETTINGS_BASE, "LMAK_Salt", true) + ).data; + const encPasswdHex = ( + await registryManager.readRegKey(LMAK_SETTINGS_BASE, "LMAK_Value", true) + ).data; + let isStaticKey = false; + let macAddr = null; + + try { + macAddr = ( + await registryManager.readRegKey( + LMAK_SETTINGS_BASE, + "LMAK_FakeMac", + true + ) + ).data; + if (!macAddr) { + isStaticKey = false; + } else { + isStaticKey = true; + } + } catch { + isStaticKey = false; + } + + if (!isStaticKey) { + macAddr = this.priv_getMacAddr(); + + if (!macAddr) { + console.error( + "[HugoAura / Config / ERROR] Failed to retrieve password from reg: MAC Address invalid." + ); + return { + success: false, + data: null, + error: new Error("Mac is null or undefined"), + }; + } + } + + if (!saltHex || !ivHex || !authTagHex || !encPasswdHex) { + console.error( + "[HugoAura / Config / ERROR] Failed to retrieve password from reg: Reg keys invalid." + ); + return { + success: false, + data: null, + error: new Error("Reg key invalid"), + }; + } + const salt = Buffer.from(saltHex, "hex"); + const iv = Buffer.from(ivHex, "hex"); + const authTag = Buffer.from(authTagHex, "hex"); + const encPasswd = Buffer.from(encPasswdHex, "utf-8").toString(); + + const key = crypto.scryptSync(macAddr, salt, 32); + const decipherIns = crypto.createDecipheriv( + CRYPTO_SETTINGS_AES.mode, + key, + iv, + { + // @ts-expect-error + authTagLength: CRYPTO_SETTINGS_AES.tagLength, + } + ); + + decipherIns.setAuthTag(authTag); + + const result = Buffer.concat([ + decipherIns.update(encPasswd, "hex"), + decipherIns.final(), + ]).toString(); + + return { + success: true, + data: result, + error: null, + }; + } catch (e) { + console.error( + "[HugoAura / Config / ERROR] Unexpected error occurred while retrieving password from reg, error:", + e + ); + return { + success: false, + data: null, + error: e, + }; + } + } + + async clearEncPasswdRegKey() { + await registryManager.delRegKey(LMAK_SETTINGS_BASE, null); } /** @@ -443,7 +547,7 @@ class ConfigManager { * @param {SHA256EncryptedPassword} passwd */ encryptConfig(configData, passwd) { - registryManager.initRegistry(); + registryManager.initRegistrySync(); const salt = crypto.randomBytes(CRYPTO_SETTINGS_AES.saltLength); const key = crypto.pbkdf2Sync( passwd, @@ -617,9 +721,9 @@ class ConfigManager { * * @param {Record | null} curConfig * @param {SHA256EncryptedPassword | undefined | null} passwd - * @returns {{success: boolean}} + * @returns {Promise<{success: boolean}>} */ - switchToDecConfig(curConfig, passwd = null) { + async switchToDecConfig(curConfig, passwd = null) { let decConfig = null; if (!curConfig && passwd) { const getDecConfigResult = this.decryptConfig(passwd); @@ -639,7 +743,7 @@ class ConfigManager { } this.useEncConfig = false; - this.clearEncPasswdRegKey(); + await this.clearEncPasswdRegKey(); // @ts-expect-error this.writeConfig(curConfig ? curConfig : decConfig); try { diff --git a/src/aura/init/shared/registryManager.js b/src/aura/init/shared/registryManager.js index 42f5e92..6a552ae 100755 --- a/src/aura/init/shared/registryManager.js +++ b/src/aura/init/shared/registryManager.js @@ -1,20 +1,52 @@ // @ts-check -const childProc = require("child_process"); +const { exec, execSync } = require("child_process"); // Constants const LOG_PREFIX = "[HugoAura / Init / Reg"; const LOG_PREFIX_FUNC = "[HugoAura / Reg"; -const AURA_REGISTRY_PATH = ["HKCU", "SOFTWARE", "HugoAura"].join("\\"); +const AURA_REGISTRY_PATH = ["HKEY_USERS", ".DEFAULT", "SOFTWARE", "HugoAura"].join("\\"); class RegistryManager { /** * @param {string} [path] + * @returns {Promise} */ - handleCreateReg(path) { + async handleCreateReg(path) { try { - const createResult = childProc.execSync(["reg", "add", path].join(" "), { + const { stdout } = await new Promise((resolve, reject) => { + exec( + ["reg", "add", path].join(" "), + { encoding: "utf8" }, + (error, stdout, stderr) => { + if (error) reject(error); + else resolve({ stdout, stderr }); + } + ); + }); + + console.log( + `${LOG_PREFIX} / SUCCESS] Registry path ${path} successfully created.` + ); + console.debug(`${LOG_PREFIX} / DEBUG] Reg add command stdout:`, stdout); + return true; + } catch (e) { + console.error( + `${LOG_PREFIX} / ERROR] Failed creating registry path, error:`, + e + ); + return false; + } + } + + /** + * @param {string} [path] + * @returns {boolean} + */ + handleCreateRegSync(path) { + try { + const createResult = execSync(["reg", "add", path].join(" "), { encoding: "utf8", }); @@ -27,6 +59,8 @@ class RegistryManager { createResult ); return true; + } else { + return false; } } catch (e) { console.error( @@ -37,9 +71,37 @@ class RegistryManager { } } - initRegistry() { + /** + * @returns {Promise} + */ + async initRegistry() { try { - const queryResult = childProc.execSync( + const { stdout } = await new Promise((resolve, reject) => { + exec( + ["reg", "query", AURA_REGISTRY_PATH].join(" "), + { encoding: "utf8" }, + (error, stdout, stderr) => { + if (error) reject(error); + else resolve({ stdout, stderr }); + } + ); + }); + + console.log(`${LOG_PREFIX}] Registry check up success.`); + console.debug(`${LOG_PREFIX}] Command stdout:`, stdout); + return true; + } catch (e) { + console.warn(`${LOG_PREFIX} / WARN] Failed to query registry, error:`, e); + return await this.handleCreateReg(AURA_REGISTRY_PATH); + } + } + + /** + * @returns {boolean} + */ + initRegistrySync() { + try { + const queryResult = execSync( ["reg", "query", AURA_REGISTRY_PATH].join(" "), { encoding: "utf8" } ); @@ -48,23 +110,81 @@ class RegistryManager { console.log(`${LOG_PREFIX}] Registry check up success.`); console.debug(`${LOG_PREFIX}] Command stdout:`, queryResult); return true; + } else { + return false; } } catch (e) { console.warn(`${LOG_PREFIX} / WARN] Failed to query registry, error:`, e); - return this.handleCreateReg(AURA_REGISTRY_PATH); + return this.handleCreateRegSync(AURA_REGISTRY_PATH); } } /** - * * @param {string} relativePath * @param {string} keyName * @param {string} keyVal * @param {boolean | undefined} silent + * @returns {Promise<{ success: boolean, error: Error | null }>} */ - createOrUpdateRegKey(relativePath, keyName, keyVal, silent = false) { + async createOrUpdateRegKey(relativePath, keyName, keyVal, silent = false) { try { - const result = childProc.execSync( + const { stdout } = await new Promise((resolve, reject) => { + exec( + [ + "reg", + "add", + [AURA_REGISTRY_PATH, relativePath].join("\\"), + "/v", + keyName, + "/t", + "REG_SZ", + "/d", + `\"${keyVal}\"`, + "/f", + ].join(" "), + { encoding: "utf8" }, + (error, stdout, stderr) => { + if (error) reject(error); + else resolve({ stdout, stderr }); + } + ); + }); + + if (!silent) { + console.debug( + `${LOG_PREFIX_FUNC} / SUCCESS] Successfully created / updated reg key ${relativePath}/${keyName} with data: ${keyVal}` + ); + console.debug( + `${LOG_PREFIX_FUNC} / SUCCESS] Add key command stdout:`, + stdout + ); + } + return { + success: true, + error: null, + }; + } catch (e) { + console.error( + `${LOG_PREFIX_FUNC} / ERROR] Failed to create / update reg key, error:`, + silent ? "" : e + ); + return { + success: false, + error: e, + }; + } + } + + /** + * @param {string} relativePath + * @param {string} keyName + * @param {string} keyVal + * @param {boolean | undefined} silent + * @returns {{ success: boolean, error: Error | null }} + */ + createOrUpdateRegKeySync(relativePath, keyName, keyVal, silent = false) { + try { + const result = execSync( [ "reg", "add", @@ -83,21 +203,76 @@ class RegistryManager { if (result) { if (!silent) { console.debug( - `${LOG_PREFIX_FUNC} / SUCCESS] Successfully created / updated reg key ${relativePath}/${keyName} with data: ${keyVal}` - ); - console.debug( - `${LOG_PREFIX_FUNC} / SUCCESS] Add key command stdout:`, + `${LOG_PREFIX} / SUCCESS] Successfully created / updated reg key ${relativePath}/${keyName} with data: ${keyVal}`, result ); + console.error(""); } - return { - success: true, - error: null, - }; } + return { + success: true, + error: null, + }; } catch (e) { console.error( - `${LOG_PREFIX_FUNC} / ERROR] Failed to create / update reg key, error:`, + `${LOG_PREFIX_FUNC} / ERROR] Failed to create / update reg key error:`, + e + ); + return { + success: false, + error: e, + }; + } + } + + /** + * @param {string} relativePath + * @param {string | null} keyName + * @param {boolean | undefined} silent + * @returns {Promise<{ success: boolean, error: Error | null }>} + */ + async delRegKey(relativePath, keyName, silent = false) { + if (keyName === undefined) { + throw new Error( + `${LOG_PREFIX_FUNC} / CRITICAL] Arg \"keyName\" for function \"delRegKey\" cannot be undefined. Only null or null accepted.` + ); + } + + try { + const { stdout } = await new Promise((resolve, reject) => { + exec( + [ + "reg", + "delete", + [AURA_REGISTRY_PATH, relativePath].join("\\"), + keyName ? "/v" : "", + keyName ? keyName : "", + "/f", + ].join(" "), + { encoding: "utf8" }, + (error, stdout, stderr) => { + if (error) reject(error); + else resolve({ stdout, stderr }); + } + ); + }); + + if (!silent) { + console.debug( + `${LOG_PREFIX_FUNC} / SUCCESS] Successfully deleted reg key ${relativePath}/${keyName}` + ); + console.debug( + `${LOG_PREFIX_FUNC} / SUCCESS] Delete key command stdout:`, + stdout + ); + } + return { + success: true, + error: null, + }; + } catch (e) { + console.error( + `${LOG_PREFIX_FUNC} / ERROR] Failed to delete reg key, error:`, silent ? "" : e ); return { @@ -107,24 +282,21 @@ class RegistryManager { } } - /*>>> BUC <<< - keyName === null --> delete the whole entry - >>> EUC <<<*/ /** - * * @param {string} relativePath * @param {string | null} keyName * @param {boolean | undefined} silent + * @returns {{ success: boolean, error: Error | null }} */ - delRegKey(relativePath, keyName, silent = false) { + delRegKeySync(relativePath, keyName, silent = false) { if (keyName === undefined) { throw new Error( - `${LOG_PREFIX_FUNC} / CRITICAL] Arg \"keyName\" for function \"delRegKey\" cannot be undefined. Only null or string accepted.` + `${LOG_PREFIX_FUNC} / CRITICAL] Arg \"keyName\" for function \"delRegKeySync\" cannot be undefined. Only null or string accepted.` ); } try { - const result = childProc.execSync( + const result = execSync( [ "reg", "delete", @@ -150,6 +322,11 @@ class RegistryManager { success: true, error: null, }; + } else { + return { + success: false, + error: null, + }; } } catch (e) { console.error( @@ -164,14 +341,76 @@ class RegistryManager { } /** - * * @param {string} relativePath * @param {string} keyName * @param {boolean | undefined} silent + * @returns {Promise<{ success: boolean, data: string | null, error: Error | null }>} */ - readRegKey(relativePath, keyName, silent = false) { + async readRegKey(relativePath, keyName, silent = false) { try { - const readResult = childProc.execSync( + const { stdout } = await new Promise((resolve, reject) => { + exec( + [ + "reg", + "query", + [AURA_REGISTRY_PATH, relativePath].join("\\"), + "/v", + `\"${keyName}\"`, + ].join(" "), + { encoding: "utf8" }, + (error, stdout, stderr) => { + if (error) reject(error); + else resolve({ stdout, stderr }); + } + ); + }); + + if (!silent) { + console.debug( + `${LOG_PREFIX_FUNC} / SUCCESS] Successfully read reg key ${relativePath}/${keyName}, stdout:`, + stdout + ); + } + + const match = stdout.match(/REG_SZ\s+(.+)/); + + if (!match) { + console.warn(`${LOG_PREFIX} / WARN] Data not found in stdout`); + return { + success: false, + data: null, + error: new Error("Data not found"), + }; + } + + const data = match[1].trim(); + return { + success: true, + data, + error: null, + }; + } catch (e) { + console.error( + `${LOG_PREFIX} / ERROR] Failed to read reg key, error:`, + e + ); + return { + success: false, + data: null, + error: e, + }; + } + } + + /** + * @param {string} relativePath + * @param {string} keyName + * @param {boolean | undefined} silent + * @returns {{ success: boolean, data: string | null, error: Error | null }} + */ + readRegKeySync(relativePath, keyName, silent = false) { + try { + const readResult = execSync( [ "reg", "query", @@ -185,14 +424,14 @@ class RegistryManager { if (readResult) { if (!silent) { console.debug( - `${LOG_PREFIX}] Successfully read reg key ${relativePath}/${keyName}, stdout:`, + `${LOG_PREFIX_FUNC} / SUCCESS] Successfully read reg key ${relativePath}/${keyName}, stdout:`, readResult ); } const match = readResult.match(/REG_SZ\s+(.+)/); if (!match) { - console.warn(`${LOG_PREFIX_FUNC} / WARN] Data not found in stdout`); + console.warn(`${LOG_PREFIX} / WARN] Data not found in stdout`); return { success: false, data: null, @@ -206,10 +445,16 @@ class RegistryManager { data, error: null, }; + } else { + return { + success: false, + data: null, + error: null, + }; } } catch (e) { console.error( - `${LOG_PREFIX_FUNC} / ERROR] Failed to read reg key, error:`, + `${LOG_PREFIX} / ERROR] Failed to read reg key, error:`, silent ? "" : e ); return { diff --git a/src/aura/types/shared/global.d.ts b/src/aura/types/shared/global.d.ts index fbd248c..f4f2be4 100755 --- a/src/aura/types/shared/global.d.ts +++ b/src/aura/types/shared/global.d.ts @@ -26,6 +26,7 @@ type GlobalHugoAuraConfig = AuraConfig; declare global { var ipcRenderer: RendererProcessOnlyVal; + var _ACCEPT_DATA: any; var __HUGO_AURA__: GlobalHugoAuraInfo; var __HUGO_AURA_CONFIG__: GlobalHugoAuraConfig; var __HUGO_AURA_CONFIG_MGR__: ConfigManager; diff --git a/src/aura/types/shared/pls/status.d.ts b/src/aura/types/shared/pls/status.d.ts index 6f710b3..0227676 100755 --- a/src/aura/types/shared/pls/status.d.ts +++ b/src/aura/types/shared/pls/status.d.ts @@ -9,3 +9,7 @@ interface PLSStatus { version: string; authToken: string; } + +type PLSLifecycleType = "isDetached" | "isSvcInstalled" | "isSvcStart"; + +type PLSLifecycleControlType = "instSvc" | "rmSvc" | "startSvc" | "stopSvc"; diff --git a/src/aura/ui/composables/plsConfigManager.js b/src/aura/ui/composables/plsConfigManager.js index 6560ea7..2154c75 100755 --- a/src/aura/ui/composables/plsConfigManager.js +++ b/src/aura/ui/composables/plsConfigManager.js @@ -61,7 +61,7 @@ const updatePlsConfigToRemote = async (configKey, configValue) => { const plsConfigUpdateEvent = new CustomEvent("onPLSConfigUpdate", { detail: { - path: configKey, + path: configLevels, value: configValue, }, }); diff --git a/src/aura/ui/composables/settingsRenderer.js b/src/aura/ui/composables/settingsRenderer.js index 968c995..9144170 100755 --- a/src/aura/ui/composables/settingsRenderer.js +++ b/src/aura/ui/composables/settingsRenderer.js @@ -41,7 +41,261 @@ const showToast = (entry) => { } }; -const settingsRenderer = (pendingEl, settingsObj, isPls = false) => { +const insertOrRemoveEl = (parent, child, isInsert = true) => { + if (Array.isArray(child)) { + for (const perEl of child) { + isInsert ? parent.appendChild(perEl) : parent.removeChild(perEl); + } + } else { + isInsert ? parent.appendChild(child) : parent.removeChild(child); + } +}; + +const renderInputArea = (entry, operationArea, descriptionArea) => { + switch (entry.type) { + case "switch": { + const switchEl = document.createElement("input"); + switchEl.classList.add("form-check-input"); + switchEl.type = "checkbox"; + switchEl.role = "switch"; + switchEl.id = entry.id; + const elValue = entry.valueGetter(); + switchEl.value = elValue; + switchEl.checked = elValue; + switchEl.addEventListener("change", async (event) => { + showToast(entry); + await entry.callbackFn(event.target.checked); + }); + operationArea.classList.add("form-check", "form-switch"); + return switchEl; + } + case "radio": { + const elValue = entry.valueGetter(); + const elArr = []; + for (const template of entry.templates) { + const inlineContainerEl = document.createElement("div"); + inlineContainerEl.classList.add("form-check", "form-check-inline"); + const radioEl = document.createElement("input"); + radioEl.value = template; + radioEl.classList.add("form-check-input"); + radioEl.type = "radio"; + radioEl.name = `${entry.id}Radios`; + radioEl.id = `${entry.id}Radio${entry.templates.indexOf(template)}`; + radioEl.checked = template === elValue ? true : false; + radioEl.addEventListener("change", async (event) => { + if (event.target.checked) { + showToast(entry); + await entry.callbackFn(event.target.value); + } + }); + inlineContainerEl.appendChild(radioEl); + const labelEl = document.createElement("label"); + labelEl.classList.add("form-check-label"); + labelEl.setAttribute("for", radioEl.id); + labelEl.textContent = + entry.templateLabels[entry.templates.indexOf(template)]; + inlineContainerEl.appendChild(labelEl); + elArr.push(inlineContainerEl); + } + return elArr; + } + case "input": { + const inputEl = document.createElement("input"); + inputEl.classList.add("form-control"); + inputEl.type = entry.subType; + inputEl.value = entry.valueGetter(); + inputEl.placeholder = entry.placeHolder; + inputEl.id = entry.id; + inputEl.addEventListener("change", async (event) => { + const result = await entry.callbackFn(event.target.value); + const success = result.valid; + if (success) { + showToast(entry); + + if (inputEl.className.includes("is-invalid")) { + inputEl.classList.remove("is-invalid"); + descriptionArea.textContent = entry.description; + descriptionArea.classList.remove("ase-desc-error-hint"); + } + } else { + inputEl.classList.add("is-invalid"); + descriptionArea.textContent = result.hint; + descriptionArea.classList.add("ase-desc-error-hint"); + } + }); + operationArea.classList.add("ase-operation-area-expanded"); + return inputEl; + } + default: + break; + } +}; + +const renderChild = (entry, formEl) => { + const entryContainerEl = document.createElement("div"); + entryContainerEl.classList.add("aura-settings-entry"); + entryContainerEl.id = `${entry.id}Container`; + + const entryInfoContainerEl = document.createElement("div"); + entryInfoContainerEl.classList.add("aura-settings-entry-info-container"); + const entryTitle = document.createElement("p"); + entryTitle.classList.add("aura-settings-entry-title"); + entryTitle.textContent = entry.name; + if (entry.restart) { + const powerIcon = document.createElement("i"); + powerIcon.classList.add( + "layui-icon", + "layui-icon-logout", + "aura-settings-entry-property-icon" + ); + powerIcon.setAttribute("data-bs-toggle", "tooltip"); + powerIcon.setAttribute("data-bs-placement", "top"); + powerIcon.setAttribute("data-bs-title", "需要重启 Electron 进程"); + entryTitle.appendChild(powerIcon); + } + if (entry.PLSRequired) { + const plsIcon = document.createElement("i"); + plsIcon.classList.add( + "layui-icon", + "layui-icon-component", + "aura-settings-entry-property-icon" + ); + plsIcon.setAttribute("data-bs-toggle", "tooltip"); + plsIcon.setAttribute("data-bs-placement", "top"); + plsIcon.setAttribute("data-bs-title", "需要 PLS 支持"); + entryTitle.appendChild(plsIcon); + } + if (entry.restartPLS) { + const plsIcon = document.createElement("i"); + plsIcon.classList.add( + "layui-icon", + "layui-icon-logout", + "aura-settings-entry-property-icon" + ); + plsIcon.setAttribute("data-bs-toggle", "tooltip"); + plsIcon.setAttribute("data-bs-placement", "top"); + plsIcon.setAttribute("data-bs-title", "需要重启 PLS 进程"); + entryTitle.appendChild(plsIcon); + } + if (entry.reload) { + const reloadIcon = document.createElement("i"); + reloadIcon.classList.add( + "layui-icon", + "layui-icon-refresh", + "aura-settings-entry-property-icon" + ); + reloadIcon.setAttribute("data-bs-toggle", "tooltip"); + reloadIcon.setAttribute("data-bs-placement", "top"); + reloadIcon.setAttribute("data-bs-title", "需要重载页面"); + entryTitle.appendChild(reloadIcon); + } + + const createToolTipIcon = (type, content) => { + const tipIcon = document.createElement("i"); + tipIcon.classList.add( + "layui-icon", + "layui-icon-tips", + "aura-settings-entry-property-icon" + ); + if (type === "warning") { + tipIcon.classList.add("aura-settings-entry-warning-icon"); + } + tipIcon.setAttribute("data-bs-toggle", "tooltip"); + tipIcon.setAttribute("data-bs-placement", "top"); + tipIcon.setAttribute("data-bs-title", content); + entryTitle.appendChild(tipIcon); + }; + + if (entry.tip) { + createToolTipIcon("tip", entry.tipTitle); + } + + if (entry.warning) { + createToolTipIcon("warning", entry.warningContent); + } + + const entryDescription = document.createElement("p"); + entryDescription.classList.add("aura-settings-entry-desc"); + entryDescription.textContent = entry.description; + entryInfoContainerEl.appendChild(entryTitle); + entryInfoContainerEl.appendChild(entryDescription); + + entryContainerEl.appendChild(entryInfoContainerEl); + + const entryOperationArea = document.createElement("div"); + entryOperationArea.classList.add("aura-settings-entry-operation-area"); + + let targetEl = renderInputArea(entry, entryOperationArea, entryDescription); + insertOrRemoveEl(entryOperationArea, targetEl, true); + + if (entry.reactive) { + document.addEventListener( + entry.PLSRequired ? "onPLSConfigUpdate" : "onHugoAuraConfigUpdate", + (event) => { + if (entry.reactiveVal.includes(event.detail.path.join("."))) { + insertOrRemoveEl(entryOperationArea, targetEl, false); + targetEl = renderInputArea( + entry, + entryOperationArea, + entryDescription + ); + insertOrRemoveEl(entryOperationArea, targetEl, true); + } + } + ); + } + + const setDisableStatus = (el, isDisable, hint = null) => { + if (isDisable) { + el.classList.add("ase-operation-area-disabled"); + if (hint) { + el.setAttribute("data-bs-toggle", "tooltip"); + el.setAttribute("data-bs-placement", "top"); + el.setAttribute("data-bs-title", hint); + } + } else { + el.setAttribute("data-bs-toggle", ""); + el.setAttribute("data-bs-placement", ""); + el.setAttribute("data-bs-title", ""); + el.classList.remove("ase-operation-area-disabled"); + } + }; + + if (entry.PLSRequired) { + if (!global.__HUGO_AURA__.plsStats.connected) { + setDisableStatus(entryOperationArea, true, "连接至 PLS 以继续"); + } + + document.addEventListener("onPLSStatsUpdate", (event) => { + if (event.detail.connected) { + setDisableStatus(entryOperationArea, false); + } else { + setDisableStatus(entryOperationArea, true, "连接至 PLS 以继续"); + } + }); + } + entryContainerEl.appendChild(entryOperationArea); + const isShow = entry.auraIf(); + if (!isShow) entryContainerEl.classList.add("aura-settings-entry-hidden"); + + if (entry.associateVal) { + document.addEventListener( + entry.PLSRequired ? "onPLSConfigUpdate" : "onHugoAuraConfigUpdate", + (event) => { + if (!entry.associateVal.includes(event.detail.path.join("."))) return; + const cls = entryContainerEl.classList; + const isShow = entry.auraIf(); + isShow + ? cls.remove("aura-settings-entry-hidden") + : cls.add("aura-settings-entry-hidden"); + } + ); + } + + formEl.appendChild(entryContainerEl); +}; + +const settingsRenderer = (pendingEl, settingsObj) => { const formEl = document.createElement("form"); formEl.classList.add("aura-settings-form"); for (const category of settingsObj) { @@ -50,234 +304,7 @@ const settingsRenderer = (pendingEl, settingsObj, isPls = false) => { categoryTitleEl.textContent = category.categoryName; formEl.appendChild(categoryTitleEl); for (const entry of category.child) { - const entryContainerEl = document.createElement("div"); - entryContainerEl.classList.add("aura-settings-entry"); - entryContainerEl.id = `${entry.id}Container`; - - const entryInfoContainerEl = document.createElement("div"); - entryInfoContainerEl.classList.add("aura-settings-entry-info-container"); - const entryTitle = document.createElement("p"); - entryTitle.classList.add("aura-settings-entry-title"); - entryTitle.textContent = entry.name; - if (entry.restart) { - const powerIcon = document.createElement("i"); - powerIcon.classList.add( - "layui-icon", - "layui-icon-logout", - "aura-settings-entry-property-icon" - ); - powerIcon.setAttribute("data-bs-toggle", "tooltip"); - powerIcon.setAttribute("data-bs-placement", "top"); - powerIcon.setAttribute("data-bs-title", "需要重启 Electron 进程"); - entryTitle.appendChild(powerIcon); - } - if (entry.PLSRequired) { - const plsIcon = document.createElement("i"); - plsIcon.classList.add( - "layui-icon", - "layui-icon-component", - "aura-settings-entry-property-icon" - ); - plsIcon.setAttribute("data-bs-toggle", "tooltip"); - plsIcon.setAttribute("data-bs-placement", "top"); - plsIcon.setAttribute("data-bs-title", "需要 PLS 支持"); - entryTitle.appendChild(plsIcon); - } - if (entry.restartPLS) { - const plsIcon = document.createElement("i"); - plsIcon.classList.add( - "layui-icon", - "layui-icon-logout", - "aura-settings-entry-property-icon" - ); - plsIcon.setAttribute("data-bs-toggle", "tooltip"); - plsIcon.setAttribute("data-bs-placement", "top"); - plsIcon.setAttribute("data-bs-title", "需要重启 PLS 进程"); - entryTitle.appendChild(plsIcon); - } - if (entry.reload) { - const reloadIcon = document.createElement("i"); - reloadIcon.classList.add( - "layui-icon", - "layui-icon-refresh", - "aura-settings-entry-property-icon" - ); - reloadIcon.setAttribute("data-bs-toggle", "tooltip"); - reloadIcon.setAttribute("data-bs-placement", "top"); - reloadIcon.setAttribute("data-bs-title", "需要重载页面"); - entryTitle.appendChild(reloadIcon); - } - - const createToolTipIcon = (type, content) => { - const tipIcon = document.createElement("i"); - tipIcon.classList.add( - "layui-icon", - "layui-icon-tips", - "aura-settings-entry-property-icon" - ); - if (type === "warning") { - tipIcon.classList.add("aura-settings-entry-warning-icon"); - } - tipIcon.setAttribute("data-bs-toggle", "tooltip"); - tipIcon.setAttribute("data-bs-placement", "top"); - tipIcon.setAttribute("data-bs-title", content); - entryTitle.appendChild(tipIcon); - }; - - if (entry.tip) { - createToolTipIcon("tip", entry.tipTitle); - } - - if (entry.warning) { - createToolTipIcon("warning", entry.warningContent); - } - - const entryDescription = document.createElement("p"); - entryDescription.classList.add("aura-settings-entry-desc"); - entryDescription.textContent = entry.description; - entryInfoContainerEl.appendChild(entryTitle); - entryInfoContainerEl.appendChild(entryDescription); - - entryContainerEl.appendChild(entryInfoContainerEl); - - const entryOperationArea = document.createElement("div"); - entryOperationArea.classList.add("aura-settings-entry-operation-area"); - switch (entry.type) { - case "switch": - { - const switchEl = document.createElement("input"); - switchEl.classList.add("form-check-input"); - switchEl.type = "checkbox"; - switchEl.role = "switch"; - switchEl.id = entry.id; - const elValue = entry.valueGetter(); - switchEl.value = elValue; - switchEl.checked = elValue; - switchEl.addEventListener("change", async (event) => { - showToast(entry); - await entry.callbackFn(event.target.checked); - }); - entryOperationArea.classList.add("form-check", "form-switch"); - entryOperationArea.appendChild(switchEl); - } - break; - case "radio": - { - const elValue = entry.valueGetter(); - for (const template of entry.templates) { - const inlineContainerEl = document.createElement("div"); - inlineContainerEl.classList.add( - "form-check", - "form-check-inline" - ); - const radioEl = document.createElement("input"); - radioEl.value = template; - radioEl.classList.add("form-check-input"); - radioEl.type = "radio"; - radioEl.name = `${entry.id}Radios`; - radioEl.id = `${entry.id}Radio${entry.templates.indexOf( - template - )}`; - radioEl.checked = template === elValue ? true : false; - radioEl.addEventListener("change", async (event) => { - if (event.target.checked) { - showToast(entry); - await entry.callbackFn(event.target.value); - } - }); - inlineContainerEl.appendChild(radioEl); - const labelEl = document.createElement("label"); - labelEl.classList.add("form-check-label"); - labelEl.setAttribute("for", radioEl.id); - labelEl.textContent = - entry.templateLabels[entry.templates.indexOf(template)]; - inlineContainerEl.appendChild(labelEl); - entryOperationArea.appendChild(inlineContainerEl); - } - } - break; - case "input": - { - const inputEl = document.createElement("input"); - inputEl.classList.add("form-control"); - inputEl.type = entry.subType; - inputEl.value = entry.valueGetter(); - inputEl.placeholder = entry.placeHolder; - inputEl.id = entry.id; - inputEl.addEventListener("change", async (event) => { - const result = await entry.callbackFn(event.target.value); - const success = result.valid; - if (success) { - showToast(entry); - - if (inputEl.className.includes("is-invalid")) { - inputEl.classList.remove("is-invalid"); - entryDescription.textContent = entry.description; - entryDescription.classList.remove("ase-desc-error-hint"); - } - } else { - inputEl.classList.add("is-invalid"); - entryDescription.textContent = result.hint; - entryDescription.classList.add("ase-desc-error-hint"); - } - }); - entryOperationArea.classList.add("ase-operation-area-expanded"); - entryOperationArea.appendChild(inputEl); - } - break; - default: - break; - } - - const setDisableStatus = (el, isDisable, hint = null) => { - if (isDisable) { - el.classList.add("ase-operation-area-disabled"); - if (hint) { - el.setAttribute("data-bs-toggle", "tooltip"); - el.setAttribute("data-bs-placement", "top"); - el.setAttribute("data-bs-title", hint); - } - } else { - el.setAttribute("data-bs-toggle", ""); - el.setAttribute("data-bs-placement", ""); - el.setAttribute("data-bs-title", ""); - el.classList.remove("ase-operation-area-disabled"); - } - }; - - if (entry.PLSRequired) { - if (!global.__HUGO_AURA__.plsStats.connected) { - setDisableStatus(entryOperationArea, true, "连接至 PLS 以继续"); - } - - document.addEventListener("onPLSStatsUpdate", (event) => { - if (event.detail.connected) { - setDisableStatus(entryOperationArea, false); - } else { - setDisableStatus(entryOperationArea, true, "连接至 PLS 以继续"); - } - }); - } - entryContainerEl.appendChild(entryOperationArea); - const isShow = entry.auraIf(); - if (!isShow) entryContainerEl.classList.add("aura-settings-entry-hidden"); - - if (entry.associateVal) { - document.addEventListener( - isPls ? "onPLSConfigUpdate" : "onHugoAuraConfigUpdate", - (event) => { - if (!entry.associateVal.includes(event.detail.path.join("."))) - return; - const cls = entryContainerEl.classList; - const isShow = entry.auraIf(); - isShow - ? cls.remove("aura-settings-entry-hidden") - : cls.add("aura-settings-entry-hidden"); - } - ); - } - - formEl.appendChild(entryContainerEl); + renderChild(entry, formEl); } const hrEl = document.createElement("hr"); diff --git a/src/aura/ui/css/form.css b/src/aura/ui/css/form.css index b692f1f..d5e234d 100755 --- a/src/aura/ui/css/form.css +++ b/src/aura/ui/css/form.css @@ -81,7 +81,7 @@ .aura-settings-entry-warning-icon { transform: rotate(180deg); display: inline-block; - color: rgb(241, 155, 0); + color: rgb(147, 0, 0); } /* Animations */ diff --git a/src/aura/ui/css/global.css b/src/aura/ui/css/global.css index ac9103d..d984d74 100755 --- a/src/aura/ui/css/global.css +++ b/src/aura/ui/css/global.css @@ -25,3 +25,7 @@ button.nav-link { .nav-underline { --bs-nav-underline-link-active-color: #0d6efd !important; } + +.tooltip { + --bs-tooltip-max-width: 400px !important; +} diff --git a/src/aura/ui/js/plsConnectionManager.js b/src/aura/ui/js/plsConnectionManager.js index 1a91b63..cadd536 100755 --- a/src/aura/ui/js/plsConnectionManager.js +++ b/src/aura/ui/js/plsConnectionManager.js @@ -15,19 +15,91 @@ const REQUIRE_BASE = "../../aura/ui"; const __SCOPE = "desktopAssistant"; + const PLS_REG_PATH = "ProxyLayerServices"; + const { pushMsgHandler } = require(`${REQUIRE_BASE}/pls/pushHandler`); + const RegistryManager = require(`${REQUIRE_BASE}/../init/shared/registryManager`); + + const registryManager = new RegistryManager(); /** @type {number} */ let failedCounter = 0; /** @type {boolean} */ let isErrorOccurred = false; + /** @type {number} */ + let plsPort = 22077; + /** @type {"wss" | "ws"} */ + let plsProtocol = "wss"; + + /** @type {boolean} */ + let isRetrying = false; + + /** @type {any} */ + let curSendListener = null; + const sendRetryStatusToMain = (/** @type {Boolean} */ status) => { global.ipcRenderer.invoke(`${IPC_METHOD_BASE}.post.updateRetryStatus`, { success: status, }); }; + const calcFullAuthToken = (/** @type {string} */ authToken) => { + const trustToken = window._ACCEPT_DATA.getData("deviceId"); + const conjToken = authToken + "AuraXAuth" + trustToken + "NeverEnds"; + const crypto = require("crypto"); + return crypto.createHash("sha512").update(conjToken).digest("hex"); + }; + + const clearIpcListener = () => { + if (curSendListener) { + global.ipcRenderer.off( + `${IPC_METHOD_BASE}.ws.post.onReqSendMsg`, + curSendListener + ); + curSendListener = null; + } + }; + + const startConnPlsProc = async (updatedPlsStats) => { + const authTokenRet = await registryManager.readRegKey( + PLS_REG_PATH, + "AuthToken", + true + ); + if (authTokenRet.success) { + updatedPlsStats.authToken = authTokenRet.data; + // @ts-expect-error + global.__HUGO_AURA__.plsStats.authToken = authTokenRet.data; + } else { + sendRetryStatusToMain(false); + return; + } + const portRet = await registryManager.readRegKey( + PLS_REG_PATH, + "WsPort", + true + ); + if (portRet.success) { + try { + plsPort = Number(portRet.data); + } catch { + console.warn( + `[HugoAura / UI / PLS Manager] Invalid PLS port: ${portRet.data}` + ); + } + } + const protoRet = await registryManager.readRegKey( + PLS_REG_PATH, + "Protocol", + true + ); + if (protoRet.success) { + plsProtocol = protoRet.data; + } + createPlsConnection(updatedPlsStats.authToken, connectionResultCallback); + }; + /** * * @param {string} authToken @@ -43,9 +115,11 @@ return; } + const fullAuthToken = calcFullAuthToken(authToken); + /** @type {WebSocket} */ const plsWs = new WebSocket( - `wss://pls.hugoaura.local:22077/?auth=${authToken}` + `${plsProtocol}://pls.hugoaura.local:${plsPort}/?auth=${fullAuthToken}` ); plsWs.onopen = () => { @@ -59,6 +133,26 @@ }; plsWs.onclose = () => { + clearIpcListener(); + if (global.__HUGO_AURA__.plsStats) { + if (global.__HUGO_AURA__.plsStats.status === "notReady") { + if (isRetrying) { + sendRetryStatusToMain(false); + return; + } + console.warn( + "[HugoAura / UI / PLS Manager / WARN] PLS not ready, try again after 10s..." + ); + isRetrying = true; + setTimeout(async () => { + isRetrying = false; + startConnPlsProc(global.__HUGO_AURA__.plsStats); + }, 10000); + sendRetryStatusToMain(false); + return; + } + } + console.error( "[HugoAura / UI / PLS Manager / ERROR] WebSocket connection closed." ); @@ -73,16 +167,18 @@ * @param {WebSocket} wsObj */ const registerSendReqListener = (wsObj) => { + clearIpcListener(); + /** + * + * @param {import("electron").IpcRendererEvent} _evt + * @param {any} arg + */ + curSendListener = (_evt, arg) => { + wsObj.send(JSON.stringify(arg)); + }; global.ipcRenderer.on( `${IPC_METHOD_BASE}.ws.post.onReqSendMsg`, - /** - * - * @param {import("electron").IpcRendererEvent} _evt - * @param {any} arg - */ - (_evt, arg) => { - wsObj.send(JSON.stringify(arg)); - } + curSendListener ); }; @@ -151,6 +247,11 @@ const initPlsConnection = async () => { if (!global.__HUGO_AURA__.plsStats) return; + if (isRetrying) { + sendRetryStatusToMain(false); + return; + } + failedCounter = 0; isErrorOccurred = false; @@ -173,7 +274,7 @@ } const isPlsFolderExists = ( - await global.ipcRenderer.invoke(`${IPC_METHOD_BASE}.getPlsFolderExists`) + await global.ipcRenderer.invoke(`${IPC_METHOD_BASE}.getPlsBinExists`) ).data.isExists; updatedPlsStats.installed = isPlsFolderExists; @@ -189,15 +290,11 @@ updatedPlsStats ); - const startConnPls = () => { - createPlsConnection(updatedPlsStats.authToken, connectionResultCallback); - }; - /* if (updatedPlsStats.detached && updatedPlsStats.installed) { */ if (updatedPlsStats.installed) { - startConnPls(); + await startConnPlsProc(updatedPlsStats); } else { sendRetryStatusToMain(false); } diff --git a/src/aura/ui/js/plsListener.js b/src/aura/ui/js/plsListener.js index 7fe5920..83cfb9e 100755 --- a/src/aura/ui/js/plsListener.js +++ b/src/aura/ui/js/plsListener.js @@ -14,6 +14,13 @@ `${IPC_METHOD_BASE}.post.onPlsStatsUpdate`, (_event, arg) => { global.__HUGO_AURA__.plsStats = arg; + document.dispatchEvent( + new CustomEvent("onPLSStatsUpdate", { + detail: { + connected: arg.connected, + }, + }) + ); } ); @@ -21,6 +28,14 @@ `${IPC_METHOD_BASE}.post.onPlsSettingsUpdate`, (_event, arg) => { global.__HUGO_AURA__.plsSettings = arg; + document.dispatchEvent( + new CustomEvent("onPLSConfigUpdate", { + detail: { + path: ["root", "settings"], + value: arg, + }, + }) + ); } ); @@ -28,6 +43,14 @@ `${IPC_METHOD_BASE}.post.onPlsRulesUpdate`, (_event, arg) => { global.__HUGO_AURA__.plsRules = arg; + document.dispatchEvent( + new CustomEvent("onPLSConfigUpdate", { + detail: { + path: ["root", "ruleSettings"], + value: arg, + }, + }) + ); } ); diff --git a/src/aura/ui/pages/config/config.html b/src/aura/ui/pages/config/config.html index 1d355c7..88cddad 100755 --- a/src/aura/ui/pages/config/config.html +++ b/src/aura/ui/pages/config/config.html @@ -189,6 +189,11 @@
重载页面以应用设置 +

请重载当前窗口以应用修改的设置

@@ -217,6 +222,11 @@
重启进程以应用设置 +

请重启 Electron 进程以应用修改的设置

@@ -245,6 +255,11 @@
重启 PLS 进程以应用设置 +

请重启 PLS 进程以应用修改的设置

diff --git a/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.css b/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.css index 8ec253f..bc47f57 100755 --- a/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.css +++ b/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.css @@ -137,3 +137,62 @@ .acs-bc-pls-status-page-status-area.failed p { color: rgb(175, 0, 0); } + +.acs-bc-pls-status-page-status-area.warning + .acs-bc-pls-status-page-status-area-circle { + background-color: rgb(212, 127, 0); +} + +.acs-bc-pls-status-page-status-area.warning p { + color: rgb(212, 127, 0); +} + +.acs-bc-pls-status-page-status-area.info + .acs-bc-pls-status-page-status-area-circle { + background-color: #3d78ff; +} + +.acs-bc-pls-status-page-status-area.info p { + color: #3d78ff; +} + +.acs-bc-psp-toast.body-display-none .toast-body { + display: none; +} + +.acs-bc-psp-toast.body-display-none .toast-header { + border-bottom: none; +} + +.acs-bc-psp-toast[closable="false"] .btn-close { + display: none; +} + +.acs-bc-psp-toast[variant="success"] .toast-header { + background-color: rgb(6, 196, 65); + color: rgb(255, 255, 255); +} + +.acs-bc-psp-toast[variant="warning"] .toast-header { + background-color: rgb(225, 178, 44); + color: rgb(255 255 255); +} + +.acs-bc-psp-toast[variant="error"] .toast-header { + background-color: rgb(217, 57, 4); + color: rgb(255 255 255); +} + +.acs-bc-psp-toast[variant="info"] .toast-header { + background-color: rgb(0, 149, 222); + color: rgb(255 255 255); +} + +.acs-bc-psp-toast[variant="success"] .btn-close, +.acs-bc-psp-toast[variant="warning"] .btn-close, +.acs-bc-psp-toast[variant="error"] .btn-close, +.acs-bc-psp-toast[variant="info"] .btn-close { + color: rgb(255, 255, 255); + filter: invert(1); + opacity: 0.95; +} diff --git a/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.html b/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.html index 9909eb6..04e88f1 100755 --- a/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.html +++ b/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.html @@ -92,6 +92,36 @@

卸载服务

+ +
+ + + +

启动服务

+
+ +
+ + + +

停止服务

+
@@ -133,4 +163,18 @@

不可用

+ +
+
+
+ + +
+
+
+
diff --git a/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.js b/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.js index 6083d3b..ff13849 100755 --- a/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.js +++ b/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.js @@ -1,32 +1,254 @@ if (!global.__HUGO_AURA_UI_FUNCTIONS__.subConfig) global.__HUGO_AURA_UI_FUNCTIONS__.subConfig = {}; +if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig) + global.__HUGO_AURA_UI_REACTIVES__.subConfig = {}; + (() => { const REQUIRE_BASE = "../../aura/ui/pages/configSubPages/behaviourCtrl"; const IPC_METHOD_BASE = "$aura.pls"; + const lifecycleStatus = { + installed: false, + detached: false, + svcInstalled: false, + svcRunning: false, + }; + + global.__HUGO_AURA_UI_REACTIVES__.subConfig.plsStatus = { + toastAutoHideTimeout: null, + }; + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus = { - updateOperationBtnStatus: async (btnName, side, btnContent = null) => { + updateToast: async ( + variant, + title, + body = null, + closable = true, + autoHide = true, + hideAfter = 3000 + ) => { + const toastRootEl = document.getElementById("plsStatusNotifyToast"); + const toastHeaderEl = document.getElementById( + "plsStatusNotifyToastTitle" + ); + const toastBodyEl = document.getElementById("plsStatusNotifyToastBody"); + + const bsToastIns = bootstrap.Toast.getOrCreateInstance(toastRootEl); + if (bsToastIns.isShown) { + bsToastIns.hide(); + const timeout = + global.__HUGO_AURA_UI_REACTIVES__.subConfig.plsStatus + .toastAutoHideTimeout; + if (timeout) { + clearTimeout(timeout); + } + await global.__HUGO_AURA_GLOBAL__.utils.sleep(160); + } + + toastRootEl.setAttribute("variant", variant); + toastHeaderEl.innerHTML = title; + if (body) { + toastBodyEl.innerHTML = body; + toastRootEl.classList.remove("body-display-none"); + } else { + toastRootEl.classList.add("body-display-none"); + } + + toastRootEl.setAttribute("closable", closable.toString()); + + bsToastIns.show(); + if (autoHide && hideAfter) { + global.__HUGO_AURA_UI_REACTIVES__.subConfig.plsStatus.toastAutoHideTimeout = + setTimeout(() => { + bsToastIns.hide(); + }, hideAfter); + } + }, + + updateOperationBtnStatus: async ( + btnName, + isDisabled, + btnContent = null + ) => { const btnEl = document.getElementById(`acsBcPsp-operBtn-${btnName}`); if (!btnEl) return false; - btnEl.setAttribute("aura-disabled", side ? "true" : "false"); + btnEl.setAttribute("aura-disabled", isDisabled ? "true" : "false"); if (btnContent) { const btnPEl = btnEl.getElementsByTagName("p")[0]; btnPEl.textContent = btnContent; } - if (side) { + if (isDisabled) { btnEl.onclick = () => {}; } else { switch (btnName) { case "Refresh": - btnEl.onclick = () => + btnEl.onclick = () => { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "warning", + "正在更新", + null, + false, + false, + null + ); global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.refreshPlsStatus(); + }; break; case "Download": break; + + // ↓ 这边的确可以把这些全都合并到一个可复用 fn 里去, 但没必要 case "Install": + btnEl.onclick = async () => { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "info", + "正在请求安装", + null, + false, + false, + null + ); + const ret = await ipcRenderer.invoke( + `${IPC_METHOD_BASE}.plsLifecycleControl`, + { target: "instSvc" } + ); + if (ret.success) { + lifecycleStatus.svcInstalled = true; + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "success", + "服务安装成功", + null, + true, + true, + 2000 + ); + } else { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "error", + "服务安装失败", + "

检查日志以获取详细信息

", + true, + false, + null + ); + } + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); + }; break; case "Uninstall": + btnEl.onclick = async () => { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "info", + "正在请求卸载", + null, + false, + false, + null + ); + const ret = await ipcRenderer.invoke( + `${IPC_METHOD_BASE}.plsLifecycleControl`, + { target: "rmSvc" } + ); + if (ret.success) { + lifecycleStatus.svcInstalled = false; + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "success", + "服务卸载成功", + null, + true, + true, + 2000 + ); + } else { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "error", + "服务卸载失败", + "

检查日志以获取详细信息

", + true, + false, + null + ); + } + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); + }; + break; + case "Start": + btnEl.onclick = async () => { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "info", + "正在请求启动", + null, + false, + false, + null + ); + const ret = await ipcRenderer.invoke( + `${IPC_METHOD_BASE}.plsLifecycleControl`, + { target: "startSvc" } + ); + if (ret.success) { + lifecycleStatus.svcRunning = true; + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "success", + "PLS 已启动", + null, + true, + true, + 2000 + ); + await global.__HUGO_AURA_GLOBAL__.utils.sleep(100); + await ipcRenderer.invoke(`${IPC_METHOD_BASE}.retryPlsConnect`); + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); + } else { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "error", + "PLS 启动失败", + "

检查 PLS 日志目录以获取详细信息

", + true, + false, + null + ); + } + }; + break; + case "Stop": + btnEl.onclick = async () => { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "info", + "正在请求停止", + null, + false, + false, + null + ); + const ret = await ipcRenderer.invoke( + `${IPC_METHOD_BASE}.plsLifecycleControl`, + { target: "stopSvc" } + ); + if (ret.success) { + lifecycleStatus.svcRunning = false; + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "success", + "PLS 已停止", + null, + true, + true, + 2000 + ); + } else { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "error", + "PLS 停止失败", + null, + true, + false, + null + ); + } + }; break; default: break; @@ -35,32 +257,59 @@ if (!global.__HUGO_AURA_UI_FUNCTIONS__.subConfig) return true; }, - updateStatus: async () => { + updateStatusContent: async () => { const curPlsStats = await updatePlsStatusFromLocal(); const acIdInst = "acs-bc-psp-installStatus-container"; const atIdInst = "acs-bc-psp-installStatus-text"; - switch (curPlsStats.installed) { + switch (lifecycleStatus.installed) { case true: - updateStatusEl(acIdInst, atIdInst, "SUCCESS", "已安装"); - GLOBAL_FUNCTIONS.updateOperationBtnStatus("Install", false); - GLOBAL_FUNCTIONS.updateOperationBtnStatus("Uninstall", false); + if (!lifecycleStatus.svcInstalled) { + updateStatusEl(acIdInst, atIdInst, "WARNING", "已下载, 服务未安装"); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Install", false); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Uninstall", true); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Start", true); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Stop", true); + } else { + updateStatusEl(acIdInst, atIdInst, "SUCCESS", "已安装"); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Install", true); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Uninstall", false); + } break; case false: - updateStatusEl(acIdInst, atIdInst, "PENDING", "未安装"); + updateStatusEl(acIdInst, atIdInst, "PENDING", "未下载"); GLOBAL_FUNCTIONS.updateOperationBtnStatus("Install", true); GLOBAL_FUNCTIONS.updateOperationBtnStatus("Uninstall", true); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Start", true); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Stop", true); } const acIdLaunch = "acs-bc-psp-launchStatus-container"; const atIdLaunch = "acs-bc-psp-launchStatus-text"; - switch (curPlsStats.launched) { - case true: - updateStatusEl(acIdLaunch, atIdLaunch, "SUCCESS", "已启动"); - break; - case false: - updateStatusEl(acIdLaunch, atIdLaunch, "PENDING", "未启动"); - break; + + if (lifecycleStatus.detached) { + updateStatusEl(acIdLaunch, atIdLaunch, "INFO", "已分离"); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Start", true); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Stop", true); + } else if (lifecycleStatus.svcInstalled && lifecycleStatus.installed) { + switch (lifecycleStatus.svcRunning || curPlsStats.launched) { + case true: + if (curPlsStats.status !== "notReady") { + updateStatusEl(acIdLaunch, atIdLaunch, "SUCCESS", "已启动"); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Start", true); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Stop", false); + } else { + updateStatusEl(acIdLaunch, atIdLaunch, "WARNING", "启动中"); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Start", true); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Stop", false); + } + break; + case false: + updateStatusEl(acIdLaunch, atIdLaunch, "PENDING", "未启动"); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Start", false); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Stop", true); + break; + } } const acIdConn = "acs-bc-psp-connStatus-container"; @@ -70,7 +319,11 @@ if (!global.__HUGO_AURA_UI_FUNCTIONS__.subConfig) updateStatusEl(acIdConn, atIdConn, "SUCCESS", "已连接"); break; case false: - updateStatusEl(acIdConn, atIdConn, "FAILED", "连接失败"); + if (curPlsStats.status !== "notReady") { + updateStatusEl(acIdConn, atIdConn, "FAILED", "连接失败"); + } else { + updateStatusEl(acIdConn, atIdConn, "PENDING", "等待启动"); + } break; } @@ -82,7 +335,53 @@ if (!global.__HUGO_AURA_UI_FUNCTIONS__.subConfig) } }, - refreshPlsStatus: async () => { + refreshPlsStatus: async (init = false) => { + const binExistsRet = await ipcRenderer.invoke( + `${IPC_METHOD_BASE}.getPlsBinExists` + ); + if (binExistsRet.success && binExistsRet.data.isExists) { + lifecycleStatus.installed = true; + } else { + lifecycleStatus.installed = false; + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); + return; + } + + const isDetachedRet = await ipcRenderer.invoke( + `${IPC_METHOD_BASE}.plsLifecycleQuery`, + { target: "isDetached" } + ); + if (isDetachedRet.success && isDetachedRet.result) { + lifecycleStatus.detached = true; + } else { + lifecycleStatus.detached = false; + } + + const isSvcInstalledRet = await ipcRenderer.invoke( + `${IPC_METHOD_BASE}.plsLifecycleQuery`, + { target: "isSvcInstalled" } + ); + if (isSvcInstalledRet.success && isSvcInstalledRet.result) { + lifecycleStatus.svcInstalled = true; + } else { + lifecycleStatus.svcInstalled = false; + } + + const isSvcRunningRet = await ipcRenderer.invoke( + `${IPC_METHOD_BASE}.plsLifecycleQuery`, + { target: "isSvcStart" } + ); + if (isSvcRunningRet.success && isSvcRunningRet.result) { + lifecycleStatus.svcRunning = true; + } else { + lifecycleStatus.svcRunning = false; + } + + if (init) { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); + return; + } + const updateOperationBtnStatus = global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus .updateOperationBtnStatus; @@ -95,10 +394,18 @@ if (!global.__HUGO_AURA_UI_FUNCTIONS__.subConfig) ipcRenderer.once( `${IPC_METHOD_BASE}.post.updateRetryStatus`, - async (_evt, _arg) => { + async (_evt, arg) => { await global.__HUGO_AURA_GLOBAL__.utils.sleep(50); updateOperationBtnStatus("Refresh", false, "刷新状态"); - global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatus(); + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + arg.success ? "success" : "error", + arg.success ? "更新成功" : "连接失败", + null, + true, + true, + 3000 + ); + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); } ); } else if (result.success && result.status === "Already") { @@ -147,6 +454,9 @@ if (!global.__HUGO_AURA_UI_FUNCTIONS__.subConfig) areaContainerEl.className = "acs-bc-pls-status-page-status-area warning"; break; + case "INFO": + areaContainerEl.className = "acs-bc-pls-status-page-status-area info"; + break; default: return false; } @@ -155,11 +465,11 @@ if (!global.__HUGO_AURA_UI_FUNCTIONS__.subConfig) }; const onMounted = () => { - GLOBAL_FUNCTIONS.updateOperationBtnStatus("Refresh", false); initBsTooltip(); - GLOBAL_FUNCTIONS.updateStatus(); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Refresh", false); + GLOBAL_FUNCTIONS.refreshPlsStatus(true); document.addEventListener("onPLSStatsUpdate", () => { - GLOBAL_FUNCTIONS.updateStatus(); + GLOBAL_FUNCTIONS.updateStatusContent(); }); }; diff --git a/src/aura/ui/pages/configSubPages/behaviourCtrl/settings/basic.js b/src/aura/ui/pages/configSubPages/behaviourCtrl/settings/basic.js index 812e887..257ae6f 100755 --- a/src/aura/ui/pages/configSubPages/behaviourCtrl/settings/basic.js +++ b/src/aura/ui/pages/configSubPages/behaviourCtrl/settings/basic.js @@ -4,53 +4,48 @@ const { updatePlsConfigToRemote, } = require(`${REQUIRE_BASE}/../../../../composables/plsConfigManager`); +const reusableChkFn = { + checkRelativePath: () => { + if (newVal === "" || !newVal) + return { valid: false, hint: "请输入证书路径" }; + + if (newVal.includes(":/") || newVal.includes(":\\")) { + return { valid: false, hint: "请输入相对路径, 而非绝对路径" }; + } + + if (newVal.includes("\\")) { + return { + valid: false, + hint: '请输入正确的路径, 使用 "/" 作为路径符', + }; + } + + return { + valid: true, + }; + }, +}; + const basicSettings = [ { id: 0, categoryName: "可访问性", child: [ - /* - { - index: 0, - id: "authToken", - type: "input", - subType: "text", - name: "WebSocket 认证密钥", - description: "选择一个安全的密钥, 用于 PLS 侧验证 Aura 前端身份", - restart: true, - reload: false, - restartPLS: true, - associateVal: null, - auraIf: () => true, - defaultValue: "", - placeHolder: "输入一个密钥", - valueGetter: () => { - return global.__HUGO_AURA_CONFIG__.plsToken; - }, - callbackFn: (newVal) => { - if (newVal === "" || !newVal) - return { valid: false, hint: "请输入认证密钥" }; - - if (newVal.length < 8) { - return { valid: false, hint: "至少输入 8 位字符" }; - } - - global.__HUGO_AURA_CONFIG__.plsToken = newVal; - return { valid: true }; - }, - }, - */ { index: 0, id: "plsListenPort", type: "input", - subType: "text", - name: "PLS WS 监听端口", - description: "PLS 的 WebSocket 服务器将监听指定的端口", + subType: "number", + name: "PLS WS 默认监听端口", + description: "PLS 的 WebSocket 服务器将默认监听指定的端口", + reactive: true, + reactiveVal: ["root.settings"], restart: false, reload: false, PLSRequired: true, - restartPLS: true, + restartPLS: false, + warning: true, + warningContent: "PLS 仍会在默认端口被占用时, 自动随机端口重试", associateVal: null, auraIf: () => true, defaultValue: "", @@ -64,14 +59,114 @@ const basicSettings = [ return { valid: false, hint: "请输入端口号" }; const numberNewVal = Number(newVal); - if (numberNewVal === NaN || !(10000 <= numberNewVal <= 65535)) { + if (numberNewVal === NaN || !(10000 <= numberNewVal) || !(newVal <= 65535)) { return { valid: false, hint: "请输入合法的端口号 (10000 ~ 65535)" }; } global.__HUGO_AURA__.plsSettings.wsPort = numberNewVal; + updatePlsConfigToRemote("wsPort", numberNewVal); return { valid: true }; }, }, + { + index: 1, + id: "plsCertPath", + type: "input", + subType: "text", + name: "WSS TLS 证书相对路径", + description: "PLS 将使用指定路径下的证书启动 WSS 服务器", + reactive: true, + reactiveVal: ["root.settings"], + restart: false, + reload: false, + PLSRequired: true, + restartPLS: true, + tip: true, + tipTitle: + '路径相对于 "%USERPROFILE%\\Documents\\HugoAura\\Aura-PLS\\", 使用 "/" 作为路径符', + associateVal: null, + auraIf: () => true, + defaultValue: "", + placeHolder: "输入相对路径, 例如: config/vme50/cert.crt", + valueGetter: () => { + if (!global.__HUGO_AURA__.plsSettings) return ""; + return global.__HUGO_AURA__.plsSettings.certPath; + }, + callbackFn: (newVal) => { + const validate = reusableChkFn.checkRelativePath(); + if (!validate.valid) { + return validate; + } + + global.__HUGO_AURA__.plsSettings.certPath = newVal; + updatePlsConfigToRemote("certPath", newVal); + return { valid: true }; + }, + }, + { + index: 2, + id: "plsCertPath", + type: "input", + subType: "text", + name: "WSS TLS 证书私钥相对路径", + description: "PLS 将使用指定路径下的私钥启动 WSS 服务器", + reactive: true, + reactiveVal: ["root.settings"], + restart: false, + reload: false, + PLSRequired: true, + restartPLS: true, + tip: true, + tipTitle: + '路径相对于 "%USERPROFILE%\\Documents\\HugoAura\\Aura-PLS\\", 使用 "/" 作为路径符', + warning: true, + warningContent: "请使用 PEM 格式的密钥", + associateVal: null, + auraIf: () => true, + defaultValue: "", + placeHolder: "输入相对路径, 例如: config/vme50/cert.key", + valueGetter: () => { + if (!global.__HUGO_AURA__.plsSettings) return ""; + return global.__HUGO_AURA__.plsSettings.keyPath; + }, + callbackFn: (newVal) => { + const validate = reusableChkFn.checkRelativePath(); + if (!validate.valid) { + return validate; + } + + global.__HUGO_AURA__.plsSettings.keyPath = newVal; + updatePlsConfigToRemote("keyPath", newVal); + return { valid: true }; + }, + }, + { + index: 3, + id: "plsRegenCertAftRelaunch", + type: "switch", + name: "重新生成 TLS 证书", + description: "PLS 将在下次启动时重新生成 TLS 证书", + reactive: true, + reactiveVal: ["root.settings"], + restart: false, + reload: false, + PLSRequired: true, + restartPLS: true, + associateVal: null, + auraIf: () => true, + defaultValue: false, + valueGetter: () => { + if (!global.__HUGO_AURA__.plsSettings) return ""; + return global.__HUGO_AURA__.plsSettings.regenCert; + }, + callbackFn: (newVal) => { + if (typeof newVal !== "boolean") return false; + + global.__HUGO_AURA__.plsSettings.regenCert = newVal; + updatePlsConfigToRemote("regenCert", newVal); + return true; + }, + }, ], }, ]; diff --git a/src/aura/ui/pages/configSubPages/preferences/settings/aura.js b/src/aura/ui/pages/configSubPages/preferences/settings/aura.js index 3ec9fc0..36a703b 100755 --- a/src/aura/ui/pages/configSubPages/preferences/settings/aura.js +++ b/src/aura/ui/pages/configSubPages/preferences/settings/aura.js @@ -30,7 +30,7 @@ const functions = { ); break; case "update": - const result = global.__HUGO_AURA_CONFIG_MGR__.switchToDecConfig( + const result = await global.__HUGO_AURA_CONFIG_MGR__.switchToDecConfig( global.__HUGO_AURA_CONFIG__, null ); @@ -179,7 +179,7 @@ const auraSettings = [ return global.__HUGO_AURA_CONFIG__.auraSettings .settingsPasswordEnabled; }, - callbackFn: (newVal) => { + callbackFn: async (newVal) => { if (typeof newVal !== "boolean") return; global.__HUGO_AURA_CONFIG__.auraSettings.settingsPasswordEnabled = newVal; @@ -192,7 +192,7 @@ const auraSettings = [ !newVal && global.__HUGO_AURA_CONFIG__.auraSettings.encryptConfig ) { - global.__HUGO_AURA_CONFIG_MGR__.switchToDecConfig( + await global.__HUGO_AURA_CONFIG_MGR__.switchToDecConfig( global.__HUGO_AURA_CONFIG__, null ); @@ -220,13 +220,13 @@ const auraSettings = [ valueGetter: () => { return global.__HUGO_AURA_CONFIG__.auraSettings.encryptConfig; }, - callbackFn: (newVal) => { + callbackFn: async (newVal) => { if (typeof newVal !== "boolean") return; global.__HUGO_AURA_CONFIG__.auraSettings.encryptConfig = newVal; if (newVal) { functions.handleEnableConfigEncryption("enc", null); } else { - global.__HUGO_AURA_CONFIG_MGR__.switchToDecConfig( + await global.__HUGO_AURA_CONFIG_MGR__.switchToDecConfig( global.__HUGO_AURA_CONFIG__, null ); diff --git a/src/aura/ui/pls/routes/basic.js b/src/aura/ui/pls/routes/basic.js index acd70e8..610efb0 100755 --- a/src/aura/ui/pls/routes/basic.js +++ b/src/aura/ui/pls/routes/basic.js @@ -11,8 +11,10 @@ const basicRouteHandler = (parsedWsMsg) => { const target = parsedWsMsg.type.split(".").slice(-1)[0]; switch (target) { case "pushPlsInfo": - global.__HUGO_AURA__.plsStats.status = parsedWsMsg.data.status; - global.__HUGO_AURA__.plsStats.version = parsedWsMsg.data.version; + if (global.__HUGO_AURA__.plsStats) { + global.__HUGO_AURA__.plsStats.status = parsedWsMsg.data.status; + global.__HUGO_AURA__.plsStats.version = parsedWsMsg.data.version; + } global.ipcRenderer.invoke( `${IPC_METHOD_BASE}.updatePlsStats`, @@ -24,6 +26,19 @@ const basicRouteHandler = (parsedWsMsg) => { global.__HUGO_AURA__.plsStats ); break; + + case "plsNotReadyError": + if (global.__HUGO_AURA__.plsStats) { + global.__HUGO_AURA__.plsStats.launched = true; + global.__HUGO_AURA__.plsStats.connected = false; + global.__HUGO_AURA__.plsStats.status = "notReady"; + } + + global.ipcRenderer.invoke( + `${IPC_METHOD_BASE}.updatePlsStats`, + global.__HUGO_AURA__.plsStats + ); + break; default: return false; }