|
@@ -1,9 +1,10 @@
|
|
|
-import { app, BrowserWindow, ipcMain, Menu, Tray, nativeImage, shell, utilityProcess, powerMonitor, systemPreferences } from 'electron'
|
|
|
|
|
|
|
+import { app, BrowserWindow, ipcMain, Menu, Tray, nativeImage, shell, utilityProcess, powerMonitor, systemPreferences, dialog, safeStorage } from 'electron'
|
|
|
import { appendFileSync, mkdirSync, readFileSync } from 'node:fs'
|
|
import { appendFileSync, mkdirSync, readFileSync } from 'node:fs'
|
|
|
import { join, dirname } from 'node:path'
|
|
import { join, dirname } from 'node:path'
|
|
|
import { homedir } from 'node:os'
|
|
import { homedir } from 'node:os'
|
|
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
|
import { DesktopServiceManager } from './service-manager.mjs'
|
|
import { DesktopServiceManager } from './service-manager.mjs'
|
|
|
|
|
+import { DesktopRunnerManager } from './runner-manager.mjs'
|
|
|
|
|
|
|
|
const desktopRoot = dirname(fileURLToPath(import.meta.url))
|
|
const desktopRoot = dirname(fileURLToPath(import.meta.url))
|
|
|
const fixtureHome = process.env.HERMES_YAOYAO_DESKTOP_TEST_HOME
|
|
const fixtureHome = process.env.HERMES_YAOYAO_DESKTOP_TEST_HOME
|
|
@@ -12,7 +13,7 @@ if (fixtureHome) app.setPath('userData', join(fixtureHome, 'desktop'))
|
|
|
app.setName('夭夭')
|
|
app.setName('夭夭')
|
|
|
if (!app.requestSingleInstanceLock()) { app.quit() }
|
|
if (!app.requestSingleInstanceLock()) { app.quit() }
|
|
|
else {
|
|
else {
|
|
|
- let window, tray, manager, quitting = false, closing = false, timer
|
|
|
|
|
|
|
+ let window, tray, manager, runnerManager, quitting = false, closing = false, timer
|
|
|
const bootURL = pathToFileURL(join(desktopRoot, 'boot.html')).href
|
|
const bootURL = pathToFileURL(join(desktopRoot, 'boot.html')).href
|
|
|
const root = app.isPackaged ? join(process.resourcesPath, 'runtime') : join(app.getAppPath(), '.desktop-build')
|
|
const root = app.isPackaged ? join(process.resourcesPath, 'runtime') : join(app.getAppPath(), '.desktop-build')
|
|
|
const logRoot = join(app.getPath('logs'), fixtureHome ? 'verification' : 'service')
|
|
const logRoot = join(app.getPath('logs'), fixtureHome ? 'verification' : 'service')
|
|
@@ -99,7 +100,7 @@ else {
|
|
|
event.preventDefault()
|
|
event.preventDefault()
|
|
|
if (closing) return
|
|
if (closing) return
|
|
|
closing = true; clearInterval(timer)
|
|
closing = true; clearInterval(timer)
|
|
|
- void manager?.stop().catch(error => log(error.message)).finally(() => { quitting = true; app.quit() })
|
|
|
|
|
|
|
+ void (async()=>{await runnerManager?.stop().catch(error=>log(error.message));await manager?.stop().catch(error=>log(error.message))})().finally(() => { quitting = true; app.quit() })
|
|
|
})
|
|
})
|
|
|
app.whenReady().then(async () => {
|
|
app.whenReady().then(async () => {
|
|
|
const packageVersion = JSON.parse(readFileSync(join(root, 'release.json'), 'utf8')).webVersion
|
|
const packageVersion = JSON.parse(readFileSync(join(root, 'release.json'), 'utf8')).webVersion
|
|
@@ -116,12 +117,28 @@ else {
|
|
|
return utilityProcess.fork(join(root, 'server.mjs'), [], { cwd: root, env, stdio: 'pipe' })
|
|
return utilityProcess.fork(join(root, 'server.mjs'), [], { cwd: root, env, stdio: 'pipe' })
|
|
|
},
|
|
},
|
|
|
})
|
|
})
|
|
|
|
|
+ const { parseRunnerConfiguration } = await import(pathToFileURL(join(root,'runner-config.mjs')).href)
|
|
|
|
|
+ runnerManager = new DesktopRunnerManager({home,validate:parseRunnerConfiguration,
|
|
|
|
|
+ encrypt:value=>{if(!safeStorage.isEncryptionAvailable())throw new Error('系统钥匙串不可用,无法保存执行节点凭据');return safeStorage.encryptString(value)},
|
|
|
|
|
+ decrypt:bytes=>safeStorage.decryptString(bytes),
|
|
|
|
|
+ onState:message=>{log(`Runner: ${message}`);const item=Menu.getApplicationMenu()?.getMenuItemById('runner-status');if(item)item.label=message},
|
|
|
|
|
+ fork:()=>utilityProcess.fork(join(root,'runner.mjs'),['--desktop-ipc'],{cwd:root,stdio:'pipe',env:{PATH:process.env.PATH||'/usr/bin:/bin',NODE_ENV:'production',NODE_USE_ENV_PROXY:'0'}}),
|
|
|
|
|
+ })
|
|
|
|
|
+ async function runnerAction(action){try{await action()}catch(error){await dialog.showMessageBox(window,{type:'error',message:'执行节点操作未完成',detail:error.message})}}
|
|
|
|
|
+ async function importRunner(){
|
|
|
|
|
+ const selection=await dialog.showOpenDialog(window,{title:'导入执行节点配置',properties:['openFile'],filters:[{name:'Runner 配置',extensions:['json']}]})
|
|
|
|
|
+ if(!selection.canceled&&selection.filePaths[0]&&!closing)await runnerAction(()=>runnerManager.importFile(selection.filePaths[0]))
|
|
|
|
|
+ }
|
|
|
Menu.setApplicationMenu(Menu.buildFromTemplate([
|
|
Menu.setApplicationMenu(Menu.buildFromTemplate([
|
|
|
{ label: '夭夭', submenu: [{ role: 'about', label: '关于夭夭' }, { type: 'separator' },
|
|
{ label: '夭夭', submenu: [{ role: 'about', label: '关于夭夭' }, { type: 'separator' },
|
|
|
{ label: '显示窗口', click: show }, { label: '在浏览器中打开', click: () => { if (manager.state.url) safeExternal(manager.state.url) } },
|
|
{ label: '显示窗口', click: show }, { label: '在浏览器中打开', click: () => { if (manager.state.url) safeExternal(manager.state.url) } },
|
|
|
{ label: '查看服务日志', click: () => shell.showItemInFolder(logFile) }, { type: 'separator' },
|
|
{ label: '查看服务日志', click: () => shell.showItemInFolder(logFile) }, { type: 'separator' },
|
|
|
{ role: 'hide', label: '隐藏夭夭' }, { role: 'hideOthers', label: '隐藏其他' }, { role: 'unhide', label: '显示全部' },
|
|
{ role: 'hide', label: '隐藏夭夭' }, { role: 'hideOthers', label: '隐藏其他' }, { role: 'unhide', label: '显示全部' },
|
|
|
{ type: 'separator' }, { role: 'quit', label: '退出夭夭' }] },
|
|
{ type: 'separator' }, { role: 'quit', label: '退出夭夭' }] },
|
|
|
|
|
+ {label:'执行节点',submenu:[{id:'runner-status',label:'未配置执行节点',enabled:false},{type:'separator'},
|
|
|
|
|
+ {id:'runner-import',label:'导入节点配置…',click:importRunner},
|
|
|
|
|
+ {id:'runner-reconnect',label:'重新连接',click:()=>runnerAction(()=>runnerManager.start())},
|
|
|
|
|
+ {id:'runner-forget',label:'断开并忘记配置',click:()=>runnerAction(()=>runnerManager.forget())}]},
|
|
|
{ role: 'editMenu', label: '编辑' }, { role: 'viewMenu', label: '显示' }, { role: 'windowMenu', label: '窗口' },
|
|
{ role: 'editMenu', label: '编辑' }, { role: 'viewMenu', label: '显示' }, { role: 'windowMenu', label: '窗口' },
|
|
|
]))
|
|
]))
|
|
|
const icon = nativeImage.createFromPath(join(desktopRoot, 'icon.png')).resize({ width: 20, height: 20 })
|
|
const icon = nativeImage.createFromPath(join(desktopRoot, 'icon.png')).resize({ width: 20, height: 20 })
|
|
@@ -134,5 +151,6 @@ else {
|
|
|
powerMonitor.on('resume', () => { void manager.check() })
|
|
powerMonitor.on('resume', () => { void manager.check() })
|
|
|
timer = setInterval(() => { void manager.check() }, 10_000); timer.unref()
|
|
timer = setInterval(() => { void manager.check() }, 10_000); timer.unref()
|
|
|
await manager.start().catch(error => log(error.message))
|
|
await manager.start().catch(error => log(error.message))
|
|
|
|
|
+ if(!closing)await runnerManager.start().catch(error=>{runnerManager.publish(error.message)})
|
|
|
}).catch(error => { log(error.stack || error.message); quitting = true; app.quit() })
|
|
}).catch(error => { log(error.stack || error.message); quitting = true; app.quit() })
|
|
|
}
|
|
}
|