1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-16 22:50:10 +03:00

F #3951: Add filters to log component (#869)

This commit is contained in:
Sergio Betanzos 2021-02-24 12:09:59 +01:00 committed by GitHub
parent d3db340c91
commit c88ee63e86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 295 additions and 94 deletions

View File

@ -35,11 +35,13 @@ export default makeStyles(theme => ({
overflow: 'hidden',
position: 'relative',
'& img': {
top: 0,
left: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
userSelect: 'none'
},
transition: theme.transitions.create('filter', { duration: '0.2s' }),
filter: ({ isSelected, disableFilterImage }) => {

View File

@ -0,0 +1,108 @@
import React, { memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Paper, Divider } from '@material-ui/core'
import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab'
import { DEBUG_LEVEL } from 'client/constants'
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
border: `1px solid ${theme.palette.divider}`,
flexWrap: 'wrap',
marginBottom: '0.8em'
},
grouped: {
margin: theme.spacing(0.5),
border: 'none',
'&:not(:first-child)': {
borderRadius: theme.shape.borderRadius
},
'&:first-child': {
borderRadius: theme.shape.borderRadius
}
}
}))
const Filters = memo(({ log, filters, setFilters }) => {
const classes = useStyles()
const commands = Object.keys(log)
const handleFilterCommands = (_, filterCommand) => {
setFilters(prev => ({ ...prev, command: filterCommand }))
}
const handleFilterSeverity = (_, filterCommand) => {
setFilters(prev => ({ ...prev, severity: filterCommand }))
}
return (
<Paper elevation={0} className={classes.root}>
{/* SEVERITY FILTER */}
<ToggleButtonGroup
classes={{
grouped: classes.grouped
}}
value={filters.severity}
exclusive
size='small'
onChange={handleFilterSeverity}
>
{Object.values(DEBUG_LEVEL).map(severity => (
<ToggleButton key={severity} value={severity}>
{severity}
</ToggleButton>
))}
</ToggleButtonGroup>
<Divider flexItem orientation="vertical" className={classes.divider} />
{/* COMMANDS FILTER */}
{commands.length > 1 && (
<ToggleButtonGroup
classes={{
grouped: classes.grouped
}}
value={filters.command}
exclusive
size='small'
onChange={handleFilterCommands}
>
{commands?.map(command => (
<ToggleButton key={command} value={command}>
{command}
</ToggleButton>
))}
</ToggleButtonGroup>
)}
</Paper>
)
}, (prev, next) =>
Object.keys(prev.log).length === Object.keys(next.log).length &&
prev.filters.command === next.filters.command &&
prev.filters.severity === next.filters.severity
)
Filters.propTypes = {
filters: PropTypes.shape({
command: PropTypes.string,
severity: PropTypes.string
}),
log: PropTypes.object.isRequired,
setFilters: PropTypes.func
}
Filters.defaultProps = {
filters: {
command: undefined,
severity: undefined
},
log: {},
setFilters: () => undefined
}
Filters.displayName = 'Filters'
export default Filters

View File

@ -1,13 +1,24 @@
import React, { useEffect, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Box } from '@material-ui/core'
import { makeStyles } from '@material-ui/core'
import AutoScrollBox from 'client/components/AutoScrollBox'
import Message from 'client/components/DebugLog/message'
import { DEBUG_LEVEL } from 'client/constants'
import MessageList from 'client/components/DebugLog/messagelist'
import Filters from 'client/components/DebugLog/filters'
import * as LogUtils from 'client/components/DebugLog/utils'
const debugLogStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexFlow: 'column',
height: '100%'
},
containerScroll: {
width: '100%',
flexGrow: 1,
overflow: 'auto',
borderRadius: 5,
backgroundColor: '#1d1f21',
fontSize: '1.1em',
wordBreak: 'break-word',
'&::-webkit-scrollbar': {
@ -23,62 +34,36 @@ const debugLogStyles = makeStyles(theme => ({
}
}))
const DebugLog = memo(({ uuid, socket, logDefault }) => {
const DebugLog = memo(({ uuid, socket, logDefault, title }) => {
const classes = debugLogStyles()
const [log, setLog] = useState(logDefault)
useEffect(() => {
uuid && socket.on((socketData = {}) => {
const { id, data, command, commandId } = socketData
const [filters, setFilters] = useState(() => ({
command: undefined,
severity: undefined
}))
id === uuid && setLog(prev => ({
...prev,
[command]: {
[commandId]: [...(prev?.[command]?.[commandId] ?? []), data]
}
}))
useEffect(() => {
uuid && socket?.on((socketData = {}) => {
socketData.id === uuid &&
setLog(prevLog => LogUtils.concatNewMessageToLog(prevLog, socketData))
})
return () => uuid && socket.off()
return () => uuid && socket?.off()
}, [])
return (
<Box borderRadius={5} bgcolor={'#1d1f21'} width={1} height={1} className={classes.root}>
<AutoScrollBox scrollBehavior="auto">
{Object.entries(log)?.map(([command, entries]) =>
Object.entries(entries)?.map(([commandId, messages]) =>
messages?.map((data, index) => {
const key = `${index}-${command}-${commandId}`
<div className={classes.root}>
{title}
try {
const { timestamp, severity, message } = JSON.parse(data)
const decryptMessage = atob(message)
<Filters log={log} filters={filters} setFilters={setFilters} />
return (
<Message
key={key}
timestamp={timestamp}
severity={severity}
message={decryptMessage}
/>
)
} catch {
const severity = data.includes(DEBUG_LEVEL.ERROR)
? DEBUG_LEVEL.ERROR
: data.includes(DEBUG_LEVEL.INFO)
? DEBUG_LEVEL.INFO
: data.includes(DEBUG_LEVEL.WARN)
? DEBUG_LEVEL.WARN
: DEBUG_LEVEL.DEBUG
return (
<Message key={key} severity={severity} message={data} />
)
}
})
)
)}
</AutoScrollBox>
</Box>
<div className={classes.containerScroll}>
<AutoScrollBox scrollBehavior="auto">
<MessageList log={log} filters={filters} />
</AutoScrollBox>
</div>
</div>
)
}, (prev, next) => prev.uuid === next.uuid)
@ -88,7 +73,11 @@ DebugLog.propTypes = {
on: PropTypes.func.isRequired,
off: PropTypes.func.isRequired
}).isRequired,
logDefault: PropTypes.object
logDefault: PropTypes.object,
title: PropTypes.oneOfType([
PropTypes.element,
PropTypes.string
])
}
DebugLog.defaultProps = {
@ -97,9 +86,12 @@ DebugLog.defaultProps = {
on: () => undefined,
off: () => undefined
},
logDefault: {}
logDefault: {},
title: null
}
DebugLog.displayName = 'DebugLog'
export default DebugLog
export { LogUtils }

View File

@ -0,0 +1,46 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import Message from 'client/components/DebugLog/message'
import { getMessageInfo } from 'client/components/DebugLog/utils'
const MessageList = ({ log = {}, filters = {} }) =>
Object.entries(log)?.map(([command, entries]) => (
// filter by command
(!filters.command || filters.command.includes(command)) && (
Object.entries(entries)?.map(([commandId, messages]) =>
Array.isArray(messages) && messages?.map((data, index) => {
const { severity, ...messageInfo } = getMessageInfo(data)
// filter by severity
if (filters.severity && filters.severity !== severity) return null
const key = `${index}-${command}-${commandId}`
return (
<Message key={key} severity={severity} {...messageInfo} />
)
})
)
)
))
MessageList.propTypes = {
filters: PropTypes.shape({
command: PropTypes.string,
severity: PropTypes.string
}).isRequired,
log: PropTypes.object
}
MessageList.defaultProps = {
filters: {
command: undefined,
severity: undefined
},
log: undefined
}
MessageList.displayName = 'MessageList'
export default MessageList

View File

@ -0,0 +1,55 @@
import { DEBUG_LEVEL } from 'client/constants'
/**
* Returns severity type if message text includes debug level
* @param {string} data - Message text
* @returns {string} Severity type (debug level)
*/
export const getSeverityFromData = data =>
data.includes(DEBUG_LEVEL.ERROR)
? DEBUG_LEVEL.ERROR
: data.includes(DEBUG_LEVEL.INFO)
? DEBUG_LEVEL.INFO
: data.includes(DEBUG_LEVEL.WARN)
? DEBUG_LEVEL.WARN
: DEBUG_LEVEL.DEBUG
/**
* Returns the message information as json
* @param {string} data - Message information data as string
* @returns {object} Message data
*/
export const getMessageInfo = (data = '') => {
try {
const { message, timestamp, severity } = JSON.parse(data)
const decryptMessage = atob(message)
return { timestamp, severity, message: decryptMessage }
} catch {
const severity = getSeverityFromData(data)
return { severity, message: data }
}
}
/**
* Returns a new log with a new message concatenated
* @param {array} log - Current log data
* @param {object} message - New message to concat
* @param {string} message.command - Message's command: create, configure, etc
* @param {string} message.commandId - Message's command id
* @param {string} message.data - Message's information data
* @returns {array} New log
*/
export const concatNewMessageToLog = (log, message = {}) => {
if (typeof message !== 'object') return log
const { data, command, commandId } = message
return {
...log,
[command]: {
[commandId]: [...(log?.[command]?.[commandId] ?? []), data]
}
}
}

View File

@ -4,44 +4,39 @@ import PropTypes from 'prop-types'
import { LinearProgress } from '@material-ui/core'
import { useProvision, useFetch, useSocket } from 'client/hooks'
import DebugLog from 'client/components/DebugLog'
import DebugLog, { LogUtils } from 'client/components/DebugLog'
import * as Types from 'client/types/provision'
const Log = React.memo(({ hidden, data: { ID } }) => {
const Log = React.memo(({ hidden, data: { ID: id } }) => {
const { getProvision } = useSocket()
const { getProvisionLog } = useProvision()
const { data: provisionLog, fetchRequest, loading } = useFetch(getProvisionLog)
const {
data: { uuid = id, log } = {},
fetchRequest,
loading
} = useFetch(getProvisionLog)
React.useEffect(() => {
!hidden && fetchRequest({ id: ID })
}, [ID])
!hidden && fetchRequest({ id })
}, [id])
React.useEffect(() => {
(!provisionLog && !hidden) && fetchRequest({ id: ID })
(!log && !hidden) && fetchRequest({ id })
}, [hidden])
const log = provisionLog?.log?.reduce((res, dataLog) => {
try {
const json = JSON.parse(dataLog)
const { data, command, commandId } = json
return {
...res,
[command]: {
[commandId]: [...(res?.[command]?.[commandId] ?? []), data]
}
}
} catch { return res }
}, {})
const parsedLog = React.useMemo(() =>
log
?.map(entry => {
try { return JSON.parse(entry) } catch { return entry }
})
?.reduce(LogUtils.concatNewMessageToLog, {})
, [loading])
return loading ? (
<LinearProgress color='secondary' style={{ width: '100%' }} />
) : (
<DebugLog
uuid={provisionLog?.uuid ?? ID}
socket={getProvision}
logDefault={log}
/>
<DebugLog uuid={uuid} socket={getProvision} logDefault={parsedLog} />
)
}, (prev, next) =>
prev.hidden === next.hidden && prev.data === next.data)

View File

@ -8,18 +8,18 @@ import { yupResolver } from '@hookform/resolvers'
import FormStepper from 'client/components/FormStepper'
import Steps from 'client/containers/Provisions/Form/Create/Steps'
import FormCreateStyles from 'client/containers/Provisions/Form/Create/styles'
import formCreateStyles from 'client/containers/Provisions/Form/Create/styles'
import DebugLog from 'client/components/DebugLog'
import { useProvision, useSocket, useFetch } from 'client/hooks'
import { PATH } from 'client/router/provision'
import { set, mapUserInputs } from 'client/utils'
import { Tr, Translate } from 'client/components/HOC'
import { Translate } from 'client/components/HOC'
import { T } from 'client/constants'
function ProvisionCreateForm () {
const classes = FormCreateStyles()
const classes = formCreateStyles()
const history = useHistory()
const [uuid, setUuid] = useState(undefined)
@ -71,19 +71,22 @@ function ProvisionCreateForm () {
if (uuid) {
return (
<div className={classes.rootLog}>
<div className={classes.titleWrapper}>
<IconButton aria-label='back-to-list' size='medium'
onClick={() => history.push(PATH.PROVISIONS.LIST)}
>
<ArrowBackIcon fontSize='large' />
</IconButton>
<span className={classes.titleText}>
<Translate word={T.BackToList} values={T.Provisions} />
</span>
</div>
<DebugLog uuid={uuid} socket={getProvision} />
</div>
<DebugLog
uuid={uuid}
socket={getProvision}
title={(
<div className={classes.titleWrapper}>
<IconButton aria-label='back-to-list' size='medium'
onClick={() => history.push(PATH.PROVISIONS.LIST)}
>
<ArrowBackIcon fontSize='large' />
</IconButton>
<span className={classes.titleText}>
<Translate word={T.BackToList} values={T.Provisions} />
</span>
</div>
)}
/>
)
}