Show realtime debug log in WebUI #71

This commit is contained in:
proddy
2021-06-16 14:54:36 +02:00
parent fc2bcd50ca
commit 19b37d9e0e
15 changed files with 320 additions and 82 deletions

View File

@@ -38,10 +38,10 @@ const useStyles = makeStyles((theme: Theme) => ({
whiteSpace: 'nowrap'
},
debug: {
color: '#0000ff'
color: '#00FFFF'
},
info: {
color: '#00ff00'
color: '#ffff00'
},
notice: {
color: '#ffff00'
@@ -67,7 +67,8 @@ const LogEventConsole: FC<LogEventConsoleProps> = (props) => {
return classes.info;
case LogLevel.NOTICE:
return classes.notice;
case LogLevel.ERR:
case LogLevel.WARNING:
case LogLevel.ERROR:
return classes.err;
default:
return classes.unknown;
@@ -80,10 +81,14 @@ const LogEventConsole: FC<LogEventConsoleProps> = (props) => {
return 'DEBUG';
case LogLevel.INFO:
return 'INFO';
case LogLevel.ERR:
return 'ERR';
case LogLevel.ERROR:
return 'ERROR';
case LogLevel.NOTICE:
return 'NOTICE';
case LogLevel.WARNING:
return 'WARNING';
case LogLevel.TRACE:
return 'TRACE';
default:
return '?';
}
@@ -91,17 +96,23 @@ const LogEventConsole: FC<LogEventConsoleProps> = (props) => {
const paddedLevelLabel = (level: LogLevel) => {
const label = levelLabel(level);
return label.padStart(7, '\xa0');
return label.padStart(8, '\xa0');
};
const paddedNameLabel = (name: string) => {
const label = '[' + name + ']';
return label.padStart(8, '\xa0');
};
return (
<Box className={classes.console}>
<Box id="log-window" className={classes.console}>
{events.map((e) => (
<div className={classes.entry}>
<span>{e.time} </span>
<span>{e.time}</span>
<span className={styleLevel(e.level)}>
{paddedLevelLabel(e.level)}{' '}
</span>
<span>{paddedNameLabel(e.name)} </span>
<span>{e.message}</span>
</div>
))}

View File

@@ -1,23 +1,26 @@
import { Component } from 'react';
import { FormActions, FormButton } from '../components';
import { createStyles, WithStyles, Theme } from '@material-ui/core';
import {
createStyles,
WithStyles,
withStyles,
Typography,
Theme,
Paper
} from '@material-ui/core';
restController,
RestControllerProps,
RestFormLoader,
SectionContent
} from '../components';
import { LogEvent } from './types';
import { EVENT_SOURCE_ROOT } from '../api/Env';
import LogEventConsole from './LogEventConsole';
import { addAccessTokenParameter } from '../authentication';
import SaveIcon from '@material-ui/icons/Save';
import { ENDPOINT_ROOT, EVENT_SOURCE_ROOT } from '../api';
export const FETCH_LOG_ENDPOINT = ENDPOINT_ROOT + 'fetchLog';
export const LOG_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'logSettings';
const LOG_EVENT_EVENT_SOURCE_URL = EVENT_SOURCE_ROOT + 'log';
export const LOG_EVENT_EVENT_SOURCE_URL = EVENT_SOURCE_ROOT + 'log';
import LogEventForm from './LogEventForm';
import LogEventConsole from './LogEventConsole';
import { LogEvent, LogSettings } from './types';
interface LogEventControllerState {
eventSource?: EventSource;
@@ -32,7 +35,8 @@ const styles = (theme: Theme) =>
}
});
type LogEventControllerProps = WithStyles<typeof styles>;
type LogEventControllerProps = RestControllerProps<LogSettings> &
WithStyles<typeof styles>;
class LogEventController extends Component<
LogEventControllerProps,
@@ -49,6 +53,8 @@ class LogEventController extends Component<
}
componentDidMount() {
this.props.loadData();
this.fetchLog();
this.configureEventSource();
}
@@ -61,6 +67,24 @@ class LogEventController extends Component<
}
}
fetchLog = () => {
fetch(FETCH_LOG_ENDPOINT)
.then((response) => {
if (response.status === 200) {
return response.json();
} else {
throw Error('Unexpected status code: ' + response.status);
}
})
.then((json) => {
this.setState({ events: json.events });
})
.catch((error) => {
this.setState({ events: [] });
throw Error('Unexpected error: ' + error);
});
};
configureEventSource = () => {
this.eventSource = new EventSource(
addAccessTokenParameter(LOG_EVENT_EVENT_SOURCE_URL)
@@ -86,24 +110,16 @@ class LogEventController extends Component<
};
render() {
const { classes } = this.props;
return (
<Paper id="log-window" className={classes.content}>
<Typography variant="h6">System Log</Typography>
<FormActions>
<FormButton
startIcon={<SaveIcon />}
variant="contained"
color="secondary"
// onClick={this.requestNetworkScan}
>
Save
</FormButton>
</FormActions>
<SectionContent title="System Log" id="log-window">
<RestFormLoader
{...this.props}
render={(formProps) => <LogEventForm {...formProps} />}
/>
<LogEventConsole events={this.state.events} />
</Paper>
</SectionContent>
);
}
}
export default withStyles(styles)(LogEventController);
export default restController(LOG_SETTINGS_ENDPOINT, LogEventController);

View File

@@ -0,0 +1,87 @@
import { Component } from 'react';
import {
ValidatorForm,
SelectValidator
} from 'react-material-ui-form-validator';
import MenuItem from '@material-ui/core/MenuItem';
import {
redirectingAuthorizedFetch,
withAuthenticatedContext,
AuthenticatedContextProps
} from '../authentication';
import { RestFormProps } from '../components';
import { LogSettings } from './types';
import { ENDPOINT_ROOT } from '../api';
export const LOG_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'logSettings';
type LogEventFormProps = AuthenticatedContextProps & RestFormProps<LogSettings>;
class LogEventForm extends Component<LogEventFormProps> {
changeLevel = (event: React.ChangeEvent<HTMLSelectElement>) => {
const { data, setData } = this.props;
setData({
...data,
level: parseInt(event.target.value)
});
redirectingAuthorizedFetch(LOG_SETTINGS_ENDPOINT, {
method: 'POST',
body: JSON.stringify({ level: event.target.value }),
headers: {
'Content-Type': 'application/json'
}
})
.then((response) => {
if (response.status === 200) {
return response.json();
}
throw Error('Unexpected response code: ' + response.status);
})
.then((json) => {
this.props.enqueueSnackbar('Log settings changed', {
variant: 'success'
});
setData({
...data,
level: json.level
});
})
.catch((error) => {
this.props.enqueueSnackbar(
error.message || 'Problem changing log settings',
{ variant: 'warning' }
);
});
};
render() {
const { data, saveData } = this.props;
return (
<ValidatorForm onSubmit={saveData}>
<SelectValidator
name="level"
label="Log Level"
value={data.level}
variant="outlined"
onChange={this.changeLevel}
margin="normal"
>
<MenuItem value={-1}>OFF</MenuItem>
<MenuItem value={3}>ERROR</MenuItem>
<MenuItem value={4}>WARNING</MenuItem>
<MenuItem value={5}>NOTICE</MenuItem>
<MenuItem value={6}>INFO</MenuItem>
<MenuItem value={7}>DEBUG</MenuItem>
<MenuItem value={8}>TRACE</MenuItem>
</SelectValidator>
</ValidatorForm>
);
}
}
export default withAuthenticatedContext(LogEventForm);

View File

@@ -1,4 +1,4 @@
import React, { Component } from 'react';
import { Component } from 'react';
import {
restController,

View File

@@ -11,6 +11,7 @@ import {
FormButton,
FormActions
} from '../components';
import { isIP, isHostname, or } from '../validators';
import { OTASettings } from './types';

View File

@@ -38,14 +38,21 @@ export interface OTASettings {
}
export enum LogLevel {
ERR = 3,
ERROR = 3,
WARNING = 4,
NOTICE = 5,
INFO = 6,
DEBUG = 7
DEBUG = 7,
TRACE = 8
}
export interface LogEvent {
time: string;
level: LogLevel;
name: string;
message: string;
}
export interface LogSettings {
level: LogLevel;
}