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

F OpenNebula/one#5422: Add context to vm tabs

This commit is contained in:
Sergio Betanzos 2021-07-15 11:33:10 +02:00
parent 54744e9875
commit 1d5a8f59b3
No known key found for this signature in database
GPG Key ID: E3E704F097737136
16 changed files with 194 additions and 118 deletions

View File

@ -63,10 +63,10 @@ const Attribute = React.memo(({
}
const handleActiveEditForm = async () => {
const response = await handleGetOptionList?.()
const response = await handleGetOptionList?.() ?? []
const isFormatValid = response?.every?.(({ text, value } = {}) => !!text && !!value)
if (isFormatValid) {
if (!handleGetOptionList || isFormatValid) {
setOptions(response)
setIsEditing(true)
}
@ -79,25 +79,31 @@ const Attribute = React.memo(({
return (
<>
<Typography noWrap variant='body2'>
<Typography noWrap variant='body2' title={Tr(name)}>
{Tr(name)}
</Typography>
<div className={classes.wrapper}>
{isEditing ? (
<>
{handleGetOptionList && (
{handleGetOptionList ? (
<Inputs.Select
name={name}
value={valueInOptionList}
ref={inputRef}
options={options} />
) : (
<Inputs.Text name={name} value={value} ref={inputRef} />
)}
<Actions.Accept name={name} handleClick={handleEditAttribute} />
<Actions.Cancel name={name} handleClick={handleCancel} />
</>
) : (
<>
<Typography noWrap variant='body2'>
<Typography
noWrap
variant='body2'
title={typeof value === 'string' ? value : undefined}
>
{value}
</Typography>
{canEdit && (

View File

@ -82,4 +82,41 @@ Select.propTypes = {
)
}
export { Select }
const Text = React.forwardRef(
/**
* @param {object} props - Props
* @param {string} props.name - Attribute name
* @param {string} props.value - Attribute value
* @param {React.ForwardedRef} ref - Forward reference
* @returns {React.JSXElementConstructor} Text field
*/
({ name = '', value = '' }, ref) => {
console.log({ name, value })
const [newValue, setNewValue] = React.useState(() => value)
const handleChange = event => setNewValue(event.target.value)
return (
<TextField
color='secondary'
inputProps={{
'data-cy': Actions.getAttributeCy('text', name)
}}
inputRef={ref}
margin='dense'
onChange={handleChange}
value={newValue}
variant='outlined'
/>
)
}
)
Text.displayName = 'Text'
Text.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
}
export { Select, Text }

View File

@ -19,6 +19,7 @@ import PropTypes from 'prop-types'
import { makeStyles, List as MList, ListItem, Typography, Paper } from '@material-ui/core'
import Attribute from 'client/components/Tabs/Common/Attribute'
import { Tr } from 'client/components/HOC'
const useStyles = makeStyles(theme => ({
@ -27,7 +28,7 @@ const useStyles = makeStyles(theme => ({
borderBottom: `1px solid ${theme.palette.divider}`
},
item: {
'& > $typo': {
'& > *': {
width: '50%'
}
},
@ -49,18 +50,12 @@ const List = ({ title, list = [], ...props }) => {
</ListItem>
)}
{/* LIST */}
{list.map(({ key, value }, idx) => (
<ListItem key={`${key}-${idx}`} className={classes.item}>
<Typography className={classes.typo} noWrap title={key}>
{Tr(key)}
</Typography>
<Typography
noWrap
className={classes.typo}
title={typeof value === 'string' ? value : undefined}
>
{value}
</Typography>
{list.map((attribute, idx) => (
<ListItem
key={`${attribute.name}-${idx}`}
className={classes.item}
>
<Attribute {...attribute}/>
</ListItem>
))}
</MList>
@ -71,8 +66,14 @@ const List = ({ title, list = [], ...props }) => {
List.propTypes = {
title: PropTypes.string,
list: PropTypes.arrayOf(PropTypes.shape({
key: PropTypes.string,
value: PropTypes.any
canDelete: PropTypes.bool,
canEdit: PropTypes.bool,
handleEdit: PropTypes.func,
handleDelete: PropTypes.func,
handleGetOptionList: PropTypes.func,
name: PropTypes.string.isRequired,
value: PropTypes.any.isRequired,
valueInOptionList: PropTypes.string.isRequired
}))
}

View File

@ -64,35 +64,25 @@ const Ownership = React.memo(({
)
}
const ownership = [
{
key: T.Owner,
value: userName,
valueInOptionList: userId,
handleGetOptionList: getUserOptions,
handleEdit: user => handleEdit?.({ user })
},
{
key: T.Group,
value: groupName,
valueInOptionList: groupId,
handleGetOptionList: getGroupOptions,
handleEdit: group => handleEdit?.({ group })
}
]
return (
<Paper variant='outlined'>
<List>
<ListItem className={classes.title}>
<Typography noWrap>{Tr(T.Ownership)}</Typography>
</ListItem>
<Divider />
<ListItem className={classes.item}>
<Attribute
canEdit
name={T.Owner}
value={userName}
valueInOptionList={userId}
handleGetOptionList={getUserOptions}
handleEdit={user => handleEdit?.({ user })}
/>
</ListItem>
<ListItem className={classes.item}>
<Attribute
canEdit
name={T.Group}
value={groupName}
valueInOptionList={groupId}
handleGetOptionList={getGroupOptions}
handleEdit={group => handleEdit?.({ group })}
/>
</ListItem>
</List>
</Paper>
<List title={T.Ownership} list={ownership} />
)
})

View File

@ -0,0 +1,45 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React, { createContext, useState } from 'react'
import PropTypes from 'prop-types'
export const TabContext = createContext(null)
const TabProvider = ({ initialState = {}, children }) => {
const [information, setTabInformation] = useState(() => initialState)
const { data } = initialState
React.useEffect(() => {
data && setTabInformation(prev => ({ ...prev, data }))
}, [data])
return (
<TabContext.Provider value={{ ...information, setTabInformation }}>
{children}
</TabContext.Provider>
)
}
TabProvider.propTypes = {
initialState: PropTypes.object,
children: PropTypes.oneOfType([
PropTypes.node,
PropTypes.arrayOf(PropTypes.node)
])
}
export default TabProvider

View File

@ -17,6 +17,7 @@
import * as React from 'react'
import { makeStyles, Paper, Typography } from '@material-ui/core'
import { TabContext } from 'client/components/Tabs/TabProvider'
import { Action } from 'client/components/Cards/SelectCard'
import * as VirtualMachine from 'client/models/VirtualMachine'
import { prettyBytes } from 'client/utils'
@ -56,12 +57,13 @@ const useStyles = makeStyles(theme => ({
}
}))
const VmCapacityTab = data => {
const VmCapacityTab = () => {
const classes = useStyles()
const { TEMPLATE } = data
const { data: vm = {} } = React.useContext(TabContext)
const { TEMPLATE } = vm
const isVCenter = VirtualMachine.isVCenter(data)
const isVCenter = VirtualMachine.isVCenter(vm)
const capacity = [
{ key: 'Physical CPU', value: TEMPLATE?.CPU },

View File

@ -17,10 +17,12 @@
import * as React from 'react'
import { Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core'
import { TabContext } from 'client/components/Tabs/TabProvider'
const NavArrowDown = <span style={{ writingMode: 'vertical-rl' }}>{'>'}</span>
const VmConfigurationTab = data => {
const { TEMPLATE, USER_TEMPLATE } = data
const { data: { TEMPLATE, USER_TEMPLATE } = {} } = React.useContext(TabContext)
return (
<div>

View File

@ -19,11 +19,13 @@ import PropTypes from 'prop-types'
import { useVmApi } from 'client/features/One'
import { Permissions, Ownership } from 'client/components/Tabs/Common'
import { TabContext } from 'client/components/Tabs/TabProvider'
import Information from 'client/components/Tabs/Vm/Info/information'
const VmInfoTab = ({ tabProps, handleRefetch, ...data }) => {
const { ID, UNAME, UID, GNAME, GID, PERMISSIONS } = data
const VmInfoTab = ({ tabProps }) => {
const { changeOwnership } = useVmApi()
const { handleRefetch, data } = React.useContext(TabContext)
const { ID, UNAME, UID, GNAME, GID, PERMISSIONS } = data
const handleChangeOwnership = async newOwnership => {
const response = await changeOwnership(ID, newOwnership)
@ -58,8 +60,7 @@ const VmInfoTab = ({ tabProps, handleRefetch, ...data }) => {
}
VmInfoTab.propTypes = {
tabProps: PropTypes.object,
handleRefetch: PropTypes.func
tabProps: PropTypes.object
}
VmInfoTab.displayName = 'VmInfoTab'

View File

@ -35,42 +35,49 @@ const InformationPanel = data => {
const ips = VirtualMachine.getIps(data)
const info = [
{ key: T.ID, value: ID },
{ key: T.Name, value: NAME },
{ name: T.ID, value: ID },
{
key: T.State,
name: T.Name,
value: NAME,
canEdit: true,
handleEdit: newName => {
}
},
{
name: T.State,
value: <StatusChip text={stateName} stateColor={stateColor} />
},
{
key: T.Reschedule,
name: T.Reschedule,
value: Helper.booleanToString(+RESCHED)
},
{
key: T.Locked,
name: T.Locked,
value: Helper.levelLockToString(LOCK?.LOCKED)
},
{
key: T.IP,
name: T.IP,
value: ips?.length ? <Multiple tags={ips} /> : '--'
},
{
key: T.StartTime,
name: T.StartTime,
value: Helper.timeToString(STIME)
},
{
key: T.EndTime,
name: T.EndTime,
value: Helper.timeToString(ETIME)
},
{
key: T.Host,
name: T.Host,
value: hostId ? `#${hostId} ${hostname}` : ''
},
{
key: T.Cluster,
name: T.Cluster,
value: clusterId ? `#${clusterId} ${clusterName}` : ''
},
{
key: T.DeployID,
name: T.DeployID,
value: DEPLOY_ID
}
]

View File

@ -32,6 +32,7 @@ import {
import { useVmApi } from 'client/features/One'
import { Action } from 'client/components/Cards/SelectCard'
import Multiple from 'client/components/Tables/Vms/multiple'
import { TabContext } from 'client/components/Tabs/TabProvider'
import { T, VM_ACTIONS } from 'client/constants'
import { Tr } from 'client/components/HOC'
@ -84,9 +85,11 @@ const useStyles = makeStyles(({
}
}))
const NetworkItem = ({ vmId, handleRefetch, nic = {}, actions }) => {
const NetworkItem = ({ nic = {}, actions }) => {
const classes = useStyles()
const isMobile = useMediaQuery(theme => theme.breakpoints.down('sm'))
const { handleRefetch, data: vm } = React.useContext(TabContext)
const { detachNic } = useVmApi()
const { NIC_ID, NETWORK = '-', BRIDGE, IP, MAC, PCI_ID, ALIAS, SECURITY_GROUPS } = nic
@ -96,15 +99,15 @@ const NetworkItem = ({ vmId, handleRefetch, nic = {}, actions }) => {
[ALIAS.length, SECURITY_GROUPS?.length]
)
const detachAction = () => actions.includes(VM_ACTIONS.DETACH_NIC) && (
const detachAction = () => actions?.includes?.(VM_ACTIONS.DETACH_NIC) && (
<Action
cy={`${VM_ACTIONS.DETACH_NIC}-${NIC_ID}`}
icon={<Trash size={18} />}
stopPropagation
handleClick={async () => {
const response = await detachNic(vmId, NIC_ID)
const response = await detachNic(vm.ID, NIC_ID)
String(response) === String(vmId) && handleRefetch?.(vmId)
String(response) === String(vm.ID) && handleRefetch?.(vm.ID)
}}
/>
)
@ -169,8 +172,6 @@ const NetworkItem = ({ vmId, handleRefetch, nic = {}, actions }) => {
NetworkItem.propTypes = {
actions: PropTypes.arrayOf(PropTypes.string),
handleRefetch: PropTypes.func,
vmId: PropTypes.string,
nic: PropTypes.object
}

View File

@ -19,7 +19,7 @@ import PropTypes from 'prop-types'
import NetworkItem from 'client/components/Tabs/Vm/Network/Item'
const NetworkList = ({ vmId, handleRefetch, nics, actions }) => (
const NetworkList = ({ nics, actions }) => (
<div style={{
display: 'flex',
flexDirection: 'column',
@ -27,21 +27,13 @@ const NetworkList = ({ vmId, handleRefetch, nics, actions }) => (
paddingBlock: '0.8em'
}}>
{nics.map((nic, idx) => (
<NetworkItem
key={idx}
actions={actions}
handleRefetch={handleRefetch}
nic={nic}
vmId={vmId}
/>
<NetworkItem key={idx} actions={actions} nic={nic} />
))}
</div>
)
NetworkList.propTypes = {
actions: PropTypes.arrayOf(PropTypes.string),
handleRefetch: PropTypes.func,
vmId: PropTypes.string,
nics: PropTypes.array
}

View File

@ -18,11 +18,12 @@ import * as React from 'react'
import PropTypes from 'prop-types'
import NetworkList from 'client/components/Tabs/Vm/Network/List'
import { TabContext } from 'client/components/Tabs/TabProvider'
import * as VirtualMachine from 'client/models/VirtualMachine'
import * as Helper from 'client/models/Helper'
const VmNetworkTab = ({ tabProps, handleRefetch, ...vm }) => {
const VmNetworkTab = ({ tabProps }) => {
const { data: vm } = React.useContext(TabContext)
const { actions = [] } = tabProps
const nics = VirtualMachine.getNics(vm, {
@ -34,18 +35,12 @@ const VmNetworkTab = ({ tabProps, handleRefetch, ...vm }) => {
const actionsAvailable = Helper.getActionsAvailable(actions, hypervisor)
return (
<NetworkList
vmId={vm.ID}
actions={actionsAvailable}
nics={nics}
handleRefetch={handleRefetch}
/>
<NetworkList actions={actionsAvailable} nics={nics} />
)
}
VmNetworkTab.propTypes = {
tabProps: PropTypes.object,
handleRefetch: PropTypes.func
tabProps: PropTypes.object
}
VmNetworkTab.displayName = 'VmNetworkTab'

View File

@ -22,11 +22,7 @@ import StorageItem from 'client/components/Tabs/Vm/Storage/Item'
const StorageList = ({ disks, actions }) => (
<div style={{ display: 'grid', gap: '1em', paddingBlock: '0.8em' }}>
{disks.map((disk, idx) => (
<StorageItem
key={idx}
disk={disk}
actions={actions}
/>
<StorageItem key={idx} disk={disk} actions={actions} />
))}
</div>
)

View File

@ -18,15 +18,17 @@ import * as React from 'react'
import PropTypes from 'prop-types'
import StorageList from 'client/components/Tabs/Vm/Storage/List'
import { TabContext } from 'client/components/Tabs/TabProvider'
import * as VirtualMachine from 'client/models/VirtualMachine'
import * as Helper from 'client/models/Helper'
const VmStorageTab = ({ tabProps, ...data }) => {
const VmStorageTab = ({ tabProps = {} }) => {
const { data: vm } = React.useContext(TabContext)
const { actions = [] } = tabProps
const disks = VirtualMachine.getDisks(data)
const hypervisor = VirtualMachine.getHypervisor(data)
const disks = VirtualMachine.getDisks(vm)
const hypervisor = VirtualMachine.getHypervisor(vm)
const actionsAvailable = Helper.getActionsAvailable(actions, hypervisor)
return (
@ -35,10 +37,7 @@ const VmStorageTab = ({ tabProps, ...data }) => {
}
VmStorageTab.propTypes = {
tabProps: PropTypes.shape({
actions: PropTypes.object
}),
actions: PropTypes.array
tabProps: PropTypes.object
}
VmStorageTab.displayName = 'VmStorageTab'

View File

@ -22,6 +22,7 @@ import { useAuth } from 'client/features/Auth'
import Tabs from 'client/components/Tabs'
import { stringToCamelCase, stringToCamelSpace } from 'client/utils'
import TabProvider from 'client/components/Tabs/TabProvider'
import Capacity from 'client/components/Tabs/Vm/Capacity'
import Configuration from 'client/components/Tabs/Vm/Configuration'
import Info from 'client/components/Tabs/Vm/Info'
@ -59,14 +60,17 @@ const VmTabs = ({ data, handleRefetch }) => {
return TabContent && {
name: stringToCamelSpace(nameSanitize),
renderContent:
props => TabContent({ ...props, tabProps, handleRefetch })
renderContent: props => TabContent({ ...props, tabProps })
}
})
?.filter(Boolean))
}, [view])
return <Tabs tabs={tabsAvailable} data={data} />
return (
<TabProvider initialState={{ data, handleRefetch }}>
<Tabs tabs={tabsAvailable} />
</TabProvider>
)
}
VmTabs.propTypes = {

View File

@ -19,7 +19,7 @@ import PropTypes from 'prop-types'
import { Tabs as MTabs, Tab as MTab } from '@material-ui/core'
const Content = ({ name, renderContent: Content, hidden, data }) => (
const Content = ({ name, renderContent: Content, hidden }) => (
<div key={`tab-${name}`}
style={{
padding: 2,
@ -28,11 +28,11 @@ const Content = ({ name, renderContent: Content, hidden, data }) => (
display: hidden ? 'none' : 'block'
}}
>
{typeof Content === 'function' ? <Content {...data} /> : Content}
{typeof Content === 'function' ? <Content /> : Content}
</div>
)
const Tabs = ({ tabs = [], renderHiddenTabs = false, data }) => {
const Tabs = ({ tabs = [], renderHiddenTabs = false }) => {
const [tabSelected, setTab] = useState(0)
const renderTabs = useMemo(() => (
@ -68,7 +68,7 @@ const Tabs = ({ tabs = [], renderHiddenTabs = false, data }) => {
{renderHiddenTabs ? (
renderAllHiddenTabContents
) : (
<Content data={data} {...tabs.find(({ value }, idx) => (value ?? idx) === tabSelected)} />
<Content {...tabs.find(({ value }, idx) => (value ?? idx) === tabSelected)} />
)}
</>
)
@ -79,8 +79,7 @@ Content.displayName = 'Content'
Tabs.propTypes = {
tabs: PropTypes.array,
renderHiddenTabs: PropTypes.bool,
data: PropTypes.object
renderHiddenTabs: PropTypes.bool
}
Content.propTypes = {
@ -89,8 +88,7 @@ Content.propTypes = {
PropTypes.object,
PropTypes.func
]),
hidden: PropTypes.bool,
data: PropTypes.object
hidden: PropTypes.bool
}
export default Tabs