add max messages and make web log dynamic - #71

This commit is contained in:
proddy
2021-07-27 21:44:12 +02:00
parent dc8c322b42
commit e809ed3743
7 changed files with 223 additions and 172 deletions

View File

@@ -6,6 +6,8 @@ import { useWindowSize } from '../components';
interface LogEventConsoleProps {
events: LogEvent[];
compact: boolean;
level: number;
}
interface Offsets {
@@ -63,7 +65,9 @@ const useStyles = makeStyles((theme: Theme) => ({
const LogEventConsole: FC<LogEventConsoleProps> = (props) => {
useWindowSize();
const classes = useStyles({ topOffset, leftOffset });
const { events } = props;
const { events, compact, level } = props;
const filter_events = events.filter((e) => e.l <= level);
const styleLevel = (level: LogLevel) => {
switch (level) {
@@ -103,29 +107,34 @@ const LogEventConsole: FC<LogEventConsoleProps> = (props) => {
}
};
const paddedLevelLabel = (level: LogLevel) => {
const paddedLevelLabel = (level: LogLevel, compact: boolean) => {
const label = levelLabel(level);
return label.padStart(8, '\xa0');
return compact ? ' ' + label[0] : label.padStart(8, '\xa0');
};
const paddedNameLabel = (name: string) => {
const paddedNameLabel = (name: string, compact: boolean) => {
const label = '[' + name + ']';
return label.padEnd(12, '\xa0');
return compact ? label : label.padEnd(12, '\xa0');
};
const paddedIDLabel = (id: number) => {
const paddedIDLabel = (id: number, compact: boolean) => {
const label = id + ':';
return label.padEnd(7, '\xa0');
return compact ? label : label.padEnd(7, '\xa0');
};
return (
<Box id="log-window" className={classes.console}>
{events.map((e) => (
{filter_events.map((e) => (
<div className={classes.entry} key={e.i}>
<span>{e.t}</span>
<span className={styleLevel(e.l)}>{paddedLevelLabel(e.l)} </span>
<span>{paddedIDLabel(e.i)} </span>
<span>{paddedNameLabel(e.n)} </span>
{compact && <span>{paddedLevelLabel(e.l, compact)} </span>}
{!compact && (
<span className={styleLevel(e.l)}>
{paddedLevelLabel(e.l, compact)}{' '}
</span>
)}
<span>{paddedIDLabel(e.i, compact)} </span>
<span>{paddedNameLabel(e.n, compact)} </span>
<span>{e.m}</span>
</div>
))}

View File

@@ -3,19 +3,27 @@ import { Component } from 'react';
import {
restController,
RestControllerProps,
RestFormLoader,
SectionContent
SectionContent,
BlockFormControlLabel
} from '../components';
import { addAccessTokenParameter } from '../authentication';
import {
ValidatorForm,
SelectValidator
} from 'react-material-ui-form-validator';
import { Grid, Slider, FormLabel, Checkbox, MenuItem } from '@material-ui/core';
import {
addAccessTokenParameter,
redirectingAuthorizedFetch
} from '../authentication';
import { ENDPOINT_ROOT, EVENT_SOURCE_ROOT } from '../api';
export const FETCH_LOG_ENDPOINT = ENDPOINT_ROOT + 'fetchLog';
export const LOG_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'logSettings';
export const LOG_EVENT_EVENT_SOURCE_URL = EVENT_SOURCE_ROOT + 'log';
import LogEventForm from './LogEventForm';
import LogEventConsole from './LogEventConsole';
import { LogEvent, LogSettings } from './types';
@@ -26,6 +34,9 @@ const decoder = new Decoder();
interface LogEventControllerState {
eventSource?: EventSource;
events: LogEvent[];
compact: boolean;
level: number;
max_messages: number;
}
type LogEventControllerProps = RestControllerProps<LogSettings>;
@@ -40,12 +51,15 @@ class LogEventController extends Component<
constructor(props: LogEventControllerProps) {
super(props);
this.state = {
events: []
events: [],
compact: false,
level: 6,
max_messages: 25
};
}
componentDidMount() {
this.props.loadData();
this.fetchValues();
this.fetchLog();
this.configureEventSource();
}
@@ -59,6 +73,15 @@ class LogEventController extends Component<
}
}
changeCompact = (
event: React.ChangeEvent<HTMLInputElement>,
checked: boolean
) => {
this.setState({
compact: checked
});
};
fetchLog = () => {
fetch(FETCH_LOG_ENDPOINT)
.then((response) => {
@@ -78,6 +101,25 @@ class LogEventController extends Component<
});
};
fetchValues = () => {
redirectingAuthorizedFetch(LOG_SETTINGS_ENDPOINT)
.then((response) => {
if (response.status === 200) {
return response.json();
}
throw Error('Unexpected status code: ' + response.status);
})
.then((json) => {
this.setState({ level: json.level, max_messages: json.max_messages });
})
.catch((error) => {
const errorMessage = error.message || 'Unknown error';
this.props.enqueueSnackbar('Problem fetching: ' + errorMessage, {
variant: 'error'
});
});
};
configureEventSource = () => {
this.eventSource = new EventSource(
addAccessTokenParameter(LOG_EVENT_EVENT_SOURCE_URL)
@@ -102,14 +144,114 @@ class LogEventController extends Component<
}
};
changeMaxMessages = (
event: React.ChangeEvent<{}>,
value: number | number[]
) => {
this.setState({
max_messages: value as number
});
this.send_data(this.state.level, value as number);
};
changeLevel = (event: React.ChangeEvent<HTMLSelectElement>) => {
this.setState({
level: parseInt(event.target.value)
});
this.send_data(parseInt(event.target.value), this.state.max_messages);
};
send_data = (level: number, max_messages: number) => {
redirectingAuthorizedFetch(LOG_SETTINGS_ENDPOINT, {
method: 'POST',
body: JSON.stringify({
level: level,
max_messages: max_messages
}),
headers: {
'Content-Type': 'application/json'
}
})
.then((response) => {
if (response.status !== 200) {
throw Error('Unexpected response code: ' + response.status);
}
})
.catch((error) => {
this.props.enqueueSnackbar(
error.message || 'Problem applying log settings',
{ variant: 'warning' }
);
});
};
render() {
const { saveData } = this.props;
return (
<SectionContent title="System Log" id="log-window">
<RestFormLoader
{...this.props}
render={(formProps) => <LogEventForm {...formProps} />}
<ValidatorForm onSubmit={saveData}>
<Grid
container
spacing={3}
direction="row"
justify="flex-start"
alignItems="center"
>
<Grid item xs={2}>
<SelectValidator
name="level"
label="Log Level"
value={this.state.level}
fullWidth
variant="outlined"
onChange={this.changeLevel}
margin="normal"
>
<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}>ALL</MenuItem>
</SelectValidator>
</Grid>
<Grid item xs={2}>
<FormLabel>Buffer size</FormLabel>
<Slider
value={this.state.max_messages}
valueLabelDisplay="auto"
name="max_messages"
marks={[
{ value: 25, label: '25' },
{ value: 50, label: '50' },
{ value: 75, label: '75' }
]}
step={25}
min={25}
max={75}
onChange={this.changeMaxMessages}
/>
</Grid>
<Grid item xs={4}>
<BlockFormControlLabel
control={
<Checkbox
checked={this.state.compact}
onChange={this.changeCompact}
value="compact"
/>
}
label="Compact Layout"
/>
</Grid>
</Grid>
</ValidatorForm>
<LogEventConsole
level={this.state.level}
compact={this.state.compact}
events={this.state.events}
/>
<LogEventConsole events={this.state.events} />
</SectionContent>
);
}

View File

@@ -1,107 +0,0 @@
import { Component } from 'react';
import {
ValidatorForm,
SelectValidator
} from 'react-material-ui-form-validator';
import { Typography, Grid } from '@material-ui/core';
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}>
<Grid
container
direction="row"
justify="flex-start"
alignItems="center"
>
<Grid item xs={2}>
<SelectValidator
name="level"
label="Filter on Log Level"
value={data.level}
fullWidth
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}>ALL</MenuItem>
</SelectValidator>
</Grid>
<Grid item md>
<Typography color="primary" variant="body2">
<i>
&nbsp;(the last {data.max_messages} messages are retained and
all new log events are shown in real time below)
</i>
</Typography>
</Grid>
</Grid>
</ValidatorForm>
);
}
}
export default withAuthenticatedContext(LogEventForm);