From bfaea09c0b38b0064dcc4776e045ba6f4d2d5f8d Mon Sep 17 00:00:00 2001 From: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> Date: Thu, 3 Mar 2022 15:18:19 -0800 Subject: [PATCH] Logs Re-Design (#1656) --- portal-ui/src/icons/BoxArrowDown.tsx | 53 ++++ portal-ui/src/icons/BoxArrowUp.tsx | 57 ++++ portal-ui/src/icons/WarnFilledIcon.tsx | 59 +++++ .../Console/Logs/ErrorLogs/ErrorLogs.tsx | 244 +++--------------- .../Console/Logs/ErrorLogs/LogLine.tsx | 232 +++++++++++++++++ .../src/screens/Console/Logs/reducers.ts | 22 +- 6 files changed, 465 insertions(+), 202 deletions(-) create mode 100644 portal-ui/src/icons/BoxArrowDown.tsx create mode 100644 portal-ui/src/icons/BoxArrowUp.tsx create mode 100644 portal-ui/src/icons/WarnFilledIcon.tsx create mode 100644 portal-ui/src/screens/Console/Logs/ErrorLogs/LogLine.tsx diff --git a/portal-ui/src/icons/BoxArrowDown.tsx b/portal-ui/src/icons/BoxArrowDown.tsx new file mode 100644 index 000000000..dcaf77f2e --- /dev/null +++ b/portal-ui/src/icons/BoxArrowDown.tsx @@ -0,0 +1,53 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import * as React from "react"; +import { SVGProps } from "react"; + +const BoxArrowDown = (props: SVGProps) => ( + + + + + + + + +); + +export default BoxArrowDown; diff --git a/portal-ui/src/icons/BoxArrowUp.tsx b/portal-ui/src/icons/BoxArrowUp.tsx new file mode 100644 index 000000000..faaa36a5a --- /dev/null +++ b/portal-ui/src/icons/BoxArrowUp.tsx @@ -0,0 +1,57 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import * as React from "react"; +import { SVGProps } from "react"; + +const BoxArrowUp = (props: SVGProps) => ( + + + + + + + + +); + +export default BoxArrowUp; diff --git a/portal-ui/src/icons/WarnFilledIcon.tsx b/portal-ui/src/icons/WarnFilledIcon.tsx new file mode 100644 index 000000000..8762d2559 --- /dev/null +++ b/portal-ui/src/icons/WarnFilledIcon.tsx @@ -0,0 +1,59 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import * as React from "react"; +import { SVGProps } from "react"; + +const WarnFilledIcon = (props: SVGProps) => { + return ( + + + + + + + + + + + + + ); +}; + +export default WarnFilledIcon; diff --git a/portal-ui/src/screens/Console/Logs/ErrorLogs/ErrorLogs.tsx b/portal-ui/src/screens/Console/Logs/ErrorLogs/ErrorLogs.tsx index f55076d5a..1a8400feb 100644 --- a/portal-ui/src/screens/Console/Logs/ErrorLogs/ErrorLogs.tsx +++ b/portal-ui/src/screens/Console/Logs/ErrorLogs/ErrorLogs.tsx @@ -24,7 +24,6 @@ import moment from "moment/moment"; import { AppState } from "../../../../store"; import { logMessageReceived, logResetMessages } from "../actions"; import { LogMessage } from "../types"; -import { timeFromDate } from "../../../../common/utils"; import { wsProtocol } from "../../../../utils/wsUtils"; import { actionsTray, @@ -35,6 +34,11 @@ import { import PageHeader from "../../Common/PageHeader/PageHeader"; import PageLayout from "../../Common/Layout/PageLayout"; import SearchBox from "../../Common/SearchBox"; +import Paper from "@mui/material/Paper"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableContainer from "@mui/material/TableContainer"; +import LogLine from "./LogLine"; const styles = (theme: Theme) => createStyles({ @@ -44,7 +48,6 @@ const styles = (theme: Theme) => height: "calc(100vh - 280px)", overflow: "auto", fontSize: 13, - padding: "15px 15px 0", border: "1px solid #EAEDEE", borderRadius: 4, }, @@ -85,7 +88,7 @@ const ErrorLogs = ({ logResetMessages, messages, }: ILogs) => { - const [highlight, setHighlight] = useState(""); + const [filter, setFilter] = useState(""); useEffect(() => { logResetMessages(); @@ -128,218 +131,57 @@ const ErrorLogs = ({ } }, [logMessageReceived, logResetMessages]); - const renderError = (logElement: LogMessage) => { - let errorElems = []; - if (logElement.error !== null && logElement.error !== undefined) { - if (logElement.api && logElement.api.name) { - const errorText = `API: ${logElement.api.name}`; - - const highlightedLine = - highlight !== "" - ? errorText.toLowerCase().includes(highlight.toLowerCase()) - : false; - - errorElems.push( -
-
- {errorText} -
- ); - } - if (logElement.time) { - const errorText = `Time: ${timeFromDate(logElement.time)}`; - const highlightedLine = - highlight !== "" - ? errorText.toLowerCase().includes(highlight.toLowerCase()) - : false; - errorElems.push( -
- {errorText} -
- ); - } - if (logElement.deploymentid) { - const errorText = `DeploymentID: ${logElement.deploymentid}`; - const highlightedLine = - highlight !== "" - ? errorText.toLowerCase().includes(highlight.toLowerCase()) - : false; - errorElems.push( -
- {errorText} -
- ); - } - if (logElement.requestID) { - const errorText = `RequestID: ${logElement.requestID}`; - const highlightedLine = - highlight !== "" - ? errorText.toLowerCase().includes(highlight.toLowerCase()) - : false; - errorElems.push( -
- {errorText} -
- ); - } - if (logElement.remotehost) { - const errorText = `RemoteHost: ${logElement.remotehost}`; - const highlightedLine = - highlight !== "" - ? errorText.toLowerCase().includes(highlight.toLowerCase()) - : false; - errorElems.push( -
- {errorText} -
- ); - } - if (logElement.host) { - const errorText = `Host: ${logElement.host}`; - const highlightedLine = - highlight !== "" - ? errorText.toLowerCase().includes(highlight.toLowerCase()) - : false; - errorElems.push( -
- {errorText} -
- ); - } - if (logElement.userAgent) { - const errorText = `UserAgent: ${logElement.userAgent}`; - const highlightedLine = - highlight !== "" - ? errorText.toLowerCase().includes(highlight.toLowerCase()) - : false; - errorElems.push( -
- {errorText} -
- ); - } - if (logElement.error.message) { - const errorText = `Error: ${logElement.error.message}`; - const highlightedLine = - highlight !== "" - ? errorText.toLowerCase().includes(highlight.toLowerCase()) - : false; - errorElems.push( -
- {errorText} -
- ); - } - if (logElement.error.source) { - // for all sources add padding - for (let s in logElement.error.source) { - const errorText = logElement.error.source[s]; - const highlightedLine = - highlight !== "" - ? errorText.toLowerCase().includes(highlight.toLowerCase()) - : false; - errorElems.push( -
- {errorText} -
- ); - } + const filtLow = filter.toLowerCase(); + let filteredMessages = messages.filter((m) => { + if (filter !== "") { + if (m.ConsoleMsg.toLowerCase().indexOf(filtLow) >= 0) { + return true; + } else if ( + m.error && + m.error.source && + m.error.source.filter((x) => { + return x.toLowerCase().indexOf(filtLow) >= 0; + }).length > 0 + ) { + return true; + } else if ( + m.error && + m.error.message.toLowerCase().indexOf(filtLow) >= 0 + ) { + return true; + } else if (m.api && m.api.name.toLowerCase().indexOf(filtLow) >= 0) { + return true; } + return false; } - return errorElems; - }; - - const renderLog = (logElement: LogMessage) => { - let logMessage = logElement.ConsoleMsg; - // remove any non ascii characters, exclude any control codes - logMessage = logMessage.replace(/([^\x20-\x7F])/g, ""); - - // regex for terminal colors like e.g. `[31;4m ` - const tColorRegex = /((\[[0-9;]+m))/g; - - // get substring if there was a match for to split what - // is going to be colored and what not, here we add color - // only to the first match. - let substr = logMessage.replace(tColorRegex, ""); - - // in case highlight is set, we select the line that contains the requested string - let highlightedLine = - highlight !== "" - ? logMessage.toLowerCase().includes(highlight.toLowerCase()) - : false; - - // if starts with multiple spaces add padding - if (substr.startsWith(" ")) { - return ( -
- {substr} -
- ); - } else if (logElement.error !== null && logElement.error !== undefined) { - // list error message and all sources and error elems - return renderError(logElement); - } else { - // for all remaining set default class - return ( -
- {substr} -
- ); - } - }; - - const renderLines = messages.map((m) => { - return renderLog(m); + return true; }); return ( - + { + setFilter(e); + }} + value={filter} />
- {renderLines} + + + + {filteredMessages.map((m) => { + return ; + })} + +
+
diff --git a/portal-ui/src/screens/Console/Logs/ErrorLogs/LogLine.tsx b/portal-ui/src/screens/Console/Logs/ErrorLogs/LogLine.tsx new file mode 100644 index 000000000..0168e5b9d --- /dev/null +++ b/portal-ui/src/screens/Console/Logs/ErrorLogs/LogLine.tsx @@ -0,0 +1,232 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import React, { Fragment, useState } from "react"; +import { LogMessage } from "../types"; +import TableRow from "@mui/material/TableRow"; +import TableCell from "@mui/material/TableCell"; +import Collapse from "@mui/material/Collapse"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Moment from "react-moment"; +import BoxArrowUp from "../../../../icons/BoxArrowUp"; +import BoxArrowDown from "../../../../icons/BoxArrowDown"; +import WarnFilledIcon from "../../../../icons/WarnFilledIcon"; + +const messageForConsoleMsg = (log: LogMessage) => { + // regex for terminal colors like e.g. `[31;4m ` + const tColorRegex = /((\[[0-9;]+m))/g; + + let fullMessage = log.ConsoleMsg; + // remove the 0x1B character + /* eslint-disable no-control-regex */ + fullMessage = fullMessage.replace(/\x1B/g, " "); + /* eslint-enable no-control-regex */ + // get substring if there was a match for to split what + // is going to be colored and what not, here we add color + // only to the first match. + fullMessage = fullMessage.replace(tColorRegex, ""); + return ( +
+
+
{fullMessage}
+
+
+ ); +}; +const messageForError = (log: LogMessage) => { + const dataStyle = { color: "green" }; + return ( + +
+ API:  + {log.api.name} +
+
+ Time:  + {log.time.toString()} +
+
+ DeploymentID:  + {log.deploymentid} +
+
+ RequestID:  + {log.requestID} +
+
+ RemoteHost:  + {log.remotehost} +
+
+ UserAgent:  + {log.userAgent} +
+
+ Error:  + {log.error && log.error.message} +
+
+
+ Backtrace:  +
+ {log.error && + log.error.source.map((e, i) => { + return ( +
+ {i}:  + {e} +
+ ); + })} +
+ ); +}; + +const LogLine = (props: { log: LogMessage }) => { + const { log } = props; + const [open, setOpen] = useState(false); + + let logMessage = ""; + if (log.ConsoleMsg !== "") { + logMessage = log.ConsoleMsg; + } else if (log.error !== null && log.error.message !== "") { + logMessage = log.error.message; + } + // remove any non ascii characters, exclude any control codes + let titleLogMessage = logMessage.replace(/━|┏|┓|┃|┗|┛/g, ""); + // remove any non ascii characters, exclude any control codes + titleLogMessage = titleLogMessage.replace(/([^\x20-\x7F])/g, ""); + + // regex for terminal colors like e.g. `[31;4m ` + const tColorRegex = /((\[[0-9;]+m))/g; + + let fullMessage = ; + if (log.ConsoleMsg !== "") { + fullMessage = messageForConsoleMsg(log); + } else if (log.error !== null && log.error.message !== "") { + fullMessage = messageForError(log); + } + + titleLogMessage = titleLogMessage.replace(tColorRegex, ""); + + let dateStr = {log.time}; + if (log.time.getFullYear() === 1) { + dateStr = n/a; + } + + return ( + + *": { borderBottom: "unset" }, cursor: "pointer" }} + style={{ backgroundColor: "#FDFDFD" }} + > + setOpen(!open)} + style={{ width: 200, color: "#989898", fontSize: 12 }} + > + + + {dateStr} + + + setOpen(!open)}> +
+
+ {titleLogMessage} +
+
+
+ setOpen(!open)} style={{ width: 40 }}> + {open ? : } + +
+ + + +
Log Details
+
+
+ + + + + {fullMessage} + + + + + +
+
+ ); +}; + +export default LogLine; diff --git a/portal-ui/src/screens/Console/Logs/reducers.ts b/portal-ui/src/screens/Console/Logs/reducers.ts index 9066abdfd..29d30c986 100644 --- a/portal-ui/src/screens/Console/Logs/reducers.ts +++ b/portal-ui/src/screens/Console/Logs/reducers.ts @@ -35,9 +35,29 @@ export function logReducer( ): LogState { switch (action.type) { case LOG_MESSAGE_RECEIVED: + // if it's a simple ConsoleMsg, append it to the current ConsoleMsg in the + // state if any + let msgs = [...state.messages]; + + if ( + msgs.length > 0 && + action.message.time.getFullYear() === 1 && + action.message.ConsoleMsg !== "" + ) { + for (let m in msgs) { + if (msgs[m].time.getFullYear() === 1) { + msgs[ + m + ].ConsoleMsg = `${msgs[m].ConsoleMsg}\n${action.message.ConsoleMsg}`; + } + } + } else { + msgs.push(action.message); + } + return { ...state, - messages: [...state.messages, action.message], + messages: msgs, }; case LOG_RESET_MESSAGES: return {