feat: logs file and process end status

This commit is contained in:
2023-07-21 13:51:48 +02:00
parent 6d6e8f0d5a
commit 5faf101a21
7 changed files with 152 additions and 64 deletions

View File

@@ -13,7 +13,6 @@ module.exports = {
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: true,
tsconfigRootDir: __dirname,
},
plugins: ['react-refresh'],

View File

@@ -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)

View File

@@ -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);
@@ -150,3 +159,38 @@ const 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;
}

View File

@@ -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
<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>
</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>
</div>
);
}

View File

@@ -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>
);
}

View File

@@ -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';

View File

@@ -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" }]
}