mirror of
https://github.com/sern-handler/gui
synced 2026-06-06 01:16:54 +00:00
feat: logs file and process end status
This commit is contained in:
@@ -13,7 +13,6 @@ module.exports = {
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: true,
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
plugins: ['react-refresh'],
|
||||
|
||||
@@ -19,4 +19,6 @@ electron/index.ts
|
||||
|
||||
# CI builds?
|
||||
|
||||
Builds will be set up using a jetbrains teamcity instance when migration to new server is done. ETA still not known. Packaged binaries are built on my host computer for now. If you're skeptical, build the app for yourself.
|
||||
CI builds are a WIP. We have working Linux and Windows CircleCI workflows in place, but build artifacts are still not implemented.
|
||||
The project is in a very early stage and currently I build binaries on my host computer.
|
||||
I know this is annoying, but you should build yourself the app (which you can do by following the last question)
|
||||
@@ -1,8 +1,10 @@
|
||||
import path from 'node:path'
|
||||
import { app, BrowserWindow, dialog, ipcMain } from 'electron';
|
||||
import isDev from 'electron-is-dev';
|
||||
import colorette from 'colorette';
|
||||
import { exec } from 'node:child_process';
|
||||
import * as colorette from 'colorette';
|
||||
import * as fs from 'node:fs'
|
||||
import * as os from 'node:os'
|
||||
import { exec, spawn } from 'node:child_process';
|
||||
|
||||
function createWindow() {
|
||||
const mainWindow = new BrowserWindow({
|
||||
@@ -46,10 +48,11 @@ function createWindow() {
|
||||
});
|
||||
|
||||
ipcMain.on('submitForm', async (event, data) => {
|
||||
const fileName = createRandomFileName('txt')
|
||||
// Process the submitted data here
|
||||
console.log(`${colorette.green('✓')} Received sern init submit form data:`);
|
||||
console.log(data)
|
||||
console.log(`${colorette.cyan('🛈')} Current OS: ${currentOS}`);
|
||||
writeLineToLogAndConsole(`${colorette.green('✓')} Received sern init submit form data:`, fileName);
|
||||
writeLineToLogAndConsole(JSON.stringify(data), fileName)
|
||||
writeLineToLogAndConsole(`${colorette.cyan('🛈')} Current OS: ${currentOS}`, fileName);
|
||||
|
||||
let packageManagerCommand: string
|
||||
switch (data.chosenPackageManager) {
|
||||
@@ -83,22 +86,28 @@ function createWindow() {
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(`${colorette.cyan('🛈')} About to execute command: ${commandToRun}`);
|
||||
writeLineToLogAndConsole(`${colorette.cyan('🛈')} About to execute command: ${commandToRun}`, fileName);
|
||||
const cmd = exec(commandToRun)
|
||||
|
||||
cmd.stdout!.on('data', (data) => {
|
||||
console.log(`${colorette.cyan('🛈')} ${data}`);
|
||||
writeLineToLogAndConsole(`${colorette.cyan('🛈')} ${data}`, fileName);
|
||||
});
|
||||
|
||||
cmd.stderr!.on('data', (data) => {
|
||||
console.error(`${colorette.red('×')} stderr: ${data}`);
|
||||
writeLineToLogAndConsole(`${colorette.red('×')} stderr: ${data}`, fileName);
|
||||
});
|
||||
|
||||
cmd.on('close', (code) => {
|
||||
console.log(`${colorette.cyan('🛈')} Command exited with status code ${code}. Now notifying frontend...`);
|
||||
event.reply('submitForm')
|
||||
writeLineToLogAndConsole(`${colorette.cyan('🛈')} Command exited with status code ${code}. Now notifying frontend...`, fileName);
|
||||
event.reply('submitForm', { exitCode: code, logFileName: fileName })
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on('openTxtFile', (event, args) => {
|
||||
console.log('heya', args)
|
||||
openTempTextFile(args)
|
||||
event.reply('openTxtFile')
|
||||
})
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow);
|
||||
@@ -149,4 +158,39 @@ const asciiart = ` .:-=-:.
|
||||
.:=*#################*=-.
|
||||
:=+#########+=:
|
||||
`
|
||||
console.log(asciiart)
|
||||
console.log(asciiart)
|
||||
|
||||
// from now on will be functions that are used in the above code
|
||||
|
||||
function createRandomFileName(extension: string) {
|
||||
return `sern-gui-${randomstring(8)}.${extension}`
|
||||
}
|
||||
|
||||
function writeLineToLogAndConsole(text: string, logfilename: string) {
|
||||
console.log(text)
|
||||
fs.appendFileSync(`${os.tmpdir()}/${logfilename}`, `\n${text}`, { encoding: 'utf-8' })
|
||||
}
|
||||
|
||||
function openTempTextFile(filename: string) {
|
||||
const completeFilename = `${os.tmpdir()}/${filename}`
|
||||
switch (currentOS) {
|
||||
case 'macOS':
|
||||
return spawn('open', [completeFilename])
|
||||
case 'windows':
|
||||
return spawn('notepad', [completeFilename])
|
||||
case 'linux':
|
||||
return spawn('xdg-open', [completeFilename])
|
||||
}
|
||||
}
|
||||
|
||||
function randomstring(length: number) {
|
||||
let result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
const charactersLength = characters.length;
|
||||
let counter = 0;
|
||||
while (counter < length) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
counter += 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -8,34 +8,33 @@ import './Footer.css'
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<div className="footer">
|
||||
<Typography color="primary">
|
||||
<Link href="https://sern.dev">
|
||||
<PublicIcon color="primary" sx={{ fontSize: 'inherit', verticalAlign: 'middle', marginRight: '4px' }} />
|
||||
<Typography variant="body1" component="span" sx={{ display: 'inline-block', verticalAlign: 'middle' }}>
|
||||
front page
|
||||
</Typography>
|
||||
</Link>
|
||||
|
||||
<span style={{ margin: '0 4px' }}>•</span>
|
||||
|
||||
<Link href="https://github.com/sern-handler">
|
||||
<GitHubIcon color="primary" sx={{ fontSize: 'inherit', verticalAlign: 'middle', marginRight: '4px' }} />
|
||||
<Typography variant="body1" component="span" sx={{ display: 'inline-block', verticalAlign: 'middle' }}>
|
||||
github
|
||||
</Typography>
|
||||
</Link>
|
||||
|
||||
<span style={{ margin: '0 4px' }}>•</span>
|
||||
|
||||
<Link href="https://sern.dev/discord">
|
||||
<FontAwesomeIcon icon={faDiscord} style={{ fontSize: 'inherit', verticalAlign: 'middle', marginRight: '4px' }} />
|
||||
<Typography variant="body1" component="span" sx={{ display: 'inline-block', verticalAlign: 'middle' }}>
|
||||
discord
|
||||
</Typography>
|
||||
</Link>
|
||||
</Typography>
|
||||
<div className="footer">
|
||||
<Typography color="primary">
|
||||
<Link href="https://sern.dev">
|
||||
<PublicIcon color="primary" sx={{ fontSize: 'inherit', verticalAlign: 'middle', marginRight: '4px' }} />
|
||||
<Typography variant="body1" component="span" sx={{ display: 'inline-block', verticalAlign: 'middle' }}>
|
||||
front page
|
||||
</Typography>
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
<span style={{ margin: '0 4px' }}>•</span>
|
||||
|
||||
<Link href="https://github.com/sern-handler">
|
||||
<GitHubIcon color="primary" sx={{ fontSize: 'inherit', verticalAlign: 'middle', marginRight: '4px' }} />
|
||||
<Typography variant="body1" component="span" sx={{ display: 'inline-block', verticalAlign: 'middle' }}>
|
||||
github
|
||||
</Typography>
|
||||
</Link>
|
||||
|
||||
<span style={{ margin: '0 4px' }}>•</span>
|
||||
|
||||
<Link href="https://sern.dev/discord">
|
||||
<FontAwesomeIcon icon={faDiscord} style={{ fontSize: 'inherit', verticalAlign: 'middle', marginRight: '4px' }} />
|
||||
<Typography variant="body1" component="span" sx={{ display: 'inline-block', verticalAlign: 'middle' }}>
|
||||
discord
|
||||
</Typography>
|
||||
</Link>
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ import Checkbox from '@mui/material/Checkbox';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import type { IpcRendererEvent } from 'electron'
|
||||
import Snackbar from '@mui/material/Snackbar';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import './InitModal.css';
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
@@ -29,9 +33,14 @@ const { ipcRenderer } = window.require('electron');
|
||||
}; */
|
||||
|
||||
export default function InitModal() {
|
||||
const [loadingBecauseItsSettingUp, setLoadingBecauseItsSettingUp] = React.useState(false);
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const handleOpen = () => setOpen(true);
|
||||
const handleClose = () => setOpen(false);
|
||||
const handleClose = () => {
|
||||
if (loadingBecauseItsSettingUp) return;
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const [chosenTemplate, setChosenTemplate] = React.useState('');
|
||||
const handleTemplateChange = (event: SelectChangeEvent) => {
|
||||
@@ -53,7 +62,7 @@ export default function InitModal() {
|
||||
fetch('https://raw.githubusercontent.com/sern-handler/create-bot/main/metadata/templateChoices.json')
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setTemplates(data);
|
||||
setTemplates(data as TemplateList[]);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err.message);
|
||||
@@ -88,6 +97,38 @@ export default function InitModal() {
|
||||
setSelectedPath(selectedPath);
|
||||
};
|
||||
|
||||
const [logFileName, setLogFileName] = React.useState('')
|
||||
|
||||
const handleOpenLogFile = () => {
|
||||
ipcRenderer.send('openTxtFile', logFileName);
|
||||
|
||||
ipcRenderer.on('openTxtFile', (_event, _args) => {
|
||||
ipcRenderer.removeAllListeners('openTxtFile');
|
||||
});
|
||||
}
|
||||
|
||||
const [successSnackbarOpen, setSuccessSnackbarOpen] = React.useState(false)
|
||||
const handleSuccessSnackbarClose = () => setSuccessSnackbarOpen(false);
|
||||
|
||||
const [errorSnackbarOpen, setErrorSnackbarOpen] = React.useState(false)
|
||||
const handleErrorSnackbarClose = () => setErrorSnackbarOpen(false);
|
||||
|
||||
const snackbarAction = (
|
||||
<React.Fragment>
|
||||
<Button color="secondary" size="small" onClick={handleOpenLogFile}>
|
||||
OPEN LOG FILE
|
||||
</Button>
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label="close"
|
||||
color="inherit"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
const isFormValid = () => {
|
||||
@@ -108,13 +149,21 @@ export default function InitModal() {
|
||||
};
|
||||
|
||||
setLoading(true);
|
||||
setLoadingBecauseItsSettingUp(true)
|
||||
|
||||
ipcRenderer.send('submitForm', data);
|
||||
|
||||
ipcRenderer.on('submitForm', () => {
|
||||
ipcRenderer.on('submitForm', (_event, args) => {
|
||||
setLoading(false);
|
||||
setLoadingBecauseItsSettingUp(false);
|
||||
handleClose();
|
||||
ipcRenderer.removeAllListeners('submitForm'); // Remove the listener to avoid memory leaks
|
||||
setLogFileName(args.logFileName)
|
||||
if (args.exitCode === 0) {
|
||||
setSuccessSnackbarOpen(true)
|
||||
} else {
|
||||
setErrorSnackbarOpen(true)
|
||||
}
|
||||
ipcRenderer.removeAllListeners('submitForm');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -210,20 +259,6 @@ export default function InitModal() {
|
||||
{selectedPath ? `Selected directory: ${selectedPath}` : ''}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="formRow">
|
||||
<Typography
|
||||
variant="body1"
|
||||
component="div"
|
||||
sx={{
|
||||
display: 'block',
|
||||
margin: '0 auto',
|
||||
marginTop: '5px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
Do not close the modal while it's loading.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="bottomRight">
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -236,6 +271,16 @@ export default function InitModal() {
|
||||
</div>
|
||||
</Box>
|
||||
</Modal>
|
||||
<Snackbar open={successSnackbarOpen} autoHideDuration={5000} onClose={handleSuccessSnackbarClose} action={snackbarAction}>
|
||||
<Alert onClose={handleSuccessSnackbarClose} severity="success" sx={{ width: '100%' }} action={snackbarAction}>
|
||||
The command was successful!
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<Snackbar open={errorSnackbarOpen} autoHideDuration={5000} onClose={handleErrorSnackbarClose} action={snackbarAction}>
|
||||
<Alert onClose={handleErrorSnackbarClose} severity="error" sx={{ width: '100%' }} action={snackbarAction}>
|
||||
The command was not successful
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom/client';
|
||||
import './main.css';
|
||||
import App from './App';
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
"outDir": "./dist",
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
@@ -21,6 +20,6 @@
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "src/vite-env.d.ts"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user