mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 08:19:52 +03:00
Show realtime debug log in WebUI #71
This commit is contained in:
@@ -15,13 +15,14 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
interface SectionContentProps {
|
||||
title: string;
|
||||
titleGutter?: boolean;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const SectionContent: React.FC<SectionContentProps> = (props) => {
|
||||
const { children, title, titleGutter } = props;
|
||||
const { children, title, titleGutter, id } = props;
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Paper className={classes.content}>
|
||||
<Paper id={id} className={classes.content}>
|
||||
<Typography variant="h6" gutterBottom={titleGutter}>
|
||||
{title}
|
||||
</Typography>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
RestFormLoader,
|
||||
SectionContent
|
||||
} from '../components';
|
||||
|
||||
import { ENDPOINT_ROOT } from '../api';
|
||||
import EMSESPDevicesForm from './EMSESPDevicesForm';
|
||||
import { EMSESPDevices } from './EMSESPtypes';
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
withAuthenticatedContext,
|
||||
AuthenticatedContextProps
|
||||
} from '../authentication';
|
||||
|
||||
import { RestFormProps, FormButton, extractEventValue } from '../components';
|
||||
|
||||
import {
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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);
|
||||
|
||||
87
interface/src/system/LogEventForm.tsx
Normal file
87
interface/src/system/LogEventForm.tsx
Normal 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);
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Component } from 'react';
|
||||
|
||||
import {
|
||||
restController,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
FormButton,
|
||||
FormActions
|
||||
} from '../components';
|
||||
|
||||
import { isIP, isHostname, or } from '../validators';
|
||||
|
||||
import { OTASettings } from './types';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user