merge from local

This commit is contained in:
SrGooglo 2024-04-02 16:01:02 +02:00
parent 363ad1cd97
commit fd6358ef11
22 changed files with 434 additions and 155 deletions

View File

@ -10,9 +10,14 @@
}, },
"cSpell.words": [ "cSpell.words": [
"admzip", "admzip",
"antd",
"APPDATA", "APPDATA",
"catched", "catched",
"Classname",
"execa", "execa",
"getstation",
"imjs",
"ragestudio",
"rclone", "rclone",
"sevenzip", "sevenzip",
"unzipper", "unzipper",

View File

@ -29,6 +29,7 @@
"open": "8.4.2", "open": "8.4.2",
"request": "^2.88.2", "request": "^2.88.2",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"signal-exit": "^4.1.0",
"unzipper": "^0.10.14", "unzipper": "^0.10.14",
"upath": "^2.0.1", "upath": "^2.0.1",
"uuid": "^9.0.1", "uuid": "^9.0.1",

View File

@ -0,0 +1,36 @@
import path from "path"
import { JSONFilePreset } from "../libraries/lowdb/presets/node"
import Vars from "../vars"
//! WARNING: Please DO NOT storage any password or sensitive data here,
// cause its not use any encryption method, and it will be stored in plain text.
// This is intended to store session tokens among other vars.
export default class ManifestAuthService {
static vaultPath = path.resolve(Vars.runtime_path, "auth.json")
static async withDB() {
return await JSONFilePreset(ManifestAuthService.vaultPath, {})
}
static has = async (pkg_id) => {
const db = await this.withDB()
return !!db.data[pkg_id]
}
static set = async (pkg_id, value) => {
const db = await this.withDB()
return await db.update((data) => {
data[pkg_id] = value
})
}
static get = async (pkg_id) => {
const db = await this.withDB()
return await db.data[pkg_id]
}
}

View File

@ -0,0 +1,33 @@
import ManifestAuthDB from "../classes/ManifestAuthDB"
import DB from "../db"
import Logger from "../logger"
const Log = Logger.child({ service: "AUTH" })
export default async (pkg_id, value) => {
if (!pkg_id) {
Log.error("pkg_id is required")
return false
}
if (!value) {
Log.error("value is required")
return false
}
const pkg = await DB.getPackages(pkg_id)
if (!pkg) {
Log.error("Package not found")
return false
}
Log.info(`Setting auth for [${pkg_id}]`)
await ManifestAuthDB.set(pkg_id, value)
global._relic_eventBus.emit("pkg:authorized", pkg)
return true
}

View File

@ -67,7 +67,9 @@ export default async function execute(pkg_id, { useRemote = false, force = false
} catch (error) { } catch (error) {
global._relic_eventBus.emit(`pkg:error`, { global._relic_eventBus.emit(`pkg:error`, {
id: pkg_id, id: pkg_id,
error event: "execute",
last_status: "installed",
error,
}) })
BaseLog.error(`Failed to execute package [${pkg_id}]`, error) BaseLog.error(`Failed to execute package [${pkg_id}]`, error)

View File

@ -32,17 +32,40 @@ export default async () => {
if (!fs.existsSync(prerequisite.finalBin)) { if (!fs.existsSync(prerequisite.finalBin)) {
Log.info(`Missing prerequisite: ${prerequisite.id}, installing...`) Log.info(`Missing prerequisite: ${prerequisite.id}, installing...`)
global._relic_eventBus.emit("app:setup", {
installed: false,
message: `Installing ${prerequisite.id}`,
})
if (fs.existsSync(prerequisite.destination)) { if (fs.existsSync(prerequisite.destination)) {
Log.info(`Deleting temporal file [${prerequisite.destination}]`) Log.info(`Deleting temporal file [${prerequisite.destination}]`)
global._relic_eventBus.emit("app:setup", {
installed: false,
message: `Deleting temporal file [${prerequisite.destination}]`,
})
await fs.promises.rm(prerequisite.destination) await fs.promises.rm(prerequisite.destination)
} }
if (fs.existsSync(prerequisite.extract)) { if (fs.existsSync(prerequisite.extract)) {
Log.info(`Deleting temporal directory [${prerequisite.extract}]`) Log.info(`Deleting temporal directory [${prerequisite.extract}]`)
global._relic_eventBus.emit("app:setup", {
installed: false,
message: `Deleting temporal directory [${prerequisite.extract}]`,
})
await fs.promises.rm(prerequisite.extract, { recursive: true }) await fs.promises.rm(prerequisite.extract, { recursive: true })
} }
Log.info(`Creating base directory: ${Vars.binaries_path}/${prerequisite.id}...`) Log.info(`Creating base directory: ${Vars.binaries_path}/${prerequisite.id}...`)
global._relic_eventBus.emit("app:setup", {
installed: false,
message: `Creating base directory: ${Vars.binaries_path}/${prerequisite.id}`,
})
await fs.promises.mkdir(path.resolve(Vars.binaries_path, prerequisite.id), { recursive: true }) await fs.promises.mkdir(path.resolve(Vars.binaries_path, prerequisite.id), { recursive: true })
if (typeof prerequisite.url === "function") { if (typeof prerequisite.url === "function") {
@ -52,10 +75,21 @@ export default async () => {
Log.info(`Downloading ${prerequisite.id} from [${prerequisite.url}] to destination [${prerequisite.destination}]...`) Log.info(`Downloading ${prerequisite.id} from [${prerequisite.url}] to destination [${prerequisite.destination}]...`)
global._relic_eventBus.emit("app:setup", {
installed: false,
message: `Starting download ${prerequisite.id} from [${prerequisite.url}] to destination [${prerequisite.destination}]`,
})
try { try {
await downloadFile( await downloadFile(
prerequisite.url, prerequisite.url,
prerequisite.destination prerequisite.destination,
(progress) => {
global._relic_eventBus.emit("app:setup", {
installed: false,
message: `Downloaded ${progress.transferredString} / ${progress.totalString} | ${progress.speedString}/s`,
})
}
) )
} catch (error) { } catch (error) {
await fs.promises.rm(prerequisite.destination) await fs.promises.rm(prerequisite.destination)
@ -66,6 +100,11 @@ export default async () => {
if (typeof prerequisite.extract === "string") { if (typeof prerequisite.extract === "string") {
Log.info(`Extracting ${prerequisite.id} to destination [${prerequisite.extract}]...`) Log.info(`Extracting ${prerequisite.id} to destination [${prerequisite.extract}]...`)
global._relic_eventBus.emit("app:setup", {
installed: false,
message: `Extracting ${prerequisite.id} to destination [${prerequisite.extract}]`,
})
const zip = new admzip(prerequisite.destination) const zip = new admzip(prerequisite.destination)
await zip.extractAllTo(prerequisite.extract, true) await zip.extractAllTo(prerequisite.extract, true)
@ -88,6 +127,12 @@ export default async () => {
if (prerequisite.deleteBeforeExtract === true) { if (prerequisite.deleteBeforeExtract === true) {
Log.info(`Deleting temporal file [${prerequisite.destination}]`) Log.info(`Deleting temporal file [${prerequisite.destination}]`)
global._relic_eventBus.emit("app:setup", {
installed: false,
message: `Deleting temporal file [${prerequisite.destination}]`,
})
await fs.promises.unlink(prerequisite.destination) await fs.promises.unlink(prerequisite.destination)
} }
@ -97,6 +142,12 @@ export default async () => {
prerequisite.finalBin prerequisite.finalBin
Log.info(`Rewriting permissions to ${to}...`) Log.info(`Rewriting permissions to ${to}...`)
global._relic_eventBus.emit("app:setup", {
installed: false,
message: `Rewriting permissions to ${to}`,
})
await chmodRecursive(to, 0o755) await chmodRecursive(to, 0o755)
} }
@ -104,6 +155,11 @@ export default async () => {
for (const dir of prerequisite.moveDirs) { for (const dir of prerequisite.moveDirs) {
Log.info(`Moving ${dir.from} to ${dir.to}...`) Log.info(`Moving ${dir.from} to ${dir.to}...`)
global._relic_eventBus.emit("app:setup", {
installed: false,
message: `Moving ${dir.from} to ${dir.to}`,
})
await fs.promises.rename(dir.from, dir.to) await fs.promises.rename(dir.from, dir.to)
if (dir.deleteParentBefore === true) { if (dir.deleteParentBefore === true) {
@ -113,8 +169,19 @@ export default async () => {
} }
} }
global._relic_eventBus.emit("app:setup", {
installed: true,
message: null,
})
Log.info(`Prerequisite: ${prerequisite.id} is ready!`) Log.info(`Prerequisite: ${prerequisite.id} is ready!`)
} catch (error) { } catch (error) {
global._relic_eventBus.emit("app:setup", {
installed: false,
error: error,
message: error.message,
})
Log.error(error) Log.error(error)
Log.error("Aborting setup due to an error...") Log.error("Aborting setup due to an error...")
return false return false

View File

@ -16,6 +16,7 @@ import PackageUpdate from "./handlers/update"
import PackageApply from "./handlers/apply" import PackageApply from "./handlers/apply"
import PackageList from "./handlers/list" import PackageList from "./handlers/list"
import PackageRead from "./handlers/read" import PackageRead from "./handlers/read"
import PackageAuthorize from "./handlers/authorize"
export default class RelicCore { export default class RelicCore {
constructor(params) { constructor(params) {
@ -26,8 +27,6 @@ export default class RelicCore {
logger = Logger logger = Logger
db = DB
async initialize() { async initialize() {
await DB.initialize() await DB.initialize()
@ -52,6 +51,7 @@ export default class RelicCore {
apply: PackageApply, apply: PackageApply,
list: PackageList, list: PackageList,
read: PackageRead, read: PackageRead,
authorize: PackageAuthorize,
} }
openPath(pkg_id) { openPath(pkg_id) {

View File

@ -1,5 +1,6 @@
import open from "open" import open from "open"
import axios from "axios" import axios from "axios"
import ManifestAuthDB from "../../../classes/ManifestAuthDB"
export default class Auth { export default class Auth {
constructor(ctx) { constructor(ctx) {
@ -7,31 +8,24 @@ export default class Auth {
} }
async get() { async get() {
return { const storagedData = await ManifestAuthDB.get(this.manifest.id)
assigned_username: "test",
}
const authData = global.authService.getAuth(this.manifest.id) if (storagedData && this.manifest.authService) {
if (!this.manifest.authService.getter) {
return storagedData
}
console.log(authData)
if (authData && this.manifest.auth && this.manifest.auth.getter) {
const result = await axios({ const result = await axios({
method: "POST", method: "POST",
url: this.manifest.auth.getter, url: this.manifest.authService.getter,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
data: { data: {
auth_data: authData, auth_data: storagedData,
} }
}).catch((err) => { }).catch((err) => {
sendToRender(`new:notification`, { global._relic_eventBus.emit("auth:getter:error", err)
type: "error",
message: "Failed to authorize",
description: err.response.data.message ?? err.response.data.error ?? err.message,
duration: 10
})
return err return err
}) })
@ -45,16 +39,15 @@ export default class Auth {
return result.data return result.data
} }
return authData return storagedData
} }
request() { request() {
return true if (!this.manifest.authService || !this.manifest.authService.fetcher) {
if (!this.manifest.auth) {
return false return false
} }
const authURL = this.manifest.auth.fetcher const authURL = this.manifest.authService.fetcher
open(authURL) open(authURL)
} }

View File

@ -19,12 +19,7 @@ nsis:
uninstallDisplayName: ${productName} uninstallDisplayName: ${productName}
createDesktopShortcut: always createDesktopShortcut: always
mac: mac:
entitlementsInherit: build/entitlements.mac.plist icon: resources/icon.icns
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
notarize: false notarize: false
dmg: dmg:
artifactName: ${name}-${version}.${ext} artifactName: ${name}-${version}.${ext}

View File

@ -28,6 +28,33 @@ export default class CoreAdapter {
}, },
"pkg:new:done": (pkg) => { "pkg:new:done": (pkg) => {
sendToRender("pkg:new:done", pkg) sendToRender("pkg:new:done", pkg)
},
"app:setup": (data) => {
sendToRender("app:setup", data)
},
"auth:getter:error": (err) => {
sendToRender(`new:notification`, {
type: "error",
message: "Failed to authorize",
description: err.response.data.message ?? err.response.data.error ?? err.message,
duration: 10
})
},
"pkg:authorized": (pkg) => {
sendToRender(`new:notification`, {
type: "success",
message: "Package authorized",
description: `${pkg.name} has been authorized! You can start the package now.`,
})
},
"pkg:error": (data) => {
sendToRender(`new:notification`, {
type: "error",
message: `An error occurred`,
description: `Something failed to ${data.event} package ${data.pkg_id}`,
})
sendToRender(`pkg:update:state`, data)
} }
} }

View File

@ -169,18 +169,18 @@ class ElectronApp {
case "authorize": { case "authorize": {
if (!explicitAction[2]) { if (!explicitAction[2]) {
const [pkg_id, token] = explicitAction[1].split("%23") const [pkg_id, token] = explicitAction[1].split("%23")
return this.core.auth.authorize(pkg_id, token) return this.core.package.authorize(pkg_id, token)
} else { } else {
return this.core.auth.authorize(explicitAction[1], explicitAction[2]) return this.core.package.authorize(explicitAction[1], explicitAction[2])
} }
} }
default: { default: {
return sendToRender("installation:invoked", explicitAction[0]) return sendToRender("pkg:installation:invoked", explicitAction[0])
} }
} }
} else { } else {
// by default if no action is specified, assume is a install action // by default if no action is specified, assume is a install action
return sendToRender("installation:invoked", urlValue) return sendToRender("pkg:installation:invoked", urlValue)
} }
} }
} }

View File

@ -1,16 +1,15 @@
<!doctype html> <!doctype html>
<html> <html>
<head>
<meta charset="UTF-8" />
<title>Relic</title>
<!-- <meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
/> -->
</head>
<body> <head>
<div id="root"></div> <meta charset="UTF-8" />
<script type="module" src="/src/main.jsx"></script> <title>Relic</title>
</body> <meta http-equiv="Content-Security-Policy" content="script-src 'self'" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html> </html>

View File

@ -17,26 +17,24 @@ window.app = GlobalApp
class App extends React.Component { class App extends React.Component {
state = { state = {
loading: true, initializing: true,
pkg: null, pkg: null,
initializing: false,
setup_step: null, appSetup: {
updateAvailable: false, error: false,
updateText: null, installed: false,
message: null,
authorizedServices: {
drive: false,
}, },
appUpdate: {
changelog: null,
available: false,
},
authorizedServices: [],
} }
ipcEvents = { ipcEvents = {
"runtime:error": (event, data) => {
antd.message.error(data)
},
"runtime:info": (event, data) => {
antd.message.info(data)
},
"new:notification": (event, data) => { "new:notification": (event, data) => {
app.notification[data.type || "info"]({ app.notification[data.type || "info"]({
message: data.message, message: data.message,
@ -50,8 +48,13 @@ class App extends React.Component {
"new:message": (event, data) => { "new:message": (event, data) => {
antd.message[data.type || "info"](data.message) antd.message[data.type || "info"](data.message)
}, },
"app:setup": (event, data) => {
this.setState({
appSetup: data,
})
},
"app:update_available": (event, data) => { "app:update_available": (event, data) => {
if (this.state.loading) { if (this.state.initializing) {
return false return false
} }
@ -62,73 +65,49 @@ class App extends React.Component {
app.appUpdateAvailable(data) app.appUpdateAvailable(data)
}, },
"pkg:install:ask": (event, data) => { "pkg:install:ask": (event, data) => {
if (this.state.loading) { if (this.state.initializing) {
return false return false
} }
app.pkgInstallWizard(data) app.pkgInstallWizard(data)
}, },
"pkg:update_available": (event, data) => { "pkg:update_available": (event, data) => {
if (this.state.loading) { if (this.state.initializing) {
return false return false
} }
app.pkgUpdateAvailable(data) app.pkgUpdateAvailable(data)
}, },
"installation:invoked": (event, manifest) => { "pkg:installation:invoked": (event, data) => {
if (this.state.loading) { if (this.state.initializing) {
return false return false
} }
app.invokeInstall(manifest) app.invokeInstall(data)
}, }
"drive:authorized": (event, data) => {
this.setState({
authorizedServices: {
drive: true,
},
})
message.success("Google Drive API authorized")
},
"drive:unauthorized": (event, data) => {
this.setState({
authorizedServices: {
drive: false,
},
})
message.success("Google Drive API unauthorized")
},
"setup_step": (event, data) => {
console.log(`setup:step`, data)
this.setState({
setup_step: data,
})
},
} }
componentDidMount = async () => { componentDidMount = async () => {
const initResult = await ipc.exec("app:init") window.app.style.appendClassname("initializing")
console.log(`Using React version > ${versions["react"]}`)
console.log(`Using DOMRouter version > ${versions["react-router-dom"]}`)
console.log(`[APP] app:init() | Result >`, initResult)
for (const event in this.ipcEvents) { for (const event in this.ipcEvents) {
ipc.exclusiveListen(event, this.ipcEvents[event]) ipc.exclusiveListen(event, this.ipcEvents[event])
} }
const mainInitialization = await ipc.exec("app:init")
console.log(`React version > ${versions["react"]}`)
console.log(`DOMRouter version > ${versions["react-router-dom"]}`)
console.log(`app:init() | Result >`, mainInitialization)
await this.setState({
initializing: false,
pkg: mainInitialization.pkg,
})
app.location.push("/") app.location.push("/")
this.setState({ window.app.style.removeClassname("initializing")
loading: false,
pkg: initResult.pkg,
authorizedServices: {
drive: initResult.authorizedServices?.drive ?? false
},
})
} }
render() { render() {

View File

@ -10,7 +10,25 @@ globalThis.getRootCssVar = getRootCssVar
globalThis.notification = notification globalThis.notification = notification
globalThis.message = message globalThis.message = message
class GlobalStyleController {
static root = document.getElementById("root")
static appendClassname = (classname) => {
console.log(`appending classname >`, classname)
GlobalStyleController.root.classList.add(classname)
}
static removeClassname = (classname) => {
console.log(`removing classname >`, classname)
GlobalStyleController.root.classList.remove(classname)
}
static getRootCssVar = getRootCssVar
}
export default class GlobalCTXApp { export default class GlobalCTXApp {
static style = GlobalStyleController
static applyUpdate = () => { static applyUpdate = () => {
message.loading("Updating, please wait...") message.loading("Updating, please wait...")

View File

@ -0,0 +1,47 @@
import React from "react"
import * as antd from "antd"
import { BarLoader } from "react-spinners"
import GlobalStateContext from "contexts/global"
import "./index.less"
const Splash = (props) => {
const globalState = React.useContext(GlobalStateContext)
return <div className="splash">
{
!!globalState.appSetup.message && <div className="app-setup_header">
<h1>
Setting up...
</h1>
<p>
Please wait while the application is being set up.
</p>
</div>
}
{
globalState.appSetup.message && <>
<div className="app-setup_message">
<span>
{globalState.appSetup.message}
</span>
</div>
<BarLoader
className="app_loader"
color={getRootCssVar("--primary-color")}
/>
</>
}
{
!globalState.appSetup.message && <antd.Skeleton
active
round
/>
}
</div>
}
export default Splash

View File

@ -0,0 +1,32 @@
.splash {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
gap: 20px;
.app-setup_header {
display: flex;
flex-direction: column;
gap: 10px;
h1 {
font-size: 1.7rem;
}
}
.app-setup_message {
padding: 15px 10px;
border-radius: 12px;
transition: all 150ms ease-in-out;
background-color: var(--background-color-secondary);
font-family: "DM Mono", monospace;
}
}

View File

@ -5,8 +5,8 @@ export const Context = React.createContext([])
export class WithContext extends React.Component { export class WithContext extends React.Component {
state = { state = {
loading: true,
packages: [], packages: [],
pendingInstallation: false,
} }
ipcEvents = { ipcEvents = {
@ -62,24 +62,27 @@ export class WithContext extends React.Component {
packages: newData packages: newData
}) })
} }
console.log(`[ipc] pkg:update:state >`, data)
} }
} }
componentDidMount = async () => { loadPackages = async () => {
await this.setState({
loading: true,
})
const packages = await ipc.exec("pkg:list") const packages = await ipc.exec("pkg:list")
await this.setState({
packages: packages,
})
}
componentDidMount = async () => {
for (const event in this.ipcEvents) { for (const event in this.ipcEvents) {
ipc.exclusiveListen(event, this.ipcEvents[event]) ipc.exclusiveListen(event, this.ipcEvents[event])
} }
this.setState({ await this.loadPackages()
packages: [
...this.state.packages,
...packages,
]
})
} }
render() { render() {

View File

@ -12,19 +12,22 @@
view-transition-name: main-header; view-transition-name: main-header;
height: var(--app_header_height);
display: inline-flex; display: inline-flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
background-color: darken(@var-background-color-primary, 10%);
gap: 30px; gap: 30px;
height: var(--app_header_height);
padding: 0 20px; padding: 0 20px;
overflow: hidden;
transition: 150ms ease-in-out;
background-color: darken(@var-background-color-primary, 10%);
.branding { .branding {
display: inline-flex; display: inline-flex;
flex-direction: row; flex-direction: row;
@ -69,9 +72,12 @@
justify-content: center; justify-content: center;
border-radius: 50%; border-radius: 50%;
padding: 3px;
background-color: var(--primary-color); background-color: var(--primary-color);
font-size: 1.4rem; font-size: 1.6rem;
color: #3b3b3b;
} }
.app_header_nav_title { .app_header_nav_title {

View File

@ -10,25 +10,26 @@ import NewInstallation from "components/NewInstallation"
import "./index.less" import "./index.less"
class InstallationsManager extends React.Component { class Packages extends React.Component {
static contextType = InstallationsContext static contextType = InstallationsContext
render() { render() {
const { packages } = this.context const { packages, loading } = this.context
const empty = packages.length == 0 const empty = packages.length == 0
return <div className="installations_manager"> return <div className="packages">
<div className="installations_manager-header"> <div className="packages-header">
<antd.Button <antd.Button
type="primary" type="primary"
icon={<MdAdd />} icon={<MdAdd />}
onClick={() => app.drawer.open(NewInstallation, { onClick={() => app.drawer.open(NewInstallation, {
title: "Add new installation", title: "Install new package",
height: "200px", height: "200px",
})} })}
className="add-btn"
> >
Add new installation Add new
</antd.Button> </antd.Button>
<antd.Input.Search <antd.Input.Search
@ -39,13 +40,17 @@ class InstallationsManager extends React.Component {
/> />
</div> </div>
<div className={empty ? "installations_list empty" : "installations_list"}> <div className={empty ? "packages-list empty" : "packages-list"}>
{ {
empty && <antd.Empty description="No packages installed" /> loading && <antd.Skeleton active round />
} }
{ {
packages.map((manifest) => { !loading && empty && <antd.Empty description="No packages installed" />
}
{
!loading && packages.map((manifest) => {
return <PackageItem key={manifest.id} manifest={manifest} /> return <PackageItem key={manifest.id} manifest={manifest} />
}) })
} }
@ -54,10 +59,10 @@ class InstallationsManager extends React.Component {
} }
} }
const InstallationsManagerPage = (props) => { const PackagesPage = (props) => {
return <WithContext> return <WithContext>
<InstallationsManager {...props} /> <Packages {...props} />
</WithContext> </WithContext>
} }
export default InstallationsManagerPage export default PackagesPage

View File

@ -1,4 +1,4 @@
.installations_manager { .packages {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -6,17 +6,52 @@
gap: 10px; gap: 10px;
.installations_manager-header { .packages-header {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
gap: 10px; gap: 10px;
.ant-btn-default { .ant-btn-default {
background-color: var(--background-color-secondary); background-color: var(--background-color-secondary);
} }
.add-btn {
display: flex;
flex-direction: row;
padding: 5px;
border-radius: 12px;
gap: 0px;
span:not(.ant-btn-icon) {
opacity: 0;
transition: all 150ms ease;
max-width: 0px;
}
&:hover {
padding: 5px 10px;
border-radius: 8px;
gap: 7px;
span:not(.ant-btn-icon) {
opacity: 1;
max-width: 200px;
}
}
.ant-btn-icon {
margin: 0;
font-size: 1.2rem;
}
}
} }
.installations_list { .packages-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -1,5 +1,4 @@
import React from "react" import React from "react"
import BarLoader from "react-spinners/BarLoader"
import { Skeleton } from "antd" import { Skeleton } from "antd"
import { HashRouter, Route, Routes, useNavigate, useParams } from "react-router-dom" import { HashRouter, Route, Routes, useNavigate, useParams } from "react-router-dom"
@ -7,12 +6,14 @@ import loadable from "@loadable/component"
import GlobalStateContext from "contexts/global" import GlobalStateContext from "contexts/global"
import SplashScreen from "components/Splash"
const DefaultNotFoundRender = () => { const DefaultNotFoundRender = () => {
return <div>Not found</div> return <div>Not found</div>
} }
const DefaultLoadingRender = () => { const DefaultLoadingRender = () => {
return <Skeleton active /> return <SplashScreen />
} }
const BuildPageController = (route, element, bindProps) => { const BuildPageController = (route, element, bindProps) => {
@ -155,19 +156,8 @@ export const PageRender = (props) => {
const globalState = React.useContext(GlobalStateContext) const globalState = React.useContext(GlobalStateContext)
if (globalState.setup_step || globalState.loading) { if (globalState.initializing) {
return <div className="app_setup"> return <SplashScreen />
<BarLoader
className="app_loader"
color={getRootCssVar("--primary-color")}
/>
<h1>Setting up...</h1>
<code>
<pre>{globalState.setup_step}</pre>
</code>
</div>
} }
return <Routes> return <Routes>

View File

@ -41,6 +41,12 @@ body {
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
&.initializing {
.app_header {
height: 0px;
}
}
} }
.app_layout { .app_layout {