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

F #3951: Update log component & minor styles (#658)

This commit is contained in:
Sergio Betanzos 2021-01-18 17:01:32 +01:00 committed by GitHub
parent b4565b9b78
commit 5c340a5372
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 206 additions and 233 deletions

View File

@ -82,7 +82,6 @@
"fuse.js": "^6.4.1",
"guacamole-lite": "^0.6.3",
"helmet": "^4.1.1",
"html-entities": "^1.3.1",
"http": "^0.0.1-security",
"http-proxy-middleware": "^1.0.5",
"https": "^1.0.0",
@ -130,4 +129,4 @@
"yup": "^0.29.3",
"zeromq": "^5.2.0"
}
}
}

View File

@ -1,11 +1,24 @@
import React, { memo, Children, useEffect, useRef, useState } from 'react'
import React, { memo, useEffect, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
import { makeStyles, Chip, Slide } from '@material-ui/core'
import ArrowBottomIcon from '@material-ui/icons/VerticalAlignBottom'
const useStyles = makeStyles(theme => ({
scrollable: {
padding: theme.spacing(1),
overflowY: 'scroll',
'&::-webkit-scrollbar': {
width: 14
},
'&::-webkit-scrollbar-thumb': {
backgroundClip: 'content-box',
border: '4px solid transparent',
borderRadius: 7,
boxShadow: 'inset 0 0 0 10px',
color: theme.palette.secondary.light
}
},
wrapperButton: {
top: 5,
position: 'sticky',
@ -14,8 +27,6 @@ const useStyles = makeStyles(theme => ({
button: { padding: theme.spacing(0, 2) }
}))
const baseClass = 'react-auto-scroll'
const AutoScrollBox = memo(({
children,
className,
@ -30,9 +41,7 @@ const AutoScrollBox = memo(({
const containerElement = useRef(null)
const style = {
padding: 8,
height,
overflowY: 'scroll',
scrollBehavior: 'auto',
pointerEvents: preventInteraction ? 'none' : 'auto'
}
@ -74,15 +83,9 @@ const AutoScrollBox = memo(({
}, [children, containerElement, autoScroll])
return (
<div style={{ height }}
className={clsx(baseClass, className, {
[`${baseClass}--empty`]: Children.count(children) === 0,
[`${baseClass}--prevent-interaction`]: preventInteraction,
[`${baseClass}--showOption`]: showOption
})}
>
<div style={{ height }} className={className}>
<div
className={`${baseClass}__scroll-container`}
className={classes.scrollable}
onWheel={onWheel}
ref={containerElement}
style={style}

View File

@ -1,48 +1,43 @@
import React, { memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Chip, Box, Typography, Avatar } from '@material-ui/core'
import { makeStyles, Typography } from '@material-ui/core'
import DatastoreIcon from '@material-ui/icons/FolderOpen'
import SelectCard from 'client/components/Cards/SelectCard'
import { StatusBadge } from 'client/components/Status'
import { StatusBadge, StatusChip } from 'client/components/Status'
import Datastore from 'client/constants/datastore'
const useStyles = makeStyles(theme => ({
title: { display: 'flex', gap: '8px' },
avatar: {
width: '5ch',
height: '5ch',
color: theme.palette.primary.contrastText
},
card: { backgroundColor: theme.palette.primary.main }
card: { backgroundColor: theme.palette.primary.main },
title: { display: 'flex', gap: '0.5rem' }
}))
const DatastoreCard = memo(
({ value, isSelected, handleClick, actions }) => {
const classes = useStyles()
const { ID, NAME, TYPE, STATE } = value
const type = Datastore.TYPES[TYPE]
const state = Datastore.STATES[STATE]
const classes = useStyles()
const renderChip = ({ label, ...props }) =>
<Chip size="small" title={label} label={label} {...props} />
return (
<SelectCard
stylesProps={{ minHeight: 160 }}
cardProps={{ className: classes.card, elevation: 2 }}
icon={
<StatusBadge stateColor={state.color} >
<Avatar className={classes.avatar} title={state.name}>{ID}</Avatar>
<StatusBadge stateColor={state.color}>
<DatastoreIcon />
</StatusBadge>
}
title={(
<Box component="span" className={classes.title}>
<Typography noWrap>{NAME}</Typography>
{renderChip({ label: type.name })}
</Box>
)}
cardHeaderProps={{ disableTypography: true }}
title={
<span className={classes.title}>
<Typography title={NAME} noWrap component='span'>
{NAME}
</Typography>
<StatusChip stateColor={'#c6c6c6'}>{type.name}</StatusChip>
</span>
}
subheader={`#${ID}`}
isSelected={isSelected}
handleClick={handleClick}
actions={actions}

View File

@ -1,48 +1,45 @@
import React, { memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Avatar, Chip, Box, Typography } from '@material-ui/core'
import { makeStyles, Chip, Box, Typography } from '@material-ui/core'
import HostIcon from '@material-ui/icons/VideogameAsset'
import SelectCard from 'client/components/Cards/SelectCard'
import { StatusBadge } from 'client/components/Status'
import { StatusBadge, StatusChip } from 'client/components/Status'
import Host from 'client/constants/host'
const useStyles = makeStyles(theme => ({
title: { display: 'flex', gap: '8px' },
avatar: {
width: '5ch',
height: '5ch',
color: theme.palette.primary.contrastText
},
card: { backgroundColor: theme.palette.primary.main }
card: { backgroundColor: theme.palette.primary.main },
title: { display: 'flex', gap: '0.5rem' }
}))
const HostCard = memo(
({ value, isSelected, handleClick, actions }) => {
const classes = useStyles()
const { ID, NAME, STATE, IM_MAD: imMad, VM_MAD: vmMad } = value
const state = Host.STATES[STATE]
const mad = imMad === vmMad ? imMad : `${imMad}/${vmMad}`
const classes = useStyles()
const renderChip = ({ label, ...props }) =>
<Chip size="small" title={label} label={label} {...props} />
return (
<SelectCard
stylesProps={{ minHeight: 160 }}
cardProps={{ className: classes.card, elevation: 2 }}
icon={
<StatusBadge stateColor={state.color}>
<Avatar className={classes.avatar} title={state.name}>{ID}</Avatar>
<HostIcon />
</StatusBadge>
}
title={(
<Box component="span" className={classes.title}>
<Typography noWrap>{NAME}</Typography>
{renderChip({ label: mad })}
</Box>
)}
title={
<span className={classes.title}>
<Typography title={NAME} noWrap component='span'>
{NAME}
</Typography>
<StatusChip stateColor={'#c6c6c6'}>{mad}</StatusChip>
</span>
}
subheader={`#${ID}`}
isSelected={isSelected}
handleClick={handleClick}
actions={actions}

View File

@ -1,16 +1,11 @@
import React, { memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Avatar } from '@material-ui/core'
import { makeStyles } from '@material-ui/core'
import NetworkIcon from '@material-ui/icons/AccountTree'
import SelectCard from 'client/components/Cards/SelectCard'
const useStyles = makeStyles(theme => ({
title: { display: 'flex', gap: '8px' },
avatar: {
width: '5ch',
height: '5ch',
color: theme.palette.primary.contrastText
},
card: { backgroundColor: theme.palette.primary.main }
}))
@ -23,8 +18,9 @@ const NetworkCard = memo(
<SelectCard
stylesProps={{ minHeight: 120 }}
cardProps={{ className: classes.card, elevation: 2 }}
icon={<Avatar className={classes.avatar} title={ID}>{ID}</Avatar>}
icon={<NetworkIcon />}
title={NAME}
subheader={`#${ID}`}
isSelected={isSelected}
handleClick={handleClick}
actions={actions}

View File

@ -38,10 +38,13 @@ const ProvisionCard = memo(
return (
<SelectCard
title={`(ID: ${ID}) - ${NAME}`}
title={NAME}
subheader={`#${ID}`}
isSelected={isSelected}
handleClick={handleClick}
action={actions?.map(action => <Action key={action?.cy} {...action} />)}
action={actions?.map(action =>
<Action key={action?.cy} {...action} />
)}
icon={
isProvider ? (
<ProviderIcon />

View File

@ -1,56 +0,0 @@
import React, { memo, useMemo } from 'react'
import PropTypes from 'prop-types'
import SelectCard from 'client/components/Cards/SelectCard'
import { isExternalURL } from 'client/utils'
import { PROVIDER_IMAGES_URL, PROVISION_IMAGES_URL } from 'client/constants'
const ProvisionTemplateCard = memo(
({ value, title, isSelected, isProvider, handleClick }) => {
const IMAGES_URL = isProvider ? PROVIDER_IMAGES_URL : PROVISION_IMAGES_URL
const { image } = (isProvider ? value?.plain : value) ?? {}
const imgSource = useMemo(() =>
isExternalURL(image) ? image : `${IMAGES_URL}/${image}`
, [image])
return (
<SelectCard
stylesProps={{ minHeight: 80 }}
isSelected={isSelected}
handleClick={handleClick}
title={title}
mediaProps={image && {
component: 'img',
image: imgSource,
draggable: false
}}
/>
)
}, (prev, next) => prev.isSelected === next.isSelected
)
ProvisionTemplateCard.propTypes = {
value: PropTypes.shape({
name: PropTypes.string.isRequired,
plain: PropTypes.shape({
image: PropTypes.string
})
}),
title: PropTypes.string,
isProvider: PropTypes.bool,
isSelected: PropTypes.bool,
handleClick: PropTypes.func
}
ProvisionTemplateCard.defaultProps = {
value: {},
title: undefined,
isProvider: undefined,
isSelected: undefined,
handleClick: undefined
}
ProvisionTemplateCard.displayName = 'ProvisionTemplateCard'
export default ProvisionTemplateCard

View File

@ -59,29 +59,32 @@ const SelectCard = memo(({
}
>
{/* CARD HEADER */}
{(title || subheader || icon || action) && <CardHeader
action={action}
avatar={icon}
classes={{
content: classes.headerContent,
avatar: classes.headerAvatar
}}
title={title}
titleTypographyProps={{
variant: 'body1',
noWrap: true,
className: classes.header,
title
}}
subheader={subheader}
subheaderTypographyProps={{
variant: 'body2',
noWrap: true,
className: classes.subheader,
title: subheader
}}
{...cardHeaderProps}
/>}
{(title || subheader || icon || action) && (
<CardHeader
action={action}
avatar={icon}
classes={{
root: classes.headerRoot,
content: classes.headerContent,
avatar: classes.headerAvatar
}}
title={title}
titleTypographyProps={{
variant: 'body1',
noWrap: true,
className: classes.header,
title: typeof title === 'string' ? title : undefined
}}
subheader={subheader}
subheaderTypographyProps={{
variant: 'body2',
noWrap: true,
className: classes.subheader,
title: typeof subheader === 'string' ? subheader : undefined
}}
{...cardHeaderProps}
/>
)}
{/* CARD CONTENT */}
{children}
@ -150,10 +153,7 @@ SelectCard.propTypes = {
PropTypes.string,
PropTypes.object
]),
cardHeaderProps: PropTypes.shape({
titleTypographyProps: PropTypes.object,
subheaderTypographyProps: PropTypes.object
}),
cardHeaderProps: PropTypes.object,
mediaProps: PropTypes.shape({
classes: PropTypes.object,
className: PropTypes.string,

View File

@ -34,11 +34,12 @@ export default makeStyles(theme => ({
}
},
media: {},
headerRoot: { alignItems: 'end' },
headerContent: { overflow: 'auto' },
headerAvatar: {
display: 'flex',
color: theme.palette.primary.contrastText
},
headerContent: { overflowX: 'hidden' },
header: {
color: theme.palette.primary.contrastText
},

View File

@ -1,71 +1,30 @@
import React, { useEffect, useState, memo } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
import { makeStyles, Box } from '@material-ui/core'
import AutoScrollBox from 'client/components/AutoScrollBox'
import Message from 'client/components/DebugLog/message'
import { DEBUG_LEVEL } from 'client/constants'
import AnsiHtml from 'client/components/DebugLog/ansiHtml'
const useStyles = makeStyles(theme => ({
const debugLogStyles = makeStyles(theme => ({
root: {
display: 'flex',
marginBottom: '0.3em',
padding: '0.5em 0',
cursor: 'default',
fontFamily: 'monospace',
'&:hover': {
background: '#333537'
fontSize: '1.1em',
wordBreak: 'break-word',
'&::-webkit-scrollbar': {
width: 14
},
'&::-webkit-scrollbar-thumb': {
backgroundClip: 'content-box',
border: '4px solid transparent',
borderRadius: 7,
boxShadow: 'inset 0 0 0 10px',
color: theme.palette.primary.light
}
},
time: {
paddingLeft: '0.5em',
minWidth: '220px'
},
message: {
color: '#fafafa'
},
[DEBUG_LEVEL.ERROR]: { borderLeft: `0.3em solid ${theme.palette.error.light}` },
[DEBUG_LEVEL.WARN]: { borderLeft: `0.3em solid ${theme.palette.warning.main}` },
[DEBUG_LEVEL.INFO]: { borderLeft: `0.3em solid ${theme.palette.info.main}` },
[DEBUG_LEVEL.DEBUG]: { borderLeft: `0.3em solid ${theme.palette.debug.main}` }
}
}))
// --------------------------------------------
// MESSAGE COMPONENT
// --------------------------------------------
const Message = memo(({ timestamp = '', severity = DEBUG_LEVEL.DEBUG, message }) => {
const classes = useStyles()
const sanitize = AnsiHtml(message)
return (
<div className={clsx([classes.root, classes[severity]])}>
<div className={classes.time}>{timestamp}</div>
<div className={classes.message}>{sanitize}</div>
</div>
)
})
Message.propTypes = {
timestamp: PropTypes.string,
severity: PropTypes.string,
message: PropTypes.string,
index: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
])
}
Message.defaultProps = { log: [], message: '', index: 0 }
Message.displayName = 'Message'
// --------------------------------------------
// DEBUG LOG COMPONENT
// --------------------------------------------
const DebugLog = memo(({ uuid, socket, logDefault }) => {
const classes = debugLogStyles()
const [log, setLog] = useState(logDefault)
useEffect(() => {
@ -83,7 +42,7 @@ const DebugLog = memo(({ uuid, socket, logDefault }) => {
}, [])
return (
<Box borderRadius={5} bgcolor={'#1d1f21'} width={1} height={1} style={{ fontSize: '1.1em', wordBreak: 'break-word' }}>
<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]) =>

View File

@ -0,0 +1,80 @@
import React, { memo, useState, useRef } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
import { makeStyles } from '@material-ui/core'
import ChevronRightIcon from '@material-ui/icons/ChevronRight'
import { DEBUG_LEVEL } from 'client/constants'
import AnsiHtml from 'client/components/DebugLog/ansiHtml'
const MAX_CHARS = 80
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
alignItems: 'center',
marginBottom: '0.3em',
padding: '0.5em 0',
cursor: ({ isCollapsed }) => isCollapsed ? 'pointer' : 'default',
fontFamily: 'monospace',
'&:hover': {
background: '#333537'
}
},
arrow: {
padding: '0 0.5em',
width: '32px'
},
time: {
minWidth: '220px'
},
message: {
color: '#fafafa'
},
[DEBUG_LEVEL.ERROR]: { borderLeft: `0.3em solid ${theme.palette.error.light}` },
[DEBUG_LEVEL.WARN]: { borderLeft: `0.3em solid ${theme.palette.warning.light}` },
[DEBUG_LEVEL.INFO]: { borderLeft: `0.3em solid ${theme.palette.info.light}` },
[DEBUG_LEVEL.DEBUG]: { borderLeft: `0.3em solid ${theme.palette.debug.main}` }
}))
// --------------------------------------------
// MESSAGE COMPONENT
// --------------------------------------------
const Message = memo(({ timestamp, severity, message }) => {
const [isCollapsed, setCollapse] = useState(() => message?.length >= MAX_CHARS)
const classes = useStyles({ isCollapsed })
const sanitize = AnsiHtml(message)
return (
<div
className={clsx(classes.root, classes[severity])}
onClick={() => setCollapse(false)}
>
<div className={classes.arrow}>
{isCollapsed && <ChevronRightIcon fontSize='small' />}
</div>
<div className={classes.time}>{timestamp}</div>
<div className={classes.message}>
{isCollapsed ? `${sanitize.slice(0, MAX_CHARS)}...` : sanitize}
</div>
</div>
)
})
Message.propTypes = {
timestamp: PropTypes.string,
severity: PropTypes.oneOf([Object.keys(DEBUG_LEVEL)]),
message: PropTypes.string
}
Message.defaultProps = {
timestamp: '',
severity: DEBUG_LEVEL.DEBUG,
message: ''
}
Message.displayName = 'Message'
export default Message

View File

@ -9,6 +9,7 @@ export default makeStyles(theme => ({
: theme.palette.primary.main
},
title: {
userSelect: 'none',
flexGrow: 1,
display: 'inline-flex',
color: theme.palette.primary.contrastText,

View File

@ -68,6 +68,7 @@ export default makeStyles(theme => ({
// HEADER MENU
// -------------------------------
header: {
userSelect: 'none',
display: 'flex',
alignItems: 'center',
padding: '1rem',

View File

@ -1,21 +1,19 @@
import React, { memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Typography } from '@material-ui/core'
import { makeStyles, Typography, lighten } from '@material-ui/core'
import { addOpacityToColor } from 'client/utils'
const useStyles = makeStyles(theme => ({
root: ({ stateColor }) => ({
color: stateColor,
backgroundColor: addOpacityToColor(stateColor, 0.08),
root: ({ stateColor = theme.palette.primary.main }) => ({
color: lighten(stateColor, 0.75),
backgroundColor: addOpacityToColor(stateColor, 0.2),
cursor: 'default',
padding: theme.spacing('0.25rem', '0.5rem'),
minWidth: 20,
borderRadius: 2,
textTransform: 'uppercase',
fontSize: theme.typography.overline.fontSize,
fontWeight: theme.typography.fontWeightBold,
lineHeight: 'normal'
fontWeight: theme.typography.fontWeightBold
})
}))

View File

@ -40,7 +40,7 @@ const Info = memo(({ data }) => {
<Typography>{NAME}</Typography>
</ListItem>
<ListItem>
<Typography >{Tr(T.Description)}</Typography>
<Typography>{Tr(T.Description)}</Typography>
<Typography noWrap>{description}</Typography>
</ListItem>
<ListItem>

View File

@ -29,6 +29,8 @@ function Providers () {
useEffect(() => { fetchRequest() }, [])
const handleCancel = () => setShowDialog(false)
return (
<Container disableGutters>
<ListHeader
@ -52,7 +54,8 @@ function Providers () {
isProvider: true,
handleClick: () => setShowDialog({
id: ID,
title: `(ID: ${ID}) ${NAME}`
title: NAME,
subheader: `#${ID}`
}),
actions: [
{
@ -64,7 +67,7 @@ function Providers () {
{
handleClick: () => setShowDialog({
id: ID,
title: `DELETE provider - (ID: ${ID}) ${NAME}`,
title: `DELETE provider - #${ID} - ${NAME}`,
handleAccept: () => {
deleteProvider({ id: ID })
setShowDialog(false)
@ -82,11 +85,7 @@ function Providers () {
{showDialog !== false && (
<DialogRequest
request={() => getProvider({ id: showDialog.id })}
dialogProps={{
title: showDialog.title,
handleCancel: () => setShowDialog(false),
handleAccept: showDialog.handleAccept
}}
dialogProps={{ handleCancel, ...showDialog }}
>
{({ data }) => <Information data={data} />}
</DialogRequest>

View File

@ -52,13 +52,14 @@ function Provisions () {
cardsProps={({ value: { ID, NAME } }) => ({
handleClick: () => setShowDialog({
id: ID,
title: `(ID: ${ID}) ${NAME}`,
title: NAME,
subheader: `#${ID}`,
content: DialogInfo
}),
actions: [{
handleClick: () => setShowDialog({
id: ID,
title: `DELETE provision - (ID: ${ID}) ${NAME}`,
title: `DELETE provision - #${ID} - ${NAME}`,
handleAccept: () => {
deleteProvision({ id: ID })
setShowDialog(false)
@ -77,11 +78,7 @@ function Provisions () {
<DialogRequest
withTabs
request={() => getProvision({ id: showDialog.id })}
dialogProps={{
title: showDialog.title,
handleCancel,
handleAccept: showDialog.handleAccept
}}
dialogProps={{ handleCancel, ...showDialog }}
>
{props => createElement(showDialog.content, props)}
</DialogRequest>