From 70dd7af341dd4ea8065537089ade5c0d8e557b5b Mon Sep 17 00:00:00 2001 From: Sergio Betanzos Date: Tue, 6 Jul 2021 16:43:36 +0200 Subject: [PATCH] F OpenNebula/one#5422: Add information tab to vm detail --- .../client/components/Tables/Vms/detail.js | 15 +- .../src/client/components/Tables/Vms/index.js | 10 +- .../client/components/Tables/Vms/multiple.js | 8 +- .../src/client/components/Tables/Vms/row.js | 6 +- .../src/client/components/Tabs/Common/List.js | 58 ++++++ .../components/Tabs/Common/Ownership.js | 51 ++++++ .../components/Tabs/Common/Permission copy.js | 167 ++++++++++++++++++ .../components/Tabs/Common/Permissions.js | 112 ++++++++++++ .../client/components/Tabs/Common/index.js | 9 + .../src/client/components/Tabs/Vm/index.js | 73 ++++---- .../src/client/components/Tabs/Vm/info.js | 81 ++++++--- .../src/client/components/Tabs/index.js | 19 +- .../src/client/constants/translates.js | 8 + .../src/client/features/Auth/hooks.js | 7 +- src/fireedge/src/client/features/One/utils.js | 2 +- .../src/client/features/One/vm/actions.js | 4 +- .../src/client/features/One/vm/hooks.js | 3 +- .../src/client/features/One/vm/services.js | 19 +- src/fireedge/src/client/models/Helper.js | 20 +++ src/fireedge/src/client/models/Host.js | 6 +- src/fireedge/src/client/utils/helpers.js | 7 + .../src/server/utils/constants/commands/vm.js | 38 ++-- 22 files changed, 607 insertions(+), 116 deletions(-) create mode 100644 src/fireedge/src/client/components/Tabs/Common/List.js create mode 100644 src/fireedge/src/client/components/Tabs/Common/Ownership.js create mode 100644 src/fireedge/src/client/components/Tabs/Common/Permission copy.js create mode 100644 src/fireedge/src/client/components/Tabs/Common/Permissions.js create mode 100644 src/fireedge/src/client/components/Tabs/Common/index.js diff --git a/src/fireedge/src/client/components/Tables/Vms/detail.js b/src/fireedge/src/client/components/Tables/Vms/detail.js index 1eb88e940a..c9555190f7 100644 --- a/src/fireedge/src/client/components/Tables/Vms/detail.js +++ b/src/fireedge/src/client/components/Tables/Vms/detail.js @@ -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
{error}
+ return
{error || 'Error'}
} - const tabs = Object.entries(view['info-tabs'] ?? {}) - ?.map(([tabName, { enabled } = {}]) => !!enabled && tabName) - ?.filter(Boolean) - - return ( - - ) + return }) VmDetail.propTypes = { - id: PropTypes.string.isRequired, - view: PropTypes.object.isRequired + id: PropTypes.string.isRequired } VmDetail.displayName = 'VmDetail' diff --git a/src/fireedge/src/client/components/Tables/Vms/index.js b/src/fireedge/src/client/components/Tables/Vms/index.js index 1516072f27..17d6702803 100644 --- a/src/fireedge/src/client/components/Tables/Vms/index.js +++ b/src/fireedge/src/client/components/Tables/Vms/index.js @@ -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 => } + renderDetail={row => } /> ) } diff --git a/src/fireedge/src/client/components/Tables/Vms/multiple.js b/src/fireedge/src/client/components/Tables/Vms/multiple.js index d45217a1b0..c39bbc6c4f 100644 --- a/src/fireedge/src/client/components/Tables/Vms/multiple.js +++ b/src/fireedge/src/client/components/Tables/Vms/multiple.js @@ -17,11 +17,7 @@ const Multiple = ({ tags, limitTags = 1 }) => { )) return ( -
+ <> {Tags} {more > 0 && ( { )} -
+ ) } diff --git a/src/fireedge/src/client/components/Tables/Vms/row.js b/src/fireedge/src/client/components/Tables/Vms/row.js index c3ec3f915c..9f772a13d9 100644 --- a/src/fireedge/src/client/components/Tables/Vms/row.js +++ b/src/fireedge/src/client/components/Tables/Vms/row.js @@ -53,7 +53,11 @@ const Row = ({ original, value, ...props }) => {
- {!!IPS?.length && } + {!!IPS?.length && ( +
+ +
+ )}
) diff --git a/src/fireedge/src/client/components/Tabs/Common/List.js b/src/fireedge/src/client/components/Tabs/Common/List.js new file mode 100644 index 0000000000..b4faa88d3d --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Common/List.js @@ -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 ( + + + {/* TITLE */} + {title && ( + + {Tr(title)} + + )} + {/* LIST */} + {list.map(({ key, value }, idx) => ( + + {Tr(key)} + {value} + + ))} + + + ) +} + +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 diff --git a/src/fireedge/src/client/components/Tabs/Common/Ownership.js b/src/fireedge/src/client/components/Tabs/Common/Ownership.js new file mode 100644 index 0000000000..9f9e9866a5 --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Common/Ownership.js @@ -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 ( + + + + {Tr(T.Ownership)} + + + + {Tr(T.Owner)} + {userName} + + + {Tr(T.Group)} + {groupName} + + + + ) +}) + +Ownership.propTypes = { + userName: PropTypes.string.isRequired, + groupName: PropTypes.string.isRequired +} + +Ownership.displayName = 'Ownership' + +export default Ownership diff --git a/src/fireedge/src/client/components/Tabs/Common/Permission copy.js b/src/fireedge/src/client/components/Tabs/Common/Permission copy.js new file mode 100644 index 0000000000..3e6a5dda7d --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Common/Permission copy.js @@ -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' ? : + + const ConnectionButton = () => ( + } + cy='provider-connection' + handleClick={() => getProviderConnection(ID).then(setShowConnection)} + /> + ) + + return ( + + + + + + {Tr(T.Information)} + + + + {'ID'} + {ID} + + + {Tr(T.Name)} + {NAME} + + + {Tr(T.Description)} + {description} + + + {Tr(T.Provider)} + {providerName} + + + {Tr(T.RegistrationTime)} + + {new Date(time * 1000).toLocaleString()} + + + + + {hasConnection && ( + + + + {Tr(T.Credentials)} + + {!showConnection && } + + + + {Object.entries(connection)?.map(([key, value]) => + typeof value === 'string' && ( + + {key} + + {showConnection?.[key] ?? value} + + + ))} + + + )} + + + + + + {Tr(T.Permissions)} + {Tr(T.Use)} + {Tr(T.Manage)} + {Tr(T.Admin)} + + + + {Tr(T.Owner)} + {isChecked(PERMISSIONS.OWNER_U)} + {isChecked(PERMISSIONS.OWNER_M)} + {isChecked(PERMISSIONS.OWNER_A)} + + + {Tr(T.Group)} + {isChecked(PERMISSIONS.GROUP_U)} + {isChecked(PERMISSIONS.GROUP_M)} + {isChecked(PERMISSIONS.GROUP_A)} + + + {Tr(T.Other)} + {isChecked(PERMISSIONS.OTHER_U)} + {isChecked(PERMISSIONS.OTHER_M)} + {isChecked(PERMISSIONS.OTHER_A)} + + + + + + + {Tr(T.Ownership)} + + + + {Tr(T.Owner)} + {UNAME} + + + {Tr(T.Group)} + {GNAME} + + + + + + ) +}) + +Info.propTypes = { + fetchProps: PropTypes.shape({ + data: PropTypes.object.isRequired + }).isRequired +} + +Info.defaultProps = { + fetchProps: { + data: {} + } +} + +Info.displayName = 'Info' + +export default Info diff --git a/src/fireedge/src/client/components/Tabs/Common/Permissions.js b/src/fireedge/src/client/components/Tabs/Common/Permissions.js new file mode 100644 index 0000000000..5d7fdf66da --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Common/Permissions.js @@ -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 ( + + + + {Tr(T.Permissions)} + {Tr(T.Use)} + {Tr(T.Manage)} + {Tr(T.Admin)} + + {CATEGORIES.map(({ title, category }) => ( + + {/* TITLE */} + {Tr(title)} + + {/* PERMISSIONS */} + {Object.entries(permissions) + .filter(([key, _]) => key.toLowerCase().includes(category)) + .map(([key, permission]) => ( + + : } + handleClick={() => handleChange(key, permission)} + /> + + )) + } + + ))} + + + ) +}) + +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 diff --git a/src/fireedge/src/client/components/Tabs/Common/index.js b/src/fireedge/src/client/components/Tabs/Common/index.js new file mode 100644 index 0000000000..6610dd9676 --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Common/index.js @@ -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 +} diff --git a/src/fireedge/src/client/components/Tabs/Vm/index.js b/src/fireedge/src/client/components/Tabs/Vm/index.js index c1e3f3c06a..7d0cff94fe 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/index.js @@ -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 + return } VmTabs.propTypes = { - data: PropTypes.object.isRequired, - tabs: PropTypes.arrayOf( - PropTypes.string - ).isRequired + data: PropTypes.object.isRequired } VmTabs.displayName = 'VmTabs' diff --git a/src/fireedge/src/client/components/Tabs/Vm/info.js b/src/fireedge/src/client/components/Tabs/Vm/info.js index e2459eb002..7583340100 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/info.js +++ b/src/fireedge/src/client/components/Tabs/Vm/info.js @@ -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: + }, + { + key: [T.Reschedule], + value: Helper.booleanToString(+RESCHED) + }, + { + key: [T.Locked], + value: Helper.levelLockToString(LOCK?.LOCKED) + }, + { + key: [T.IP], + value: ips?.length ? : '--' + }, + { + 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 ( -
-

- - - {`#${ID} - ${NAME}`} - -

-
-

Owner: {UNAME}

-

Group: {GNAME}

-

Reschedule: {Helper.booleanToString(+RESCHED)}

-

Locked: {Helper.levelLockToString(LOCK?.LOCKED)}

-

IP: {ips.join(', ') || '--'}

-

Start time: {Helper.timeToString(STIME)}

-

End time: {Helper.timeToString(ETIME)}

-

Host: {hostId ? `#${hostId} ${hostname}` : ''}

-

Cluster: {clusterId ? `#${clusterId} ${clusterName}` : ''}

-

Deploy ID: {DEPLOY_ID}

-
+
+ + +
) } diff --git a/src/fireedge/src/client/components/Tabs/index.js b/src/fireedge/src/client/components/Tabs/index.js index 43987152ce..922358ec1d 100644 --- a/src/fireedge/src/client/components/Tabs/index.js +++ b/src/fireedge/src/client/components/Tabs/index.js @@ -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 }) => ( ) -const Tabs = ({ tabs = [], renderHiddenTabs = false }) => { +const Tabs = ({ tabs = [], renderHiddenTabs = false, data }) => { const [tabSelected, setTab] = useState(0) const renderTabs = useMemo(() => ( setTab(tab)} > @@ -52,7 +52,7 @@ const Tabs = ({ tabs = [], renderHiddenTabs = false }) => { {renderHiddenTabs ? ( renderAllHiddenTabContents ) : ( - (value ?? idx) === tabSelected)} /> + (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 diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 358b06508f..77c4f31b63 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -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', diff --git a/src/fireedge/src/client/features/Auth/hooks.js b/src/fireedge/src/client/features/Auth/hooks.js index 22c7550fbb..081decf16d 100644 --- a/src/fireedge/src/client/features/Auth/hooks.js +++ b/src/fireedge/src/client/features/Auth/hooks.js @@ -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 = () => { diff --git a/src/fireedge/src/client/features/One/utils.js b/src/fireedge/src/client/features/One/utils.js index 2ae427dc7b..1d76e153ed 100644 --- a/src/fireedge/src/client/features/One/utils.js +++ b/src/fireedge/src/client/features/One/utils.js @@ -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] diff --git a/src/fireedge/src/client/features/One/vm/actions.js b/src/fireedge/src/client/features/One/vm/actions.js index fe9d145fc6..98ed304cf1 100644 --- a/src/fireedge/src/client/features/One/vm/actions.js +++ b/src/fireedge/src/client/features/One/vm/actions.js @@ -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) diff --git a/src/fireedge/src/client/features/One/vm/hooks.js b/src/fireedge/src/client/features/One/vm/hooks.js index 1d72b83bd8..5e2fd7013c 100644 --- a/src/fireedge/src/client/features/One/vm/hooks.js +++ b/src/fireedge/src/client/features/One/vm/hooks.js @@ -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 })) } } diff --git a/src/fireedge/src/client/features/One/vm/services.js b/src/fireedge/src/client/features/One/vm/services.js index 1e7c9d90be..c4f69705ef 100644 --- a/src/fireedge/src/client/features/One/vm/services.js +++ b/src/fireedge/src/client/features/One/vm/services.js @@ -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 } }) diff --git a/src/fireedge/src/client/models/Helper.js b/src/fireedge/src/client/models/Helper.js index aa026d82a7..42a2aec9c0 100644 --- a/src/fireedge/src/client/models/Helper.js +++ b/src/fireedge/src/client/models/Helper.js @@ -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('') +} diff --git a/src/fireedge/src/client/models/Host.js b/src/fireedge/src/client/models/Host.js index 20bdfc510f..7687fbe4d7 100644 --- a/src/fireedge/src/client/models/Host.js +++ b/src/fireedge/src/client/models/Host.js @@ -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, diff --git a/src/fireedge/src/client/utils/helpers.js b/src/fireedge/src/client/utils/helpers.js index a7449b7eac..81b75df312 100644 --- a/src/fireedge/src/client/utils/helpers.js +++ b/src/fireedge/src/client/utils/helpers.js @@ -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) => ({ diff --git a/src/fireedge/src/server/utils/constants/commands/vm.js b/src/fireedge/src/server/utils/constants/commands/vm.js index 40122ff423..0c1b3a5ea6 100644 --- a/src/fireedge/src/server/utils/constants/commands/vm.js +++ b/src/fireedge/src/server/utils/constants/commands/vm.js @@ -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 }