mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-16 22:50:10 +03:00
F OpenNebula/one#5422: Add information tab to vm detail
This commit is contained in:
parent
d5f2a0e0a7
commit
70dd7af341
@ -7,7 +7,7 @@ import { useVmApi } from 'client/features/One'
|
||||
|
||||
import VmTabs from 'client/components/Tabs/Vm'
|
||||
|
||||
const VmDetail = React.memo(({ id, view = {} }) => {
|
||||
const VmDetail = React.memo(({ id }) => {
|
||||
const { getHooksSocket } = useSocket()
|
||||
const { getVm } = useVmApi()
|
||||
|
||||
@ -27,21 +27,14 @@ const VmDetail = React.memo(({ id, view = {} }) => {
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div>{error}</div>
|
||||
return <div>{error || 'Error'}</div>
|
||||
}
|
||||
|
||||
const tabs = Object.entries(view['info-tabs'] ?? {})
|
||||
?.map(([tabName, { enabled } = {}]) => !!enabled && tabName)
|
||||
?.filter(Boolean)
|
||||
|
||||
return (
|
||||
<VmTabs data={data} tabs={tabs} view={view} />
|
||||
)
|
||||
return <VmTabs data={data} />
|
||||
})
|
||||
|
||||
VmDetail.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
view: PropTypes.object.isRequired
|
||||
id: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
VmDetail.displayName = 'VmDetail'
|
||||
|
@ -16,14 +16,10 @@ const INTERVAL_ON_FIRST_RENDER = 2_000
|
||||
const VmsTable = () => {
|
||||
const vms = useVm()
|
||||
const { getVms } = useVmApi()
|
||||
const { view, views, filterPool } = useAuth()
|
||||
|
||||
const viewSelected = React.useMemo(() => views[view], [view])
|
||||
|
||||
const resourceView = viewSelected?.find(({ resource_name: name }) => name === 'VM')
|
||||
const { view, getResourceView, filterPool } = useAuth()
|
||||
|
||||
const columns = React.useMemo(() => createColumns({
|
||||
filters: resourceView?.filters,
|
||||
filters: getResourceView('VM')?.filters,
|
||||
columns: VmColumns
|
||||
}), [view])
|
||||
|
||||
@ -63,7 +59,7 @@ const VmsTable = () => {
|
||||
isLoading={loading || reloading}
|
||||
getRowId={row => String(row.ID)}
|
||||
RowComponent={VmRow}
|
||||
renderDetail={row => <VmDetail id={row.ID} view={resourceView} />}
|
||||
renderDetail={row => <VmDetail id={row.ID} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -17,11 +17,7 @@ const Multiple = ({ tags, limitTags = 1 }) => {
|
||||
))
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'end'
|
||||
}}>
|
||||
<>
|
||||
{Tags}
|
||||
{more > 0 && (
|
||||
<Tooltip arrow
|
||||
@ -34,7 +30,7 @@ const Multiple = ({ tags, limitTags = 1 }) => {
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,11 @@ const Row = ({ original, value, ...props }) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.secondary}>
|
||||
{!!IPS?.length && <Multiple tags={IPS.split(',')} />}
|
||||
{!!IPS?.length && (
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'end' }}>
|
||||
<Multiple tags={IPS.split(',')} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
58
src/fireedge/src/client/components/Tabs/Common/List.js
Normal file
58
src/fireedge/src/client/components/Tabs/Common/List.js
Normal file
@ -0,0 +1,58 @@
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { makeStyles, List as MList, ListItem, Typography, Paper } from '@material-ui/core'
|
||||
|
||||
import { Tr } from 'client/components/HOC'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
list: {
|
||||
...theme.typography.body2,
|
||||
'& > * > *': {
|
||||
width: '50%'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`
|
||||
}
|
||||
}))
|
||||
|
||||
const List = ({ title, list = [], ...props }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<Paper variant='outlined' {...props}>
|
||||
<MList className={classes.list}>
|
||||
{/* TITLE */}
|
||||
{title && (
|
||||
<ListItem className={classes.title}>
|
||||
<Typography noWrap>{Tr(title)}</Typography>
|
||||
</ListItem>
|
||||
)}
|
||||
{/* LIST */}
|
||||
{list.map(({ key, value }, idx) => (
|
||||
<ListItem key={`${key}-${idx}`}>
|
||||
<Typography noWrap title={key}>{Tr(key)}</Typography>
|
||||
<Typography noWrap title={value}>{value}</Typography>
|
||||
</ListItem>
|
||||
))}
|
||||
</MList>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
List.propTypes = {
|
||||
title: PropTypes.string,
|
||||
list: PropTypes.arrayOf(PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.node
|
||||
])
|
||||
}))
|
||||
}
|
||||
|
||||
List.displayName = 'List'
|
||||
|
||||
export default List
|
51
src/fireedge/src/client/components/Tabs/Common/Ownership.js
Normal file
51
src/fireedge/src/client/components/Tabs/Common/Ownership.js
Normal file
@ -0,0 +1,51 @@
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { makeStyles, List, ListItem, Typography, Paper, Divider } from '@material-ui/core'
|
||||
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
list: {
|
||||
'& p': {
|
||||
...theme.typography.body2,
|
||||
width: '50%'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
fontWeight: theme.typography.fontWeightBold
|
||||
}
|
||||
}))
|
||||
|
||||
const Ownership = React.memo(({ userName, groupName }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<Paper variant='outlined'>
|
||||
<List className={classes.list}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography noWrap>{Tr(T.Ownership)}</Typography>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem>
|
||||
<Typography noWrap>{Tr(T.Owner)}</Typography>
|
||||
<Typography noWrap>{userName}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Group)}</Typography>
|
||||
<Typography>{groupName}</Typography>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
)
|
||||
})
|
||||
|
||||
Ownership.propTypes = {
|
||||
userName: PropTypes.string.isRequired,
|
||||
groupName: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
Ownership.displayName = 'Ownership'
|
||||
|
||||
export default Ownership
|
@ -0,0 +1,167 @@
|
||||
import React, { memo, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import clsx from 'clsx'
|
||||
import { List, ListItem, Typography, Grid, Paper, Divider } from '@material-ui/core'
|
||||
import {
|
||||
Check as CheckIcon,
|
||||
Square as BlankSquareIcon,
|
||||
EyeEmpty as EyeIcon
|
||||
} from 'iconoir-react'
|
||||
|
||||
import { useProviderApi } from 'client/features/One'
|
||||
import { Action } from 'client/components/Cards/SelectCard'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import useStyles from 'client/containers/Providers/Sections/styles'
|
||||
|
||||
const Info = memo(({ fetchProps }) => {
|
||||
const classes = useStyles()
|
||||
const { getProviderConnection } = useProviderApi()
|
||||
|
||||
const [showConnection, setShowConnection] = useState(undefined)
|
||||
|
||||
const { ID, NAME, GNAME, UNAME, PERMISSIONS, TEMPLATE } = fetchProps?.data
|
||||
const {
|
||||
connection,
|
||||
description,
|
||||
provider: providerName,
|
||||
registration_time: time
|
||||
} = TEMPLATE?.PROVISION_BODY
|
||||
|
||||
const hasConnection = connection && Object.keys(connection).length > 0
|
||||
|
||||
const isChecked = checked =>
|
||||
checked === '1' ? <CheckIcon /> : <BlankSquareIcon />
|
||||
|
||||
const ConnectionButton = () => (
|
||||
<Action
|
||||
icon={<EyeIcon />}
|
||||
cy='provider-connection'
|
||||
handleClick={() => getProviderConnection(ID).then(setShowConnection)}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper variant="outlined" className={classes.marginBottom}>
|
||||
<List className={clsx(classes.list, 'w-50')}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography>{Tr(T.Information)}</Typography>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem>
|
||||
<Typography>{'ID'}</Typography>
|
||||
<Typography>{ID}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Name)}</Typography>
|
||||
<Typography data-cy="provider-name">{NAME}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Description)}</Typography>
|
||||
<Typography data-cy="provider-description" noWrap>{description}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Provider)}</Typography>
|
||||
<Typography data-cy="provider-type">{providerName}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.RegistrationTime)}</Typography>
|
||||
<Typography>
|
||||
{new Date(time * 1000).toLocaleString()}
|
||||
</Typography>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
{hasConnection && (
|
||||
<Paper variant="outlined">
|
||||
<List className={clsx(classes.list, 'w-50')}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography>{Tr(T.Credentials)}</Typography>
|
||||
<span className={classes.alignToRight}>
|
||||
{!showConnection && <ConnectionButton />}
|
||||
</span>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
{Object.entries(connection)?.map(([key, value]) =>
|
||||
typeof value === 'string' && (
|
||||
<ListItem key={key}>
|
||||
<Typography>{key}</Typography>
|
||||
<Typography data-cy={`provider-${key}`}>
|
||||
{showConnection?.[key] ?? value}
|
||||
</Typography>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Paper>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper variant="outlined" className={classes.marginBottom}>
|
||||
<List className={clsx(classes.list, 'w-25')}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography>{Tr(T.Permissions)}</Typography>
|
||||
<Typography>{Tr(T.Use)}</Typography>
|
||||
<Typography>{Tr(T.Manage)}</Typography>
|
||||
<Typography>{Tr(T.Admin)}</Typography>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Owner)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.OWNER_U)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.OWNER_M)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.OWNER_A)}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Group)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.GROUP_U)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.GROUP_M)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.GROUP_A)}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Other)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.OTHER_U)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.OTHER_M)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.OTHER_A)}</Typography>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
<Paper variant="outlined">
|
||||
<List className={clsx(classes.list, 'w-50')}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography>{Tr(T.Ownership)}</Typography>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Owner)}</Typography>
|
||||
<Typography>{UNAME}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Group)}</Typography>
|
||||
<Typography>{GNAME}</Typography>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
})
|
||||
|
||||
Info.propTypes = {
|
||||
fetchProps: PropTypes.shape({
|
||||
data: PropTypes.object.isRequired
|
||||
}).isRequired
|
||||
}
|
||||
|
||||
Info.defaultProps = {
|
||||
fetchProps: {
|
||||
data: {}
|
||||
}
|
||||
}
|
||||
|
||||
Info.displayName = 'Info'
|
||||
|
||||
export default Info
|
112
src/fireedge/src/client/components/Tabs/Common/Permissions.js
Normal file
112
src/fireedge/src/client/components/Tabs/Common/Permissions.js
Normal file
@ -0,0 +1,112 @@
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { makeStyles, List, ListItem, Typography, Paper } from '@material-ui/core'
|
||||
import { Check as CheckIcon, Square as BlankSquareIcon } from 'iconoir-react'
|
||||
|
||||
import { useVmApi } from 'client/features/One'
|
||||
import { Action } from 'client/components/Cards/SelectCard'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import * as Helper from 'client/models/Helper'
|
||||
|
||||
const CATEGORIES = [
|
||||
{ title: T.Owner, category: 'owner' },
|
||||
{ title: T.Group, category: 'group' },
|
||||
{ title: T.Other, category: 'other' }
|
||||
]
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
list: {
|
||||
...theme.typography.body2,
|
||||
'& > * > *': {
|
||||
width: '25%'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`
|
||||
}
|
||||
}))
|
||||
|
||||
const Permissions = React.memo(({
|
||||
id,
|
||||
OWNER_U, OWNER_M, OWNER_A,
|
||||
GROUP_U, GROUP_M, GROUP_A,
|
||||
OTHER_U, OTHER_M, OTHER_A
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
const { changePermissions } = useVmApi()
|
||||
|
||||
const [permissions, setPermissions] = React.useState(() => ({
|
||||
ownerUse: OWNER_U,
|
||||
ownerManage: OWNER_M,
|
||||
ownerAdmin: OWNER_A,
|
||||
groupUse: GROUP_U,
|
||||
groupManage: GROUP_M,
|
||||
groupAdmin: GROUP_A,
|
||||
otherUse: OTHER_U,
|
||||
otherManage: OTHER_M,
|
||||
otherAdmin: OTHER_A
|
||||
}))
|
||||
|
||||
const handleChange = async (name, value) => {
|
||||
const newPermission = { [name]: Helper.stringToBoolean(value) ? '0' : '1' }
|
||||
const response = await changePermissions(id, newPermission)
|
||||
|
||||
String(response?.data) === String(id) &&
|
||||
setPermissions(prev => ({ ...prev, ...newPermission }))
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper variant='outlined'>
|
||||
<List className={classes.list}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography noWrap>{Tr(T.Permissions)}</Typography>
|
||||
<Typography noWrap>{Tr(T.Use)}</Typography>
|
||||
<Typography noWrap>{Tr(T.Manage)}</Typography>
|
||||
<Typography noWrap>{Tr(T.Admin)}</Typography>
|
||||
</ListItem>
|
||||
{CATEGORIES.map(({ title, category }) => (
|
||||
<ListItem key={category}>
|
||||
{/* TITLE */}
|
||||
<Typography noWrap>{Tr(title)}</Typography>
|
||||
|
||||
{/* PERMISSIONS */}
|
||||
{Object.entries(permissions)
|
||||
.filter(([key, _]) => key.toLowerCase().includes(category))
|
||||
.map(([key, permission]) => (
|
||||
<span key={key}>
|
||||
<Action
|
||||
cy={`permission-${key}`}
|
||||
disabled={permission === undefined}
|
||||
icon={+permission ? <CheckIcon /> : <BlankSquareIcon />}
|
||||
handleClick={() => handleChange(key, permission)}
|
||||
/>
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Paper>
|
||||
)
|
||||
})
|
||||
|
||||
Permissions.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
OWNER_U: PropTypes.string.isRequired,
|
||||
OWNER_M: PropTypes.string.isRequired,
|
||||
OWNER_A: PropTypes.string.isRequired,
|
||||
GROUP_U: PropTypes.string.isRequired,
|
||||
GROUP_M: PropTypes.string.isRequired,
|
||||
GROUP_A: PropTypes.string.isRequired,
|
||||
OTHER_U: PropTypes.string.isRequired,
|
||||
OTHER_M: PropTypes.string.isRequired,
|
||||
OTHER_A: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
Permissions.displayName = 'Permissions'
|
||||
|
||||
export default Permissions
|
9
src/fireedge/src/client/components/Tabs/Common/index.js
Normal file
9
src/fireedge/src/client/components/Tabs/Common/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
import List from 'client/components/Tabs/Common/List'
|
||||
import Permissions from 'client/components/Tabs/Common/Permissions'
|
||||
import Ownership from 'client/components/Tabs/Common/Ownership'
|
||||
|
||||
export {
|
||||
List,
|
||||
Permissions,
|
||||
Ownership
|
||||
}
|
@ -1,50 +1,61 @@
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import loadable from '@loadable/component'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
|
||||
import Tabs from 'client/components/Tabs'
|
||||
import { stringToCamelCase, stringToCamelSpace } from 'client/utils'
|
||||
|
||||
const stringToCamelCase = s =>
|
||||
s.replace(
|
||||
/([-_][a-z])/ig,
|
||||
$1 => $1.toUpperCase()
|
||||
.replace('-', '')
|
||||
.replace('_', '')
|
||||
)
|
||||
const Capacity = loadable(() => import('client/components/Tabs/Vm/capacity'))
|
||||
const Configuration = loadable(() => import('client/components/Tabs/Vm/configuration'))
|
||||
const Info = loadable(() => import('client/components/Tabs/Vm/info'))
|
||||
const Log = loadable(() => import('client/components/Tabs/Vm/log'))
|
||||
const Network = loadable(() => import('client/components/Tabs/Vm/network'))
|
||||
const Placement = loadable(() => import('client/components/Tabs/Vm/placement'))
|
||||
const SchedActions = loadable(() => import('client/components/Tabs/Vm/schedActions'))
|
||||
const Snapshot = loadable(() => import('client/components/Tabs/Vm/snapshot'))
|
||||
const Storage = loadable(() => import('client/components/Tabs/Vm/storage'))
|
||||
|
||||
const stringToCamelSpace = s => s.replace(/([a-z])([A-Z])/g, '$1 $2')
|
||||
const loadTab = tabName => ({
|
||||
capacity: Capacity,
|
||||
configuration: Configuration,
|
||||
info: Info,
|
||||
log: Log,
|
||||
network: Network,
|
||||
placement: Placement,
|
||||
schedActions: SchedActions,
|
||||
snapshot: Snapshot,
|
||||
storage: Storage
|
||||
}[tabName])
|
||||
|
||||
const VmTabs = ({ data, tabs }) => {
|
||||
const [renderTabs, setTabs] = React.useState(() => [])
|
||||
const VmTabs = ({ data }) => {
|
||||
const [tabsAvailable, setTabs] = React.useState(() => [])
|
||||
const { view, getResourceView } = useAuth()
|
||||
|
||||
React.useEffect(() => {
|
||||
const loadTab = async tabKey => {
|
||||
try {
|
||||
const camelCaseKey = stringToCamelCase(tabKey)
|
||||
const infoTabs = getResourceView('VM')?.['info-tabs'] ?? {}
|
||||
|
||||
// dynamic import => client/components/Tabs/Vm
|
||||
const tabComponent = await import(`./${camelCaseKey}`)
|
||||
const tabs = Object.entries(infoTabs)
|
||||
?.map(([tabName, { enabled } = {}]) => !!enabled && tabName)
|
||||
?.filter(Boolean)
|
||||
|
||||
setTabs(prev => prev.concat([{
|
||||
name: stringToCamelSpace(camelCaseKey),
|
||||
renderContent: tabComponent.default(data)
|
||||
}]))
|
||||
} catch (error) {}
|
||||
}
|
||||
setTabs(() => tabs.map(tabName => {
|
||||
const nameSanitize = stringToCamelCase(tabName)
|
||||
const TabContent = loadTab(nameSanitize)
|
||||
|
||||
// reset
|
||||
setTabs([])
|
||||
return TabContent && {
|
||||
name: stringToCamelSpace(nameSanitize),
|
||||
renderContent: props => TabContent.render({ ...props })
|
||||
}
|
||||
}).filter(Boolean))
|
||||
}, [view])
|
||||
|
||||
tabs?.forEach(loadTab)
|
||||
}, [tabs?.length])
|
||||
|
||||
return <Tabs tabs={renderTabs} />
|
||||
return <Tabs tabs={tabsAvailable} data={data} />
|
||||
}
|
||||
|
||||
VmTabs.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
tabs: PropTypes.arrayOf(
|
||||
PropTypes.string
|
||||
).isRequired
|
||||
data: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
VmTabs.displayName = 'VmTabs'
|
||||
|
@ -1,11 +1,15 @@
|
||||
import * as React from 'react'
|
||||
import { StatusBadge } from 'client/components/Status'
|
||||
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { List, Permissions, Ownership } from 'client/components/Tabs/Common'
|
||||
import Multiple from 'client/components/Tables/Vms/multiple'
|
||||
|
||||
import * as VirtualMachine from 'client/models/VirtualMachine'
|
||||
import * as Helper from 'client/models/Helper'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const VmInfoTab = data => {
|
||||
const { ID, NAME, UNAME, GNAME, RESCHED, STIME, ETIME, LOCK, DEPLOY_ID } = data
|
||||
const { ID, NAME, UNAME, GNAME, RESCHED, STIME, ETIME, LOCK, DEPLOY_ID, PERMISSIONS } = data
|
||||
|
||||
const { name: stateName, color: stateColor } = VirtualMachine.getState(data)
|
||||
|
||||
@ -14,30 +18,57 @@ const VmInfoTab = data => {
|
||||
|
||||
const ips = VirtualMachine.getIps(data)
|
||||
|
||||
const info = [
|
||||
{ key: [T.ID], value: ID },
|
||||
{ key: [T.Name], value: NAME },
|
||||
{
|
||||
key: [T.State],
|
||||
value: <StatusChip text={stateName} stateColor={stateColor} />
|
||||
},
|
||||
{
|
||||
key: [T.Reschedule],
|
||||
value: Helper.booleanToString(+RESCHED)
|
||||
},
|
||||
{
|
||||
key: [T.Locked],
|
||||
value: Helper.levelLockToString(LOCK?.LOCKED)
|
||||
},
|
||||
{
|
||||
key: [T.IP],
|
||||
value: ips?.length ? <Multiple tags={ips} /> : '--'
|
||||
},
|
||||
{
|
||||
key: [T.StartTime],
|
||||
value: Helper.timeToString(STIME)
|
||||
},
|
||||
{
|
||||
key: [T.EndTime],
|
||||
value: Helper.timeToString(ETIME)
|
||||
},
|
||||
{
|
||||
key: [T.Host],
|
||||
value: hostId ? `#${hostId} ${hostname}` : ''
|
||||
},
|
||||
{
|
||||
key: [T.Cluster],
|
||||
value: clusterId ? `#${clusterId} ${clusterName}` : ''
|
||||
},
|
||||
{
|
||||
key: [T.DeployID],
|
||||
value: DEPLOY_ID
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<StatusBadge
|
||||
title={stateName}
|
||||
stateColor={stateColor}
|
||||
customTransform='translate(150%, 50%)'
|
||||
/>
|
||||
<span style={{ marginLeft: 20 }}>
|
||||
{`#${ID} - ${NAME}`}
|
||||
</span>
|
||||
</p>
|
||||
<div>
|
||||
<p>Owner: {UNAME}</p>
|
||||
<p>Group: {GNAME}</p>
|
||||
<p>Reschedule: {Helper.booleanToString(+RESCHED)}</p>
|
||||
<p>Locked: {Helper.levelLockToString(LOCK?.LOCKED)}</p>
|
||||
<p>IP: {ips.join(', ') || '--'}</p>
|
||||
<p>Start time: {Helper.timeToString(STIME)}</p>
|
||||
<p>End time: {Helper.timeToString(ETIME)}</p>
|
||||
<p>Host: {hostId ? `#${hostId} ${hostname}` : ''}</p>
|
||||
<p>Cluster: {clusterId ? `#${clusterId} ${clusterName}` : ''}</p>
|
||||
<p>Deploy ID: {DEPLOY_ID}</p>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gap: '1em',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(480px, 1fr))',
|
||||
padding: '1em'
|
||||
}}>
|
||||
<List title={T.Information} list={info} style={{ gridRow: 'span 3' }} />
|
||||
<Permissions id={ID} {...PERMISSIONS} />
|
||||
<Ownership userName={UNAME} groupName={GNAME} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
|
||||
|
||||
import { Tabs as MTabs, Tab as MTab } from '@material-ui/core'
|
||||
|
||||
const Content = ({ name, renderContent: Content, hidden }) => (
|
||||
const Content = ({ name, renderContent: Content, hidden, data }) => (
|
||||
<div key={`tab-${name}`}
|
||||
style={{
|
||||
padding: 2,
|
||||
@ -12,17 +12,17 @@ const Content = ({ name, renderContent: Content, hidden }) => (
|
||||
display: hidden ? 'none' : 'block'
|
||||
}}
|
||||
>
|
||||
{typeof Content === 'function' ? <Content /> : Content}
|
||||
{typeof Content === 'function' ? <Content {...data} /> : Content}
|
||||
</div>
|
||||
)
|
||||
|
||||
const Tabs = ({ tabs = [], renderHiddenTabs = false }) => {
|
||||
const Tabs = ({ tabs = [], renderHiddenTabs = false, data }) => {
|
||||
const [tabSelected, setTab] = useState(0)
|
||||
|
||||
const renderTabs = useMemo(() => (
|
||||
<MTabs
|
||||
value={tabSelected}
|
||||
variant="scrollable"
|
||||
variant='scrollable'
|
||||
scrollButtons='auto'
|
||||
onChange={(_, tab) => setTab(tab)}
|
||||
>
|
||||
@ -52,7 +52,7 @@ const Tabs = ({ tabs = [], renderHiddenTabs = false }) => {
|
||||
{renderHiddenTabs ? (
|
||||
renderAllHiddenTabContents
|
||||
) : (
|
||||
<Content {...tabs.find(({ value }, idx) => (value ?? idx) === tabSelected)} />
|
||||
<Content data={data} {...tabs.find(({ value }, idx) => (value ?? idx) === tabSelected)} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
@ -61,13 +61,20 @@ const Tabs = ({ tabs = [], renderHiddenTabs = false }) => {
|
||||
Tabs.displayName = 'Tabs'
|
||||
Content.displayName = 'Content'
|
||||
|
||||
Tabs.propTypes = {
|
||||
tabs: PropTypes.array,
|
||||
renderHiddenTabs: PropTypes.bool,
|
||||
data: PropTypes.object
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
name: PropTypes.string,
|
||||
renderContent: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.func
|
||||
]),
|
||||
hidden: PropTypes.bool
|
||||
hidden: PropTypes.bool,
|
||||
data: PropTypes.object
|
||||
}
|
||||
|
||||
export default Tabs
|
||||
|
@ -145,11 +145,19 @@ module.exports = {
|
||||
Information: 'Information',
|
||||
|
||||
/* general schema */
|
||||
ID: 'ID',
|
||||
Name: 'Name',
|
||||
State: 'State',
|
||||
Description: 'Description',
|
||||
RegistrationTime: 'Registration time',
|
||||
StartTime: 'Start time',
|
||||
EndTime: 'End time',
|
||||
Locked: 'Locked',
|
||||
|
||||
/* instances schema */
|
||||
IP: 'IP',
|
||||
Reschedule: 'Reschedule',
|
||||
DeployID: 'Deploy ID',
|
||||
|
||||
/* flow schema */
|
||||
Strategy: 'Strategy',
|
||||
|
@ -9,7 +9,7 @@ export const useAuth = () => {
|
||||
const auth = useSelector(state => state.auth, shallowEqual)
|
||||
const groups = useSelector(state => state.one.groups, shallowEqual)
|
||||
|
||||
const { user, jwt } = auth
|
||||
const { user, jwt, view, views } = auth
|
||||
|
||||
const userGroups = [user?.GROUPS?.ID]
|
||||
.flat()
|
||||
@ -18,7 +18,10 @@ export const useAuth = () => {
|
||||
|
||||
const isLogged = !!jwt && !!userGroups?.length
|
||||
|
||||
return { ...auth, groups: userGroups, isLogged }
|
||||
const getResourceView = resourceName => views?.[view]
|
||||
?.find(({ resource_name: name }) => name === resourceName)
|
||||
|
||||
return { ...auth, groups: userGroups, isLogged, getResourceView }
|
||||
}
|
||||
|
||||
export const useAuthApi = () => {
|
||||
|
@ -23,7 +23,7 @@ export const createAction = (type, service, wrapResult) =>
|
||||
|
||||
status === httpCodes.unauthorized.id && dispatch(logout(T.SessionExpired))
|
||||
|
||||
return rejectWithValue(message, data?.message ?? statusText)
|
||||
return rejectWithValue(message ?? data?.data ?? data?.message ?? statusText)
|
||||
}
|
||||
}, {
|
||||
condition: (_, { getState }) => !getState().one.requests[type]
|
||||
|
@ -15,7 +15,7 @@ export const getVms = createAction(
|
||||
)
|
||||
|
||||
export const terminateVm = createAction(
|
||||
'provider/delete',
|
||||
'vm/delete',
|
||||
payload => vmService.actionVm({
|
||||
...payload,
|
||||
action: {
|
||||
@ -24,3 +24,5 @@ export const terminateVm = createAction(
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
export const changePermissions = createAction('vm/chmod', vmService.changePermissions)
|
||||
|
@ -19,6 +19,7 @@ export const useVmApi = () => {
|
||||
return {
|
||||
getVm: id => unwrapDispatch(actions.getVm({ id })),
|
||||
getVms: options => unwrapDispatch(actions.getVms(options)),
|
||||
terminateVm: id => unwrapDispatch(actions.terminateVm({ id }))
|
||||
terminateVm: id => unwrapDispatch(actions.terminateVm({ id })),
|
||||
changePermissions: (id, data) => unwrapDispatch(actions.changePermissions({ id, data }))
|
||||
}
|
||||
}
|
||||
|
@ -22,17 +22,30 @@ export const vmService = ({
|
||||
const command = { name, ...Commands[name] }
|
||||
return poolRequest(data, command, 'VM')
|
||||
},
|
||||
actionVm: async ({ action, id }) => {
|
||||
actionVm: async ({ id, action }) => {
|
||||
const name = Actions.VM_ACTION
|
||||
const { url, options } = requestParams(
|
||||
{ action, id },
|
||||
{ id, action },
|
||||
{ name, ...Commands[name] }
|
||||
)
|
||||
|
||||
const res = await RestClient.put(url, options)
|
||||
const res = await RestClient.put(url, options?.data)
|
||||
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res
|
||||
|
||||
return res?.data?.VM ?? {}
|
||||
},
|
||||
changePermissions: async ({ id, data }) => {
|
||||
const name = Actions.VM_CHMOD
|
||||
const { url, options } = requestParams(
|
||||
{ id, ...data },
|
||||
{ name, ...Commands[name] }
|
||||
)
|
||||
|
||||
const res = await RestClient.put(url, options?.data)
|
||||
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res?.data
|
||||
|
||||
return res
|
||||
}
|
||||
})
|
||||
|
@ -18,3 +18,23 @@ export const levelLockToString = level => ({
|
||||
3: 'Admin',
|
||||
4: 'All'
|
||||
}[level] || '-')
|
||||
|
||||
export const permissionsToOctal = permissions => {
|
||||
const {
|
||||
OWNER_U, OWNER_M, OWNER_A,
|
||||
GROUP_U, GROUP_M, GROUP_A,
|
||||
OTHER_U, OTHER_M, OTHER_A
|
||||
} = permissions
|
||||
|
||||
const getCategoryValue = ([u, m, a]) => (
|
||||
(stringToBoolean(u) ? 4 : 0) +
|
||||
(stringToBoolean(m) ? 2 : 0) +
|
||||
(stringToBoolean(a) ? 1 : 0)
|
||||
)
|
||||
|
||||
return [
|
||||
[OWNER_U, OWNER_M, OWNER_A],
|
||||
[GROUP_U, GROUP_M, GROUP_A],
|
||||
[OTHER_U, OTHER_M, OTHER_A]
|
||||
].map(getCategoryValue).join('')
|
||||
}
|
||||
|
@ -7,12 +7,14 @@ export const getAllocatedInfo = ({ HOST_SHARE = {} } = {}) => {
|
||||
const { CPU_USAGE, TOTAL_CPU, MEM_USAGE, TOTAL_MEM } = HOST_SHARE
|
||||
|
||||
const percentCpuUsed = +CPU_USAGE * 100 / +TOTAL_CPU || 0
|
||||
const percentCpuLabel = `${CPU_USAGE} / ${TOTAL_CPU} (${Math.round(percentCpuUsed)}%)`
|
||||
const percentCpuLabel = `${CPU_USAGE} / ${TOTAL_CPU}
|
||||
(${Math.round(isFinite(percentCpuUsed) ? percentCpuUsed : '--')}%)`
|
||||
|
||||
const percentMemUsed = +MEM_USAGE * 100 / +TOTAL_MEM || 0
|
||||
const usedMemBytes = prettyBytes(+MEM_USAGE)
|
||||
const totalMemBytes = prettyBytes(+TOTAL_MEM)
|
||||
const percentMemLabel = `${usedMemBytes} / ${totalMemBytes} (${Math.round(percentMemUsed)}%)`
|
||||
const percentMemLabel = `${usedMemBytes} / ${totalMemBytes}
|
||||
(${Math.round(isFinite(percentMemUsed) ? percentMemUsed : '--')}%)`
|
||||
|
||||
return {
|
||||
percentCpuUsed,
|
||||
|
@ -43,6 +43,13 @@ export const addOpacityToColor = (color, opacity) => {
|
||||
export const capitalize = ([firstLetter, ...restOfWord]) =>
|
||||
firstLetter.toUpperCase() + restOfWord.join('')
|
||||
|
||||
export const stringToCamelCase = s => s.replace(
|
||||
/([-_\s][a-z])/ig,
|
||||
$1 => $1.toUpperCase().replace(/[-_\s]/g, '')
|
||||
)
|
||||
|
||||
export const stringToCamelSpace = s => s.replace(/([a-z])([A-Z])/g, '$1 $2')
|
||||
|
||||
export const getValidationFromFields = fields =>
|
||||
fields.reduce(
|
||||
(schema, field) => ({
|
||||
|
@ -155,7 +155,7 @@ module.exports = {
|
||||
from: postBody,
|
||||
default: 0
|
||||
},
|
||||
livemigration: {
|
||||
liveMigration: {
|
||||
from: postBody,
|
||||
default: false
|
||||
},
|
||||
@ -357,39 +357,39 @@ module.exports = {
|
||||
from: resource,
|
||||
default: 0
|
||||
},
|
||||
user_use: {
|
||||
ownerUse: {
|
||||
from: postBody,
|
||||
default: -1
|
||||
},
|
||||
user_manage: {
|
||||
ownerManage: {
|
||||
from: postBody,
|
||||
default: -1
|
||||
},
|
||||
user_admin: {
|
||||
ownerAdmin: {
|
||||
from: postBody,
|
||||
default: -1
|
||||
},
|
||||
group_use: {
|
||||
groupUse: {
|
||||
from: postBody,
|
||||
default: -1
|
||||
},
|
||||
group_manage: {
|
||||
groupManage: {
|
||||
from: postBody,
|
||||
default: -1
|
||||
},
|
||||
group_admin: {
|
||||
groupAdmin: {
|
||||
from: postBody,
|
||||
default: -1
|
||||
},
|
||||
other_use: {
|
||||
otherUse: {
|
||||
from: postBody,
|
||||
default: -1
|
||||
},
|
||||
other_manage: {
|
||||
otherManage: {
|
||||
from: postBody,
|
||||
default: -1
|
||||
},
|
||||
other_admin: {
|
||||
otherAdmin: {
|
||||
from: postBody,
|
||||
default: -1
|
||||
}
|
||||
@ -601,7 +601,7 @@ module.exports = {
|
||||
from: query,
|
||||
default: -2
|
||||
},
|
||||
filterbykey: {
|
||||
filterByKey: {
|
||||
from: query,
|
||||
default: ''
|
||||
}
|
||||
@ -669,19 +669,19 @@ module.exports = {
|
||||
from: query,
|
||||
default: -2
|
||||
},
|
||||
start_month: {
|
||||
startMonth: {
|
||||
filter: query,
|
||||
default: -1
|
||||
},
|
||||
start_year: {
|
||||
startYear: {
|
||||
filter: query,
|
||||
default: -1
|
||||
},
|
||||
end_month: {
|
||||
endMonth: {
|
||||
filter: query,
|
||||
default: -1
|
||||
},
|
||||
end_year: {
|
||||
endYear: {
|
||||
filter: query,
|
||||
default: -1
|
||||
}
|
||||
@ -691,19 +691,19 @@ module.exports = {
|
||||
// inspected
|
||||
httpMethod: GET,
|
||||
params: {
|
||||
start_month: {
|
||||
startMonth: {
|
||||
filter: query,
|
||||
default: -1
|
||||
},
|
||||
start_year: {
|
||||
startYear: {
|
||||
filter: query,
|
||||
default: -1
|
||||
},
|
||||
end_month: {
|
||||
endMonth: {
|
||||
filter: query,
|
||||
default: -1
|
||||
},
|
||||
end_year: {
|
||||
endYear: {
|
||||
filter: query,
|
||||
default: -1
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user