mirror of
https://github.com/ragestudio/relic.git
synced 2025-06-09 02:24:18 +00:00
merge from local
This commit is contained in:
parent
86a6effeb1
commit
e187a49947
@ -33,6 +33,7 @@ export default async (pkg, step) => {
|
||||
//`--depth ${step.depth ?? 1}`,
|
||||
//"--filter=blob:none",
|
||||
//"--filter=tree:0",
|
||||
"--progress",
|
||||
"--recurse-submodules",
|
||||
"--remote-submodules",
|
||||
step.url,
|
||||
|
@ -83,6 +83,7 @@ export default async function apply(pkg_id, changes = {}) {
|
||||
return pkg
|
||||
} catch (error) {
|
||||
global._relic_eventBus.emit(`pkg:error`, {
|
||||
event: "apply",
|
||||
id: pkg_id,
|
||||
error
|
||||
})
|
||||
|
@ -19,6 +19,20 @@ export default async function execute(pkg_id, { useRemote = false, force = false
|
||||
return false
|
||||
}
|
||||
|
||||
if (pkg.last_status !== "installed") {
|
||||
if (!force) {
|
||||
BaseLog.info(`Package not installed [${pkg_id}], aborting execution`)
|
||||
|
||||
global._relic_eventBus.emit(`pkg:error`, {
|
||||
id: pkg_id,
|
||||
event: "execute",
|
||||
error: new Error("Package not valid or not installed"),
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const manifestPath = useRemote ? pkg.remote_manifest : pkg.local_manifest
|
||||
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
|
@ -164,12 +164,13 @@ export default async function install(manifest) {
|
||||
return pkg
|
||||
} catch (error) {
|
||||
global._relic_eventBus.emit(`pkg:error`, {
|
||||
id: id,
|
||||
error
|
||||
id: id ?? manifest.constructor.id,
|
||||
event: "install",
|
||||
error,
|
||||
})
|
||||
|
||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
||||
id: id,
|
||||
id: id ?? manifest.constructor.id,
|
||||
last_status: "failed",
|
||||
status_text: `Installation failed`,
|
||||
})
|
||||
|
76
packages/core/src/handlers/lastOperationRetry.js
Normal file
76
packages/core/src/handlers/lastOperationRetry.js
Normal file
@ -0,0 +1,76 @@
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
|
||||
import Logger from "../logger"
|
||||
import DB from "../db"
|
||||
|
||||
import PackageInstall from "./install"
|
||||
import PackageUpdate from "./update"
|
||||
import PackageUninstall from "./uninstall"
|
||||
|
||||
import Vars from "../vars"
|
||||
|
||||
export default async function lastOperationRetry(pkg_id) {
|
||||
try {
|
||||
const Log = Logger.child({ service: `OPERATION_RETRY|${pkg_id}` })
|
||||
const pkg = await DB.getPackages(pkg_id)
|
||||
|
||||
if (!pkg) {
|
||||
Log.error(`This package doesn't exist`)
|
||||
return null
|
||||
}
|
||||
|
||||
Log.info(`Try performing last operation retry...`)
|
||||
|
||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
||||
id: pkg.id,
|
||||
status_text: `Performing last operation retry...`,
|
||||
})
|
||||
|
||||
switch (pkg.last_status) {
|
||||
case "installing":
|
||||
await PackageInstall(pkg.local_manifest)
|
||||
break
|
||||
case "updating":
|
||||
await PackageUpdate(pkg_id)
|
||||
break
|
||||
case "uninstalling":
|
||||
await PackageUninstall(pkg_id)
|
||||
break
|
||||
case "failed": {
|
||||
// copy pkg.local_manifest to cache after uninstall
|
||||
const cachedManifest = path.join(Vars.cache_path, path.basename(pkg.local_manifest))
|
||||
|
||||
await fs.promises.copyFile(pkg.local_manifest, cachedManifest)
|
||||
|
||||
await PackageUninstall(pkg_id)
|
||||
await PackageInstall(cachedManifest)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
Log.error(`Invalid last status: ${pkg.last_status}`)
|
||||
|
||||
global._relic_eventBus.emit(`pkg:error`, {
|
||||
id: pkg.id,
|
||||
event: "retrying last operation",
|
||||
status_text: `Performing last operation retry...`,
|
||||
})
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return pkg
|
||||
} catch (error) {
|
||||
Logger.error(`Failed to perform last operation retry of [${pkg_id}]`)
|
||||
Logger.error(error)
|
||||
|
||||
global._relic_eventBus.emit(`pkg:error`, {
|
||||
event: "retrying last operation",
|
||||
id: pkg_id,
|
||||
error: error,
|
||||
})
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
@ -20,21 +20,33 @@ export default async function uninstall(pkg_id) {
|
||||
const Log = Logger.child({ service: `UNINSTALLER|${pkg.id}` })
|
||||
|
||||
Log.info(`Uninstalling package...`)
|
||||
|
||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
||||
id: pkg.id,
|
||||
status_text: `Uninstalling package...`,
|
||||
})
|
||||
|
||||
const ManifestRead = await ManifestReader(pkg.local_manifest)
|
||||
const manifest = await ManifestVM(ManifestRead.code)
|
||||
try {
|
||||
const ManifestRead = await ManifestReader(pkg.local_manifest)
|
||||
const manifest = await ManifestVM(ManifestRead.code)
|
||||
|
||||
if (typeof manifest.uninstall === "function") {
|
||||
Log.info(`Performing uninstall hook...`)
|
||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
||||
if (typeof manifest.uninstall === "function") {
|
||||
Log.info(`Performing uninstall hook...`)
|
||||
|
||||
global._relic_eventBus.emit(`pkg:update:state`, {
|
||||
id: pkg.id,
|
||||
status_text: `Performing uninstall hook...`,
|
||||
})
|
||||
|
||||
await manifest.uninstall(pkg)
|
||||
}
|
||||
} catch (error) {
|
||||
Log.error(`Failed to perform uninstall hook`, error)
|
||||
global._relic_eventBus.emit(`pkg:error`, {
|
||||
event: "uninstall",
|
||||
id: pkg.id,
|
||||
status_text: `Performing uninstall hook...`,
|
||||
error
|
||||
})
|
||||
await manifest.uninstall(pkg)
|
||||
}
|
||||
|
||||
Log.info(`Deleting package directory...`)
|
||||
@ -62,6 +74,7 @@ export default async function uninstall(pkg_id) {
|
||||
return pkg
|
||||
} catch (error) {
|
||||
global._relic_eventBus.emit(`pkg:error`, {
|
||||
event: "uninstall",
|
||||
id: pkg_id,
|
||||
error
|
||||
})
|
||||
|
@ -116,10 +116,21 @@ export default async function update(pkg_id) {
|
||||
return pkg
|
||||
} catch (error) {
|
||||
global._relic_eventBus.emit(`pkg:error`, {
|
||||
event: "update",
|
||||
id: pkg_id,
|
||||
error
|
||||
error,
|
||||
last_status: "failed"
|
||||
})
|
||||
|
||||
try {
|
||||
await DB.updatePackageById(pkg_id, {
|
||||
last_status: "failed",
|
||||
})
|
||||
} catch (error) {
|
||||
BaseLog.error(`Failed to update status of pkg [${pkg_id}]`)
|
||||
BaseLog.error(error.stack)
|
||||
}
|
||||
|
||||
BaseLog.error(`Failed to update package [${pkg_id}]`, error)
|
||||
BaseLog.error(error.stack)
|
||||
|
||||
|
@ -18,6 +18,7 @@ import PackageList from "./handlers/list"
|
||||
import PackageRead from "./handlers/read"
|
||||
import PackageAuthorize from "./handlers/authorize"
|
||||
import PackageCheckUpdate from "./handlers/checkUpdate"
|
||||
import PackageLastOperationRetry from "./handlers/lastOperationRetry"
|
||||
|
||||
export default class RelicCore {
|
||||
constructor(params) {
|
||||
@ -55,7 +56,8 @@ export default class RelicCore {
|
||||
list: PackageList,
|
||||
read: PackageRead,
|
||||
authorize: PackageAuthorize,
|
||||
checkUpdate: PackageCheckUpdate
|
||||
checkUpdate: PackageCheckUpdate,
|
||||
lastOperationRetry: PackageLastOperationRetry,
|
||||
}
|
||||
|
||||
openPath(pkg_id) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import winston from "winston"
|
||||
import WinstonTransport from "winston-transport"
|
||||
import colors from "cli-color"
|
||||
|
||||
const servicesToColor = {
|
||||
@ -6,10 +7,6 @@ const servicesToColor = {
|
||||
color: "whiteBright",
|
||||
background: "bgBlackBright",
|
||||
},
|
||||
"INSTALL": {
|
||||
color: "whiteBright",
|
||||
background: "bgBlueBright",
|
||||
},
|
||||
}
|
||||
|
||||
const paintText = (level, service, ...args) => {
|
||||
@ -27,6 +24,13 @@ const format = winston.format.printf(({ timestamp, service = "CORE", level, mess
|
||||
return `${paintText(level, service, `(${level}) [${service}]`)} > ${message}`
|
||||
})
|
||||
|
||||
class EventBusTransport extends WinstonTransport {
|
||||
log(info, next) {
|
||||
global._relic_eventBus.emit(`logger:new`, info)
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
export default winston.createLogger({
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
@ -34,6 +38,7 @@ export default winston.createLogger({
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console(),
|
||||
new EventBusTransport(),
|
||||
//new winston.transports.File({ filename: "error.log", level: "error" }),
|
||||
//new winston.transports.File({ filename: "combined.log" }),
|
||||
],
|
||||
|
@ -39,6 +39,15 @@ export async function readManifest(manifest) {
|
||||
throw new Error(`Manifest is not a file: ${target}`)
|
||||
}
|
||||
|
||||
// copy to cache
|
||||
const cachedManifest = path.join(Vars.cache_path, path.basename(target))
|
||||
|
||||
await fs.promises.copyFile(target, cachedManifest)
|
||||
|
||||
if (!fs.existsSync(cachedManifest)) {
|
||||
throw new Error(`Manifest copy failed: ${target}`)
|
||||
}
|
||||
|
||||
return {
|
||||
remote_manifest: undefined,
|
||||
local_manifest: target,
|
||||
|
@ -1,14 +1,76 @@
|
||||
import sendToRender from "../utils/sendToRender"
|
||||
import { ipcMain } from "electron"
|
||||
|
||||
export default class CoreAdapter {
|
||||
constructor(electronApp, RelicCore) {
|
||||
this.app = electronApp
|
||||
this.core = RelicCore
|
||||
|
||||
this.initialize()
|
||||
this.initialized = false
|
||||
}
|
||||
|
||||
events = {
|
||||
loggerWindow = null
|
||||
|
||||
ipcEvents = {
|
||||
"pkg:list": async () => {
|
||||
return await this.core.package.list()
|
||||
},
|
||||
"pkg:get": async (event, pkg_id) => {
|
||||
return await this.core.db.getPackages(pkg_id)
|
||||
},
|
||||
"pkg:read": async (event, manifest_path, options = {}) => {
|
||||
const manifest = await this.core.package.read(manifest_path, options)
|
||||
|
||||
return JSON.stringify({
|
||||
...this.core.db.defaultPackageState({ ...manifest }),
|
||||
...manifest,
|
||||
name: manifest.pkg_name,
|
||||
})
|
||||
},
|
||||
"pkg:install": async (event, manifest_path) => {
|
||||
return await this.core.package.install(manifest_path)
|
||||
},
|
||||
"pkg:update": async (event, pkg_id, { execOnFinish = false } = {}) => {
|
||||
await this.core.package.update(pkg_id)
|
||||
|
||||
if (execOnFinish) {
|
||||
await this.core.package.execute(pkg_id)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
"pkg:apply": async (event, pkg_id, changes) => {
|
||||
return await this.core.package.apply(pkg_id, changes)
|
||||
},
|
||||
"pkg:uninstall": async (event, pkg_id) => {
|
||||
return await this.core.package.uninstall(pkg_id)
|
||||
},
|
||||
"pkg:execute": async (event, pkg_id, { force = false } = {}) => {
|
||||
// check for updates first
|
||||
if (!force) {
|
||||
const update = await this.core.package.checkUpdate(pkg_id)
|
||||
|
||||
if (update) {
|
||||
return sendToRender("pkg:update_available", update)
|
||||
}
|
||||
}
|
||||
|
||||
return await this.core.package.execute(pkg_id)
|
||||
},
|
||||
"pkg:open": async (event, pkg_id) => {
|
||||
return await this.core.openPath(pkg_id)
|
||||
},
|
||||
"pkg:last_operation_retry": async (event, pkg_id) => {
|
||||
return await this.core.package.lastOperationRetry(pkg_id)
|
||||
},
|
||||
"pkg:cancel_current_operation": async (event, pkg_id) => {
|
||||
return await this.core.package.cancelCurrentOperation(pkg_id)
|
||||
},
|
||||
"core:open-path": async (event, pkg_id) => {
|
||||
return await this.core.openPath(pkg_id)
|
||||
},
|
||||
}
|
||||
|
||||
coreEvents = {
|
||||
"pkg:new": (pkg) => {
|
||||
sendToRender("pkg:new", pkg)
|
||||
},
|
||||
@ -51,22 +113,53 @@ export default class CoreAdapter {
|
||||
sendToRender(`new:notification`, {
|
||||
type: "error",
|
||||
message: `An error occurred`,
|
||||
description: `Something failed to ${data.event} package ${data.pkg_id}`,
|
||||
description: `Something failed to ${data.event} package ${data.id}`,
|
||||
})
|
||||
|
||||
sendToRender(`pkg:update:state`, data)
|
||||
},
|
||||
"logger:new": (data) => {
|
||||
if (this.loggerWindow) {
|
||||
this.loggerWindow.webContents.send("logger:new", data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initialize = () => {
|
||||
for (const [key, handler] of Object.entries(this.events)) {
|
||||
attachLogger = (window) => {
|
||||
this.loggerWindow = window
|
||||
|
||||
window.webContents.send("logger:new", {
|
||||
timestamp: new Date().getTime(),
|
||||
message: "Core adapter Logger attached",
|
||||
})
|
||||
}
|
||||
|
||||
initialize = async () => {
|
||||
if (this.initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const [key, handler] of Object.entries(this.coreEvents)) {
|
||||
global._relic_eventBus.on(key, handler)
|
||||
}
|
||||
|
||||
for (const [key, handler] of Object.entries(this.ipcEvents)) {
|
||||
ipcMain.handle(key, handler)
|
||||
}
|
||||
|
||||
await this.core.initialize()
|
||||
await this.core.setup()
|
||||
|
||||
this.initialized = true
|
||||
}
|
||||
|
||||
deinitialize = () => {
|
||||
for (const [key, handler] of Object.entries(this.events)) {
|
||||
for (const [key, handler] of Object.entries(this.coreEvents)) {
|
||||
global._relic_eventBus.off(key, handler)
|
||||
}
|
||||
|
||||
for (const [key, handler] of Object.entries(this.ipcEvents)) {
|
||||
ipcMain.removeHandler(key, handler)
|
||||
}
|
||||
}
|
||||
}
|
@ -26,62 +26,54 @@ const ProtocolRegistry = require("protocol-registry")
|
||||
|
||||
const protocolRegistryNamespace = "relic"
|
||||
|
||||
class LogsViewer {
|
||||
window = null
|
||||
|
||||
async createWindow() {
|
||||
this.window = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
show: false,
|
||||
resizable: true,
|
||||
autoHideMenuBar: true,
|
||||
icon: "../../resources/icon.png",
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "../preload/index.js"),
|
||||
sandbox: false,
|
||||
},
|
||||
})
|
||||
|
||||
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||
this.window.loadURL(`${process.env["ELECTRON_RENDERER_URL"]}/logs`)
|
||||
} else {
|
||||
this.window.loadFile(path.join(__dirname, "../renderer/index.html"))
|
||||
}
|
||||
|
||||
await new Promise((resolve) => this.window.once("ready-to-show", resolve))
|
||||
|
||||
this.window.show()
|
||||
|
||||
return this.window
|
||||
}
|
||||
|
||||
closeWindow() {
|
||||
if (this.window) {
|
||||
this.window.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ElectronApp {
|
||||
constructor() {
|
||||
this.win = null
|
||||
this.core = new RelicCore()
|
||||
this.adapter = new CoreAdapter(this, this.core)
|
||||
}
|
||||
|
||||
window = null
|
||||
|
||||
logsViewer = new LogsViewer()
|
||||
|
||||
handlers = {
|
||||
"pkg:list": async () => {
|
||||
return await this.core.package.list()
|
||||
},
|
||||
"pkg:get": async (event, pkg_id) => {
|
||||
return await this.core.db.getPackages(pkg_id)
|
||||
},
|
||||
"pkg:read": async (event, manifest_path, options = {}) => {
|
||||
const manifest = await this.core.package.read(manifest_path, options)
|
||||
|
||||
return JSON.stringify({
|
||||
...this.core.db.defaultPackageState({ ...manifest }),
|
||||
...manifest,
|
||||
name: manifest.pkg_name,
|
||||
})
|
||||
},
|
||||
"pkg:install": async (event, manifest_path) => {
|
||||
return await this.core.package.install(manifest_path)
|
||||
},
|
||||
"pkg:update": async (event, pkg_id, { execOnFinish = false } = {}) => {
|
||||
await this.core.package.update(pkg_id)
|
||||
|
||||
if (execOnFinish) {
|
||||
await this.core.package.execute(pkg_id)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
"pkg:apply": async (event, pkg_id, changes) => {
|
||||
return await this.core.package.apply(pkg_id, changes)
|
||||
},
|
||||
"pkg:uninstall": async (event, pkg_id) => {
|
||||
return await this.core.package.uninstall(pkg_id)
|
||||
},
|
||||
"pkg:execute": async (event, pkg_id, { force = false } = {}) => {
|
||||
// check for updates first
|
||||
if (!force) {
|
||||
const update = await this.core.package.checkUpdate(pkg_id)
|
||||
|
||||
if (update) {
|
||||
return sendToRender("pkg:update_available", update)
|
||||
}
|
||||
}
|
||||
|
||||
return await this.core.package.execute(pkg_id)
|
||||
},
|
||||
"pkg:open": async (event, pkg_id) => {
|
||||
return await this.core.openPath(pkg_id)
|
||||
},
|
||||
"updater:check": () => {
|
||||
autoUpdater.checkForUpdates()
|
||||
},
|
||||
@ -90,22 +82,31 @@ class ElectronApp {
|
||||
autoUpdater.quitAndInstall()
|
||||
}, 3000)
|
||||
},
|
||||
"settings:get": (e, key) => {
|
||||
"settings:get": (event, key) => {
|
||||
return global.SettingsStore.get(key)
|
||||
},
|
||||
"settings:set": (e, key, value) => {
|
||||
"settings:set": (event, key, value) => {
|
||||
return global.SettingsStore.set(key, value)
|
||||
},
|
||||
"settings:delete": (e, key) => {
|
||||
"settings:delete": (event, key) => {
|
||||
return global.SettingsStore.delete(key)
|
||||
},
|
||||
"settings:has": (e, key) => {
|
||||
"settings:has": (event, key) => {
|
||||
return global.SettingsStore.has(key)
|
||||
},
|
||||
"app:open-logs": async (event) => {
|
||||
const loggerWindow = await this.logsViewer.createWindow()
|
||||
|
||||
this.adapter.attachLogger(loggerWindow)
|
||||
|
||||
loggerWindow.webContents.send("logger:new", {
|
||||
timestamp: new Date().getTime(),
|
||||
message: "Logger opened, starting watching logs",
|
||||
})
|
||||
},
|
||||
"app:init": async (event, data) => {
|
||||
try {
|
||||
await this.core.initialize()
|
||||
await this.core.setup()
|
||||
await this.adapter.initialize()
|
||||
|
||||
return {
|
||||
pkg: pkg,
|
||||
@ -126,19 +127,8 @@ class ElectronApp {
|
||||
}
|
||||
}
|
||||
|
||||
events = {
|
||||
"open-runtime-path": () => {
|
||||
return this.core.openPath()
|
||||
},
|
||||
"open-dev-logs": () => {
|
||||
return sendToRender("new:message", {
|
||||
message: "Not implemented yet",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
createWindow() {
|
||||
this.win = global.win = new BrowserWindow({
|
||||
this.window = global.mainWindow = new BrowserWindow({
|
||||
width: 450,
|
||||
height: 670,
|
||||
show: false,
|
||||
@ -151,20 +141,20 @@ class ElectronApp {
|
||||
}
|
||||
})
|
||||
|
||||
this.win.on("ready-to-show", () => {
|
||||
this.win.show()
|
||||
this.window.on("ready-to-show", () => {
|
||||
this.window.show()
|
||||
})
|
||||
|
||||
this.win.webContents.setWindowOpenHandler((details) => {
|
||||
this.window.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url)
|
||||
|
||||
return { action: "deny" }
|
||||
})
|
||||
|
||||
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||
this.win.loadURL(process.env["ELECTRON_RENDERER_URL"])
|
||||
this.window.loadURL(process.env["ELECTRON_RENDERER_URL"])
|
||||
} else {
|
||||
this.win.loadFile(path.join(__dirname, "../renderer/index.html"))
|
||||
this.window.loadFile(path.join(__dirname, "../renderer/index.html"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,12 +196,12 @@ class ElectronApp {
|
||||
event.preventDefault()
|
||||
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (this.win) {
|
||||
if (this.win.isMinimized()) {
|
||||
this.win.restore()
|
||||
if (this.window) {
|
||||
if (this.window.isMinimized()) {
|
||||
this.window.restore()
|
||||
}
|
||||
|
||||
this.win.focus()
|
||||
this.window.focus()
|
||||
}
|
||||
|
||||
console.log(`Second instance >`, commandLine)
|
||||
@ -235,10 +225,6 @@ class ElectronApp {
|
||||
ipcMain.handle(key, this.handlers[key])
|
||||
}
|
||||
|
||||
for (const key in this.events) {
|
||||
ipcMain.on(key, this.events[key])
|
||||
}
|
||||
|
||||
app.on("second-instance", this.handleOnSecondInstance)
|
||||
|
||||
app.on("open-url", (event, url) => {
|
||||
@ -308,4 +294,4 @@ class ElectronApp {
|
||||
}
|
||||
}
|
||||
|
||||
new ElectronApp().initialize()
|
||||
new ElectronApp().initialize()
|
@ -32,7 +32,7 @@ export default (event, data) => {
|
||||
return copy
|
||||
}
|
||||
|
||||
global.win.webContents.send(event, serializeIpc(data))
|
||||
global.mainWindow.webContents.send(event, serializeIpc(data))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
@ -12,14 +12,19 @@ import AppDrawer from "layout/components/Drawer"
|
||||
|
||||
import { InternalRouter, PageRender } from "./router.jsx"
|
||||
|
||||
import CrashError from "components/Crash"
|
||||
import LogsViewer from "./pages/logs"
|
||||
|
||||
// create a global app context
|
||||
window.app = GlobalApp
|
||||
|
||||
class App extends React.Component {
|
||||
state = {
|
||||
initializing: true,
|
||||
pkg: null,
|
||||
|
||||
crash: null,
|
||||
initializing: true,
|
||||
|
||||
appSetup: {
|
||||
error: false,
|
||||
installed: false,
|
||||
@ -98,6 +103,15 @@ class App extends React.Component {
|
||||
console.log(`React version > ${versions["react"]}`)
|
||||
console.log(`DOMRouter version > ${versions["react-router-dom"]}`)
|
||||
|
||||
//check if path is /logs
|
||||
if (window.location.pathname === "/logs") {
|
||||
return await this.setState({
|
||||
initializing: false,
|
||||
no_layout: true,
|
||||
log_viewer_mode: true,
|
||||
})
|
||||
}
|
||||
|
||||
window.app.style.appendClassname("initializing")
|
||||
|
||||
for (const event in this.ipcEvents) {
|
||||
@ -133,17 +147,34 @@ class App extends React.Component {
|
||||
algorithm: antd.theme.darkAlgorithm
|
||||
}}
|
||||
>
|
||||
<InternalRouter>
|
||||
<GlobalStateContext.Provider value={this.state}>
|
||||
{
|
||||
this.state.log_viewer_mode && <LogsViewer />
|
||||
}
|
||||
|
||||
<AppDrawer />
|
||||
<AppModalDialog />
|
||||
{
|
||||
!this.state.log_viewer_mode && <>
|
||||
<InternalRouter>
|
||||
<GlobalStateContext.Provider value={this.state}>
|
||||
{
|
||||
!this.state.crash && <>
|
||||
<AppDrawer />
|
||||
<AppModalDialog />
|
||||
|
||||
<AppLayout>
|
||||
<PageRender />
|
||||
</AppLayout>
|
||||
</GlobalStateContext.Provider>
|
||||
</InternalRouter>
|
||||
<AppLayout>
|
||||
<PageRender />
|
||||
</AppLayout>
|
||||
</>
|
||||
}
|
||||
|
||||
{
|
||||
this.state.crash && <CrashError
|
||||
crash={this.state.crash}
|
||||
/>
|
||||
}
|
||||
</GlobalStateContext.Provider>
|
||||
</InternalRouter>
|
||||
</>
|
||||
}
|
||||
</antd.ConfigProvider>
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
gap: 20px;
|
||||
|
||||
padding: 20px;
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
|
@ -14,7 +14,7 @@ const PackageItem = (props) => {
|
||||
const isLoading = manifest.last_status === "loading" || manifest.last_status === "installing" || manifest.last_status === "updating"
|
||||
const isInstalling = manifest.last_status === "installing"
|
||||
const isInstalled = !!manifest.installed_at
|
||||
const isFailed = manifest.last_status === "error"
|
||||
const isFailed = manifest.last_status === "failed"
|
||||
|
||||
console.log(manifest, {
|
||||
isLoading,
|
||||
@ -38,7 +38,7 @@ const PackageItem = (props) => {
|
||||
}
|
||||
|
||||
const onClickFolder = () => {
|
||||
ipc.exec("pkg:open", manifest.id)
|
||||
ipc.exec("core:open-path", manifest.id)
|
||||
}
|
||||
|
||||
const onClickDelete = () => {
|
||||
@ -60,7 +60,7 @@ const PackageItem = (props) => {
|
||||
}
|
||||
|
||||
const onClickRetryInstall = () => {
|
||||
ipc.exec("pkg:retry_install", manifest.id)
|
||||
ipc.exec("pkg:last_operation_retry", manifest.id)
|
||||
}
|
||||
|
||||
function handleUpdate(event, data) {
|
||||
@ -75,7 +75,7 @@ const PackageItem = (props) => {
|
||||
return manifest.last_status
|
||||
}
|
||||
|
||||
return `v${manifest.version}` ?? "N/A"
|
||||
return `${isFailed ? "failed |" : ""} v${manifest.version}` ?? "N/A"
|
||||
}
|
||||
|
||||
const MenuProps = {
|
||||
@ -148,7 +148,6 @@ const PackageItem = (props) => {
|
||||
manifest.icon && <img src={manifest.icon} className="installation_item_icon" />
|
||||
}
|
||||
|
||||
|
||||
<div className="installation_item_info">
|
||||
<h2>
|
||||
{
|
||||
@ -164,16 +163,24 @@ const PackageItem = (props) => {
|
||||
|
||||
<div className="installation_item_actions">
|
||||
{
|
||||
isFailed && <antd.Button
|
||||
type="primary"
|
||||
onClick={onClickRetryInstall}
|
||||
>
|
||||
Retry
|
||||
</antd.Button>
|
||||
isFailed && <>
|
||||
<antd.Button
|
||||
type="primary"
|
||||
onClick={onClickRetryInstall}
|
||||
>
|
||||
Retry
|
||||
</antd.Button>
|
||||
|
||||
<antd.Button
|
||||
icon={<MdDelete />}
|
||||
type="primary"
|
||||
onClick={onClickDelete}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
{
|
||||
isInstalled && manifest.executable && <antd.Dropdown.Button
|
||||
!isFailed && isInstalled && manifest.executable && <antd.Dropdown.Button
|
||||
menu={MenuProps}
|
||||
onClick={onClickPlay}
|
||||
type="primary"
|
||||
@ -186,7 +193,7 @@ const PackageItem = (props) => {
|
||||
}
|
||||
|
||||
{
|
||||
isInstalled && !manifest.executable && <antd.Dropdown
|
||||
isFailed && isInstalled && !manifest.executable && <antd.Dropdown
|
||||
menu={MenuProps}
|
||||
disabled={isLoading}
|
||||
>
|
||||
@ -199,7 +206,7 @@ const PackageItem = (props) => {
|
||||
}
|
||||
|
||||
{
|
||||
isInstalling && <antd.Button
|
||||
isFailed && isInstalling && <antd.Button
|
||||
type="primary"
|
||||
onClick={onClickCancelInstall}
|
||||
>
|
||||
|
67
packages/gui/src/renderer/src/pages/logs/index.jsx
Normal file
67
packages/gui/src/renderer/src/pages/logs/index.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
import React from "react"
|
||||
|
||||
import "./index.less"
|
||||
|
||||
const Timestamp = ({ timestamp }) => {
|
||||
if (isNaN(timestamp)) {
|
||||
return <span className="timestamp">{timestamp}</span>
|
||||
}
|
||||
|
||||
return <span
|
||||
className="timestamp"
|
||||
>
|
||||
{
|
||||
new Date(timestamp).toLocaleString().split(", ").join("|")
|
||||
}
|
||||
</span>
|
||||
}
|
||||
|
||||
const LogEntry = ({ log }) => {
|
||||
return <div className="log-entry">
|
||||
<span className="line_indicator">
|
||||
{">"}
|
||||
</span>
|
||||
|
||||
{log.timestamp && <Timestamp timestamp={log.timestamp} />}
|
||||
|
||||
{!log.timestamp && <span className="timestamp">- no timestamp -</span>}
|
||||
|
||||
<p>
|
||||
{log.message ?? "No message"}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
const LogsViewer = () => {
|
||||
const listRef = React.useRef()
|
||||
const [timeline, setTimeline] = React.useState([])
|
||||
|
||||
const events = {
|
||||
"logger:new": (event, log) => {
|
||||
setTimeline((timeline) => [...timeline, log])
|
||||
|
||||
listRef.current.scrollTop = listRef.current.scrollHeight
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
for (const event in events) {
|
||||
ipc.exclusiveListen(event, events[event])
|
||||
}
|
||||
}, [])
|
||||
|
||||
return <div
|
||||
className="app-logs"
|
||||
ref={listRef}
|
||||
>
|
||||
{
|
||||
timeline.length === 0 && <p>No logs</p>
|
||||
}
|
||||
|
||||
{
|
||||
timeline.map((log) => <LogEntry key={log.id} log={log} />)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default LogsViewer
|
47
packages/gui/src/renderer/src/pages/logs/index.less
Normal file
47
packages/gui/src/renderer/src/pages/logs/index.less
Normal file
@ -0,0 +1,47 @@
|
||||
.app-logs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
padding: 10px;
|
||||
|
||||
font-family: "DM Mono", monospace;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
|
||||
height: 100vh;
|
||||
|
||||
.log-entry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
align-items: flex-start;
|
||||
|
||||
gap: 7px;
|
||||
font-size: 0.8rem;
|
||||
line-height: 0.8rem;
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
padding: 8px;
|
||||
|
||||
color: var(--text-color);
|
||||
|
||||
span {
|
||||
color: var(--text-color);
|
||||
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
opacity: 0.9;
|
||||
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
&:nth-child(odd) {
|
||||
background-color: var(--background-color-secondary);
|
||||
}
|
||||
}
|
||||
}
|
@ -307,8 +307,6 @@ const PackageOptionsLoader = (props) => {
|
||||
})
|
||||
}
|
||||
|
||||
console.log(manifest)
|
||||
|
||||
if (!manifest) {
|
||||
return <antd.Skeleton active />
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import loadable from "@loadable/component"
|
||||
import GlobalStateContext from "contexts/global"
|
||||
|
||||
import SplashScreen from "components/Splash"
|
||||
import CrashError from "components/Crash"
|
||||
|
||||
const DefaultNotFoundRender = () => {
|
||||
return <div>Not found</div>
|
||||
@ -131,18 +130,6 @@ export const InternalRouter = (props) => {
|
||||
}
|
||||
|
||||
export const PageRender = (props) => {
|
||||
const globalState = React.useContext(GlobalStateContext)
|
||||
|
||||
if (globalState.crash) {
|
||||
return <CrashError
|
||||
crash={globalState.crash}
|
||||
/>
|
||||
}
|
||||
|
||||
if (globalState.initializing) {
|
||||
return <SplashScreen />
|
||||
}
|
||||
|
||||
const routes = React.useMemo(() => {
|
||||
let paths = {
|
||||
...import.meta.glob("/src/pages/**/[a-z[]*.jsx"),
|
||||
@ -167,6 +154,12 @@ export const PageRender = (props) => {
|
||||
return paths
|
||||
}, [])
|
||||
|
||||
const globalState = React.useContext(GlobalStateContext)
|
||||
|
||||
if (globalState.initializing) {
|
||||
return <SplashScreen />
|
||||
}
|
||||
|
||||
return <Routes>
|
||||
{
|
||||
routes.map((route, index) => {
|
||||
|
@ -20,6 +20,7 @@ export default [
|
||||
render: (props) => {
|
||||
return (
|
||||
<Button
|
||||
disabled
|
||||
type={props.value ? "primary" : "default"}
|
||||
onClick={() => {
|
||||
if (!props.value) {
|
||||
@ -65,7 +66,10 @@ export default [
|
||||
icon: "MdUpdate",
|
||||
type: "switch",
|
||||
storaged: true,
|
||||
defaultValue: false
|
||||
defaultValue: false,
|
||||
props: {
|
||||
disabled: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -83,7 +87,7 @@ export default [
|
||||
props: {
|
||||
children: "Open",
|
||||
onClick: () => {
|
||||
ipc.send("open-runtime-path")
|
||||
ipc.exec("core:open-path")
|
||||
}
|
||||
},
|
||||
storaged: false
|
||||
@ -97,7 +101,7 @@ export default [
|
||||
props: {
|
||||
children: "Open",
|
||||
onClick: () => {
|
||||
ipc.send("open-dev-logs")
|
||||
ipc.exec("app:open-logs")
|
||||
}
|
||||
},
|
||||
storaged: false
|
||||
|
Loading…
x
Reference in New Issue
Block a user