1
0
mirror of https://github.com/OpenNebula/one.git synced 2024-12-22 13:33:52 +03:00

F #3951: Provision log (#654)

This commit is contained in:
Sergio Betanzos 2021-01-15 14:47:52 +01:00 committed by GitHub
parent 35232a764d
commit 940b38a903
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 284 additions and 58 deletions

View File

@ -1,6 +1,6 @@
name: 'aws-frankfurt'
description: 'Elastic cluster on AWS in Frakfurt'
description: 'Elastic cluster on AWS in Frankfurt'
provider: 'aws'
plain:

View File

@ -18,7 +18,7 @@
inputs:
- name: 'number_hosts'
type: text
description: 'Number of AWS instances to crearte'
description: 'Number of AWS instances to create'
default: '1'
- name: 'aws_ami_image'

View File

@ -20,9 +20,12 @@ export default {
},
error: {
light: '#e57373',
main: '#f44336',
main: '#e04d40',
dark: '#d32f2f',
contrastText: '#ffffff'
},
debug: {
main: '#7b7c7e'
}
}
}

View File

@ -11,11 +11,7 @@ const useStyles = makeStyles(theme => ({
position: 'sticky',
textAlign: 'center'
},
button: { padding: theme.spacing(0, 2) },
svg: {
color: '#fff !important',
backgroundColor: 'transparent !important'
}
button: { padding: theme.spacing(0, 2) }
}))
const baseClass = 'react-auto-scroll'
@ -94,9 +90,8 @@ const AutoScrollBox = memo(({
<Slide in={!autoScroll} direction="down" mountOnEnter unmountOnExit>
<div className={classes.wrapperButton}>
<Chip
avatar={<ArrowBottomIcon />}
color="primary"
classes={{ avatarColorPrimary: classes.svg }}
avatar={<ArrowBottomIcon style={{ backgroundColor: 'transparent' }} />}
color='secondary'
className={classes.button}
label={autoButtonText}
onClick={() => setAutoScroll(true)}

View File

@ -0,0 +1,172 @@
// Reference to https://github.com/sindresorhus/ansi-regex
var _regANSI = /(?:(?:\u001b\[)|\u009b)(?:(?:[0-9]{1,3})?(?:(?:;[0-9]{0,3})*)?[A-M|f-m])|\u001b[A-M]/
var _defColors = {
reset: ['fff', '000'], // [FOREGROUD_COLOR, BACKGROUND_COLOR]
black: '000',
red: 'ff0000',
green: '209805',
yellow: 'e8bf03',
blue: '0000ff',
magenta: 'ff00ff',
cyan: '00ffee',
lightgrey: 'f0f0f0',
darkgrey: '888'
}
var _styles = {
30: 'black',
31: 'red',
32: 'green',
33: 'yellow',
34: 'blue',
35: 'magenta',
36: 'cyan',
37: 'lightgrey'
}
var _openTags = {
1: 'font-weight:bold', // bold
2: 'opacity:0.5', // dim
3: '<i>', // italic
4: '<u>', // underscore
8: 'display:none', // hidden
9: '<del>' // delete
}
var _closeTags = {
23: '</i>', // reset italic
24: '</u>', // reset underscore
29: '</del>' // reset delete
}
;[0, 21, 22, 27, 28, 39, 49].forEach(function (n) {
_closeTags[n] = '</span>'
})
/**
* Converts text with ANSI color codes to HTML markup.
* @param {String} text
* @returns {*}
*/
export default function ansiHTML (text) {
// Returns the text if the string has no ANSI escape code.
if (!_regANSI.test(text)) {
return text
}
// Cache opened sequence.
var ansiCodes = []
// Replace with markup.
var ret = text.replace(/\033\[(\d+)*m/g, function (match, seq) {
var ot = _openTags[seq]
if (ot) {
// If current sequence has been opened, close it.
if (!!~ansiCodes.indexOf(seq)) { // eslint-disable-line no-extra-boolean-cast
ansiCodes.pop()
return '</span>'
}
// Open tag.
ansiCodes.push(seq)
return ot[0] === '<' ? ot : '<span style="' + ot + ';">'
}
var ct = _closeTags[seq]
if (ct) {
// Pop sequence
ansiCodes.pop()
return ct
}
return ''
})
// Make sure tags are closed.
var l = ansiCodes.length
;(l > 0) && (ret += Array(l + 1).join('</span>'))
return ret
}
/**
* Customize colors.
* @param {Object} colors reference to _defColors
*/
ansiHTML.setColors = function (colors) {
if (typeof colors !== 'object') {
throw new Error('`colors` parameter must be an Object.')
}
var _finalColors = {}
for (var key in _defColors) {
var hex = colors.hasOwnProperty(key) ? colors[key] : null
if (!hex) {
_finalColors[key] = _defColors[key]
continue
}
if (key === 'reset') {
if (typeof hex === 'string') {
hex = [hex]
}
if (!Array.isArray(hex) || hex.length === 0 || hex.some(function (h) {
return typeof h !== 'string'
})) {
throw new Error('The value of `' + key + '` property must be an Array and each item could only be a hex string, e.g.: FF0000')
}
var defHexColor = _defColors[key]
if (!hex[0]) {
hex[0] = defHexColor[0]
}
if (hex.length === 1 || !hex[1]) {
hex = [hex[0]]
hex.push(defHexColor[1])
}
hex = hex.slice(0, 2)
} else if (typeof hex !== 'string') {
throw new Error('The value of `' + key + '` property must be a hex string, e.g.: FF0000')
}
_finalColors[key] = hex
}
_setTags(_finalColors)
}
/**
* Reset colors.
*/
ansiHTML.reset = function () {
_setTags(_defColors)
}
/**
* Expose tags, including open and close.
* @type {Object}
*/
ansiHTML.tags = {}
if (Object.defineProperty) {
Object.defineProperty(ansiHTML.tags, 'open', {
get: function () { return _openTags }
})
Object.defineProperty(ansiHTML.tags, 'close', {
get: function () { return _closeTags }
})
} else {
ansiHTML.tags.open = _openTags
ansiHTML.tags.close = _closeTags
}
function _setTags (colors) {
// reset all
_openTags['0'] = 'font-weight:normal;opacity:1;color:#' + colors.reset[0] + ';background:#' + colors.reset[1]
// inverse
_openTags['7'] = 'color:#' + colors.reset[1] + ';background:#' + colors.reset[0]
// dark grey
_openTags['90'] = 'color:#' + colors.darkgrey
for (var code in _styles) {
var color = _styles[code]
var oriColor = colors[color] || '000'
_openTags[code] = 'color:#' + oriColor
code = parseInt(code)
_openTags[(code + 10).toString()] = 'background:#' + oriColor
}
}
ansiHTML.reset()

View File

@ -1,46 +1,56 @@
import React, { useEffect, useState, memo } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
import { makeStyles, lighten, Box } from '@material-ui/core'
import { makeStyles, Box } from '@material-ui/core'
import AutoScrollBox from 'client/components/AutoScrollBox'
import { DEBUG_LEVEL } from 'client/constants'
import AnsiHtml from 'client/components/DebugLog/ansiHtml'
const useStyles = makeStyles(theme => ({
message: ({ color = '#fafafa' }) => ({
color,
root: {
display: 'flex',
marginBottom: '0.3em',
padding: '0.5em 0',
cursor: 'default',
fontFamily: 'monospace',
padding: theme.spacing(0.5, 1),
'&:hover': { backgroundColor: lighten('#1d1f21', 0.02) }
})
'&:hover': {
background: '#333537'
}
},
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(({ log, message, index }) => {
const color = () => {
const lastMessage = log[index - 1]
if (message.includes('WARNING')) return 'yellow'
else if (message.includes('WARNING')) return 'yellow'
else if (
message.includes('ERROR') || lastMessage?.includes('ERROR')
) return 'red'
else return '#fafafa'
}
const classes = useStyles({ color: color() })
const Message = memo(({ timestamp = '', severity = DEBUG_LEVEL.DEBUG, message }) => {
const classes = useStyles()
const sanitize = AnsiHtml(message)
return (
<div key={index} className={classes.message}>
{message}
<div className={clsx([classes.root, classes[severity]])}>
<div className={classes.time}>{timestamp}</div>
<div className={classes.message}>{sanitize}</div>
</div>
)
}, (prev, next) => prev.index === next.index)
})
Message.propTypes = {
log: PropTypes.array,
timestamp: PropTypes.string,
severity: PropTypes.string,
message: PropTypes.string,
index: PropTypes.oneOfType([
PropTypes.string,
@ -59,23 +69,55 @@ const DebugLog = memo(({ uuid, socket, logDefault }) => {
const [log, setLog] = useState(logDefault)
useEffect(() => {
uuid && socket.on(socketData =>
socketData?.id === uuid && setLog(prev => [...prev, socketData?.data])
)
uuid && socket.on((socketData = {}) => {
const { id, data, command, commandId } = socketData
id === uuid && setLog(prev => ({
...prev,
[command]: {
[commandId]: [...(prev?.[command]?.[commandId] ?? []), data]
}
}))
})
return () => uuid && socket.off()
}, [])
return (
<Box borderRadius={5} bgcolor={'#1d1f21'} width={1} height={1}>
<Box borderRadius={5} bgcolor={'#1d1f21'} width={1} height={1} style={{ fontSize: '1.1em', wordBreak: 'break-word' }}>
<AutoScrollBox scrollBehavior="auto">
{log?.map((message, index) => {
const isString = typeof message === 'string'
const stringMessage = isString ? message : JSON.stringify(message)
{Object.entries(log)?.map(([command, entries]) =>
Object.entries(entries)?.map(([commandId, messages]) =>
messages?.map((data, index) => {
const key = `${index}-${command}-${commandId}`
return (
<Message key={index} log={log} message={stringMessage} index={index} />
try {
const { timestamp, severity, message } = JSON.parse(data)
const decryptMessage = atob(message)
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>
)
@ -87,7 +129,7 @@ DebugLog.propTypes = {
on: PropTypes.func.isRequired,
off: PropTypes.func.isRequired
}).isRequired,
logDefault: PropTypes.array
logDefault: PropTypes.object
}
DebugLog.defaultProps = {
@ -96,7 +138,7 @@ DebugLog.defaultProps = {
on: () => undefined,
off: () => undefined
},
logDefault: []
logDefault: {}
}
DebugLog.displayName = 'DebugLog'

View File

@ -60,6 +60,13 @@ export const INPUT_TYPES = {
AUTOCOMPLETE: 'autocomplete'
}
export const DEBUG_LEVEL = {
ERROR: 'ERROR',
WARN: 'WARN',
INFO: 'INFO',
DEBUG: 'DEBUG'
}
export * as T from 'client/constants/translates'
export * from 'client/constants/flow'
export * from 'client/constants/states'

View File

@ -9,23 +9,37 @@ import DebugLog from 'client/components/DebugLog'
const Log = React.memo(({ hidden, data: { ID } }) => {
const { getProvision } = useSocket()
const { getProvisionLog } = useProvision()
const { data: provision, fetchRequest, loading } = useFetch(getProvisionLog)
const { data: provisionLog, fetchRequest, loading } = useFetch(getProvisionLog)
React.useEffect(() => {
!hidden && fetchRequest({ id: ID })
}, [ID])
React.useEffect(() => {
(!provision && !hidden) && fetchRequest({ id: ID })
(!provisionLog && !hidden) && fetchRequest({ id: 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 }
}, {})
return loading ? (
<LinearProgress color='secondary' style={{ width: '100%' }} />
) : (
<DebugLog
uuid={provision?.uuid ?? ID}
uuid={provisionLog?.uuid ?? ID}
socket={getProvision}
logDefault={provision?.log}
logDefault={log}
/>
)
}, (prev, next) =>

View File

@ -240,14 +240,7 @@ const addPrependCommand = (command = '', resource = '') => {
}
}
const addOptionalCreateCommand = () =>{
let rtn = []
if(optionalCreateCommand){
rtn.push(optionalCreateCommand)
}
return rtn
}
const addOptionalCreateCommand = () => [optionalCreateCommand].filter(Boolean)
const executeCommandAsync = (
command = '',