diff --git a/interface/src/system/LogEventConsole.tsx b/interface/src/system/LogEventConsole.tsx index f3d2cc05a..43a6df419 100644 --- a/interface/src/system/LogEventConsole.tsx +++ b/interface/src/system/LogEventConsole.tsx @@ -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 = (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 = (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 ( - {events.map((e) => ( + {filter_events.map((e) => (
{e.t} - {paddedLevelLabel(e.l)} - {paddedIDLabel(e.i)} - {paddedNameLabel(e.n)} + {compact && {paddedLevelLabel(e.l, compact)} } + {!compact && ( + + {paddedLevelLabel(e.l, compact)}{' '} + + )} + {paddedIDLabel(e.i, compact)} + {paddedNameLabel(e.n, compact)} {e.m}
))} diff --git a/interface/src/system/LogEventController.tsx b/interface/src/system/LogEventController.tsx index cdeffafb1..f34fed1c2 100644 --- a/interface/src/system/LogEventController.tsx +++ b/interface/src/system/LogEventController.tsx @@ -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; @@ -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, + 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) => { + 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 ( - } + + + + + ERROR + WARNING + NOTICE + INFO + DEBUG + ALL + + + + Buffer size + + + + + } + label="Compact Layout" + /> + + + + + - ); } diff --git a/interface/src/system/LogEventForm.tsx b/interface/src/system/LogEventForm.tsx deleted file mode 100644 index 9c69db7ef..000000000 --- a/interface/src/system/LogEventForm.tsx +++ /dev/null @@ -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; - -class LogEventForm extends Component { - changeLevel = (event: React.ChangeEvent) => { - 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 ( - - - - - OFF - ERROR - WARNING - NOTICE - INFO - DEBUG - ALL - - - - - -  (the last {data.max_messages} messages are retained and - all new log events are shown in real time below) - - - - - - ); - } -} - -export default withAuthenticatedContext(LogEventForm); diff --git a/mock-api/server.js b/mock-api/server.js index 27edccf69..46b2600a2 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -18,7 +18,7 @@ const ES_ENDPOINT_ROOT = '/es/' const LOG_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'logSettings' const log_settings = { level: 6, - max_messages: 30, + max_messages: 50, } const FETCH_LOG_ENDPOINT = REST_ENDPOINT_ROOT + 'fetchLog' @@ -770,14 +770,22 @@ app.get(FETCH_LOG_ENDPOINT, (req, res) => { res.end(null, 'binary') }) app.get(LOG_SETTINGS_ENDPOINT, (req, res) => { + console.log( + 'Fetching log settings ' + + log_settings.level + + ',' + + log_settings.max_messages, + ) res.json(log_settings) }) app.post(LOG_SETTINGS_ENDPOINT, (req, res) => { - console.log('New log level is ' + req.body.level) - const data = { - level: req.body.level, - } - res.json(data) + console.log( + 'Setting new level=' + + req.body.level + + ' max_messages=' + + req.body.max_messages, + ) + res.sendStatus(200) }) // NETWORK diff --git a/src/version.h b/src/version.h index 838811571..1dc1c7f43 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.2.0b0" +#define EMSESP_APP_VERSION "3.2.0b1" diff --git a/src/web/WebLogService.cpp b/src/web/WebLogService.cpp index 0ce55aa47..5f4f6d652 100644 --- a/src/web/WebLogService.cpp +++ b/src/web/WebLogService.cpp @@ -23,21 +23,21 @@ using namespace std::placeholders; namespace emsesp { WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * securityManager) - : _events(EVENT_SOURCE_LOG_PATH) - , _setLevel(LOG_SETTINGS_PATH, std::bind(&WebLogService::setLevel, this, _1, _2), 256) { // for POSTS + : events_(EVENT_SOURCE_LOG_PATH) + , setValues_(LOG_SETTINGS_PATH, std::bind(&WebLogService::setValues, this, _1, _2), 256) { // for POSTS - _events.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN)); - server->addHandler(&_events); + events_.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN)); + server->addHandler(&events_); server->on(EVENT_SOURCE_LOG_PATH, HTTP_GET, std::bind(&WebLogService::forbidden, this, _1)); // for bring back the whole log server->on(FETCH_LOG_PATH, HTTP_GET, std::bind(&WebLogService::fetchLog, this, _1)); // get when page is loaded - server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getLevel, this, _1)); + server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getValues, this, _1)); // for setting a level - server->addHandler(&_setLevel); + server->addHandler(&setValues_); } void WebLogService::forbidden(AsyncWebServerRequest * request) { @@ -90,7 +90,7 @@ void WebLogService::operator<<(std::shared_ptr message) { } void WebLogService::loop() { - if (!_events.count() || log_messages_.empty()) { + if (!events_.count() || log_messages_.empty()) { return; } @@ -144,12 +144,12 @@ void WebLogService::transmit(const QueuedLogMessage & message) { char * buffer = new char[len + 1]; if (buffer) { serializeJson(jsonDocument, buffer, len + 1); - _events.send(buffer, "message", millis()); + events_.send(buffer, "message", millis()); } delete[] buffer; } -// send the current log buffer to the API +// send the complete log buffer to the API, filtering on log level void WebLogService::fetchLog(AsyncWebServerRequest * request) { MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN); // 8kb buffer JsonObject root = response->getRoot(); @@ -157,15 +157,17 @@ void WebLogService::fetchLog(AsyncWebServerRequest * request) { JsonArray log = root.createNestedArray("events"); for (const auto & msg : log_messages_) { - JsonObject logEvent = log.createNestedObject(); - auto message = std::move(msg); - char time_string[25]; + if (msg.content_->level <= log_level()) { + JsonObject logEvent = log.createNestedObject(); + auto message = std::move(msg); + char time_string[25]; - logEvent["t"] = messagetime(time_string, message.content_->uptime_ms); - logEvent["l"] = message.content_->level; - logEvent["i"] = message.id_; - logEvent["n"] = message.content_->name; - logEvent["m"] = message.content_->text; + logEvent["t"] = messagetime(time_string, message.content_->uptime_ms); + logEvent["l"] = message.content_->level; + logEvent["i"] = message.id_; + logEvent["n"] = message.content_->name; + logEvent["m"] = message.content_->text; + } } log_message_id_tail_ = log_messages_.back().id_; @@ -174,28 +176,25 @@ void WebLogService::fetchLog(AsyncWebServerRequest * request) { request->send(response); } -// sets the level after a POST -void WebLogService::setLevel(AsyncWebServerRequest * request, JsonVariant & json) { +// sets the values like level after a POST +void WebLogService::setValues(AsyncWebServerRequest * request, JsonVariant & json) { if (not json.is()) { return; } - auto && body = json.as(); + + auto && body = json.as(); + uuid::log::Level level = body["level"]; log_level(level); - if (level == uuid::log::Level::OFF) { - log_messages_.clear(); - } - // send the value back - AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL); - JsonObject root = response->getRoot(); - root["level"] = log_level(); - response->setLength(); - request->send(response); + uint8_t max_messages = body["max_messages"]; + maximum_log_messages(max_messages); + + request->send(200); // OK } -// return the current log level after a GET -void WebLogService::getLevel(AsyncWebServerRequest * request) { +// return the current value settings after a GET +void WebLogService::getValues(AsyncWebServerRequest * request) { AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL); JsonObject root = response->getRoot(); root["level"] = log_level(); diff --git a/src/web/WebLogService.h b/src/web/WebLogService.h index 4c3401a7e..885739e9d 100644 --- a/src/web/WebLogService.h +++ b/src/web/WebLogService.h @@ -34,7 +34,7 @@ namespace emsesp { class WebLogService : public uuid::log::Handler { public: - static constexpr size_t MAX_LOG_MESSAGES = 30; + static constexpr size_t MAX_LOG_MESSAGES = 50; static constexpr size_t REFRESH_SYNC = 200; WebLogService(AsyncWebServer * server, SecurityManager * securityManager); @@ -49,7 +49,7 @@ class WebLogService : public uuid::log::Handler { virtual void operator<<(std::shared_ptr message); private: - AsyncEventSource _events; + AsyncEventSource events_; class QueuedLogMessage { public: @@ -64,12 +64,12 @@ class WebLogService : public uuid::log::Handler { void forbidden(AsyncWebServerRequest * request); void transmit(const QueuedLogMessage & message); void fetchLog(AsyncWebServerRequest * request); - void getLevel(AsyncWebServerRequest * request); + void getValues(AsyncWebServerRequest * request); char * messagetime(char * out, const uint64_t t); - void setLevel(AsyncWebServerRequest * request, JsonVariant & json); - AsyncCallbackJsonWebHandler _setLevel; // for POSTs + void setValues(AsyncWebServerRequest * request, JsonVariant & json); + AsyncCallbackJsonWebHandler setValues_; // for POSTs uint64_t last_transmit_ = 0; // Last transmit time size_t maximum_log_messages_ = MAX_LOG_MESSAGES; // Maximum number of log messages to buffer before they are output