mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-23 22:50:09 +03:00
(cherry picked from commit 869d2a85dbcd35af5f7f65dcc5755ab9f0d844cd)
This commit is contained in:
parent
f5d00c97bf
commit
1c23dfcd7a
3042
src/fireedge/package-lock.json
generated
3042
src/fireedge/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -54,7 +54,6 @@
|
||||
"@babel/preset-react": "7.16.0",
|
||||
"@emotion/react": "11.6.0",
|
||||
"@emotion/styled": "11.6.0",
|
||||
"@hookform/devtools": "4.0.1",
|
||||
"@hookform/resolvers": "2.8.2",
|
||||
"@loadable/babel-plugin": "5.13.2",
|
||||
"@loadable/component": "5.15.0",
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useEffect, useMemo, JSXElementConstructor } from 'react'
|
||||
import { useEffect, useMemo, ReactElement } from 'react'
|
||||
|
||||
import Router from 'client/router'
|
||||
import { ENDPOINTS, PATH } from 'client/apps/provision/routes'
|
||||
@ -21,7 +21,9 @@ import { ENDPOINTS as DEV_ENDPOINTS } from 'client/router/dev'
|
||||
|
||||
import { useGeneral, useGeneralApi } from 'client/features/General'
|
||||
import { useAuth, useAuthApi } from 'client/features/Auth'
|
||||
import { useProvisionTemplate, useProvisionApi } from 'client/features/One'
|
||||
import provisionApi from 'client/features/OneApi/provision'
|
||||
import providerApi from 'client/features/OneApi/provider'
|
||||
import { useSocket } from 'client/hooks'
|
||||
|
||||
import Sidebar from 'client/components/Sidebar'
|
||||
import Notifier from 'client/components/Notifier'
|
||||
@ -31,37 +33,54 @@ import { _APPS } from 'client/constants'
|
||||
|
||||
export const APP_NAME = _APPS.provision.name
|
||||
|
||||
const MESSAGE_PROVISION_SUCCESS_CREATED = 'Provision successfully created'
|
||||
|
||||
/**
|
||||
* Provision App component.
|
||||
*
|
||||
* @returns {JSXElementConstructor} App rendered.
|
||||
* @returns {ReactElement} App rendered.
|
||||
*/
|
||||
const ProvisionApp = () => {
|
||||
const { isLogged, jwt, firstRender, providerConfig } = useAuth()
|
||||
const { getAuthUser, logout, getProviderConfig } = useAuthApi()
|
||||
const { getProvisionSocket } = useSocket()
|
||||
const { isLogged, jwt, firstRender } = useAuth()
|
||||
const { getAuthUser, logout } = useAuthApi()
|
||||
|
||||
const provisionTemplate = useProvisionTemplate()
|
||||
const { getProvisionsTemplates } = useProvisionApi()
|
||||
const { appTitle, zone } = useGeneral()
|
||||
const { changeAppTitle, enqueueSuccess } = useGeneralApi()
|
||||
|
||||
const { appTitle } = useGeneral()
|
||||
const { changeAppTitle } = useGeneralApi()
|
||||
const queryProps = [undefined, { skip: !jwt }]
|
||||
provisionApi.endpoints.getProvisionTemplates.useQuery(...queryProps)
|
||||
providerApi.endpoints.getProviderConfig.useQuery(...queryProps)
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
appTitle !== APP_NAME && changeAppTitle(APP_NAME)
|
||||
|
||||
try {
|
||||
if (jwt) {
|
||||
getAuthUser()
|
||||
!providerConfig && (await getProviderConfig())
|
||||
!provisionTemplate?.length && (await getProvisionsTemplates())
|
||||
}
|
||||
jwt && getAuthUser()
|
||||
} catch {
|
||||
logout()
|
||||
}
|
||||
})()
|
||||
}, [jwt])
|
||||
|
||||
useEffect(() => {
|
||||
if (!jwt || !zone) return
|
||||
|
||||
const socket = getProvisionSocket((payload) => {
|
||||
const { command, data } = payload
|
||||
|
||||
// Dispatch successfully notification when one provision is created
|
||||
if (command === 'create' && data === MESSAGE_PROVISION_SUCCESS_CREATED) {
|
||||
enqueueSuccess(MESSAGE_PROVISION_SUCCESS_CREATED)
|
||||
}
|
||||
})
|
||||
|
||||
socket?.on()
|
||||
|
||||
return () => socket?.off()
|
||||
}, [jwt, zone])
|
||||
|
||||
const endpoints = useMemo(
|
||||
() => [...ENDPOINTS, ...(isDevelopment() ? DEV_ENDPOINTS : [])],
|
||||
[]
|
||||
|
@ -21,7 +21,6 @@ import { StaticRouter, BrowserRouter } from 'react-router-dom'
|
||||
import { Provider as ReduxProvider } from 'react-redux'
|
||||
import { Store } from 'redux'
|
||||
|
||||
import SocketProvider from 'client/providers/socketProvider'
|
||||
import MuiProvider from 'client/providers/muiProvider'
|
||||
import NotistackProvider from 'client/providers/notistackProvider'
|
||||
import { TranslateProvider } from 'client/components/HOC'
|
||||
@ -42,25 +41,23 @@ buildTranslationLocale()
|
||||
*/
|
||||
const Provision = ({ store = {}, location = '', context = {} }) => (
|
||||
<ReduxProvider store={store}>
|
||||
<SocketProvider>
|
||||
<TranslateProvider>
|
||||
<MuiProvider theme={theme}>
|
||||
<NotistackProvider>
|
||||
{location && context ? (
|
||||
// server build
|
||||
<StaticRouter location={location} context={context}>
|
||||
<App />
|
||||
</StaticRouter>
|
||||
) : (
|
||||
// browser build
|
||||
<BrowserRouter basename={`${APP_URL}/${ProvisionAppName}`}>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
)}
|
||||
</NotistackProvider>
|
||||
</MuiProvider>
|
||||
</TranslateProvider>
|
||||
</SocketProvider>
|
||||
<TranslateProvider>
|
||||
<MuiProvider theme={theme}>
|
||||
<NotistackProvider>
|
||||
{location && context ? (
|
||||
// server build
|
||||
<StaticRouter location={location} context={context}>
|
||||
<App />
|
||||
</StaticRouter>
|
||||
) : (
|
||||
// browser build
|
||||
<BrowserRouter basename={`${APP_URL}/${ProvisionAppName}`}>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
)}
|
||||
</NotistackProvider>
|
||||
</MuiProvider>
|
||||
</TranslateProvider>
|
||||
</ReduxProvider>
|
||||
)
|
||||
|
||||
|
@ -26,7 +26,7 @@ import { ENDPOINTS as DEV_ENDPOINTS } from 'client/router/dev'
|
||||
|
||||
import { useGeneral, useGeneralApi } from 'client/features/General'
|
||||
import { useAuth, useAuthApi } from 'client/features/Auth'
|
||||
import { useSystem, useSystemApi } from 'client/features/One'
|
||||
import systemApi from 'client/features/OneApi/system'
|
||||
|
||||
import Sidebar from 'client/components/Sidebar'
|
||||
import Notifier from 'client/components/Notifier'
|
||||
@ -42,26 +42,23 @@ export const APP_NAME = _APPS.sunstone.name
|
||||
* @returns {JSXElementConstructor} App rendered.
|
||||
*/
|
||||
const SunstoneApp = () => {
|
||||
const { isLogged, jwt, firstRender, view, views, config } = useAuth()
|
||||
const { getAuthUser, logout, getSunstoneViews, getSunstoneConfig } =
|
||||
useAuthApi()
|
||||
const { isLogged, jwt, firstRender, view } = useAuth()
|
||||
const { getAuthUser, logout } = useAuthApi()
|
||||
|
||||
const { appTitle } = useGeneral()
|
||||
const { changeAppTitle } = useGeneralApi()
|
||||
const { config: oneConfig } = useSystem()
|
||||
const { getOneConfig } = useSystemApi()
|
||||
|
||||
const queryProps = [undefined, { skip: !jwt }]
|
||||
systemApi.endpoints.getOneConfig.useQuery(...queryProps)
|
||||
systemApi.endpoints.getSunstoneConfig.useQuery(...queryProps)
|
||||
const views = systemApi.endpoints.getSunstoneViews.useQuery(...queryProps)
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
appTitle !== APP_NAME && changeAppTitle(APP_NAME)
|
||||
|
||||
try {
|
||||
if (jwt) {
|
||||
getAuthUser()
|
||||
!view && (await getSunstoneViews())
|
||||
!config && (await getSunstoneConfig())
|
||||
!oneConfig && getOneConfig()
|
||||
}
|
||||
jwt && getAuthUser()
|
||||
} catch {
|
||||
logout()
|
||||
}
|
||||
@ -71,7 +68,7 @@ const SunstoneApp = () => {
|
||||
const endpoints = useMemo(
|
||||
() => [
|
||||
...ENDPOINTS,
|
||||
...(view ? getEndpointsByView(views?.[view], ONE_ENDPOINTS) : []),
|
||||
...(view ? getEndpointsByView(views?.data?.[view], ONE_ENDPOINTS) : []),
|
||||
...(isDevelopment() ? DEV_ENDPOINTS : []),
|
||||
],
|
||||
[view]
|
||||
|
@ -13,11 +13,22 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const SERVICE = 'service'
|
||||
const SERVICE_TEMPLATE = 'service_template'
|
||||
import { SubmitButton } from 'client/components/FormControl'
|
||||
|
||||
module.exports = {
|
||||
SERVICE,
|
||||
SERVICE_TEMPLATE,
|
||||
const QueryButton = memo(({ useQuery, ...props }) => {
|
||||
const { refetch, isFetching } = useQuery?.() ?? {}
|
||||
|
||||
return <SubmitButton isSubmitting={isFetching} onClick={refetch} {...props} />
|
||||
})
|
||||
|
||||
QueryButton.propTypes = {
|
||||
useQuery: PropTypes.func,
|
||||
...SubmitButton.propTypes,
|
||||
}
|
||||
|
||||
QueryButton.displayName = 'QueryButton'
|
||||
|
||||
export default QueryButton
|
@ -18,7 +18,7 @@ import PropTypes from 'prop-types'
|
||||
|
||||
import { Trash, Edit, ClockOutline } from 'iconoir-react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useGetSunstoneConfigQuery } from 'client/features/OneApi/system'
|
||||
import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm'
|
||||
import {
|
||||
CreateCharterForm,
|
||||
@ -28,9 +28,13 @@ import {
|
||||
} from 'client/components/Forms/Vm'
|
||||
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
import { ScheduledAction } from 'client/models/Scheduler'
|
||||
import { sentenceCase } from 'client/utils'
|
||||
import { T, VM_ACTIONS, VM_ACTIONS_IN_CHARTER } from 'client/constants'
|
||||
import {
|
||||
T,
|
||||
VM_ACTIONS,
|
||||
VM_ACTIONS_IN_CHARTER,
|
||||
ScheduleAction,
|
||||
} from 'client/constants'
|
||||
|
||||
/**
|
||||
* Returns a button to trigger form to create a scheduled action.
|
||||
@ -53,7 +57,7 @@ const CreateSchedButton = memo(({ vm, relative, onSubmit }) => (
|
||||
{
|
||||
name: T.PunctualAction,
|
||||
dialogProps: {
|
||||
title: T.ScheduledAction,
|
||||
title: T.ScheduleAction,
|
||||
dataCy: 'modal-sched-actions',
|
||||
},
|
||||
form: () =>
|
||||
@ -71,7 +75,7 @@ const CreateSchedButton = memo(({ vm, relative, onSubmit }) => (
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {object} props.vm - Vm resource
|
||||
* @param {ScheduledAction} props.schedule - Schedule action
|
||||
* @param {ScheduleAction} props.schedule - Schedule action
|
||||
* @param {boolean} [props.relative] - Applies to the form relative format
|
||||
* @param {function():Promise} props.onSubmit - Submit function
|
||||
* @returns {ReactElement} Button
|
||||
@ -91,10 +95,7 @@ const UpdateSchedButton = memo(({ vm, schedule, relative, onSubmit }) => {
|
||||
{
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate
|
||||
word={T.UpdateScheduledAction}
|
||||
values={[titleAction]}
|
||||
/>
|
||||
<Translate word={T.UpdateScheduleAction} values={[titleAction]} />
|
||||
),
|
||||
dataCy: 'modal-sched-actions',
|
||||
},
|
||||
@ -113,7 +114,7 @@ const UpdateSchedButton = memo(({ vm, schedule, relative, onSubmit }) => {
|
||||
* Returns a button to trigger modal to delete a scheduled action.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {ScheduledAction} props.schedule - Schedule action
|
||||
* @param {ScheduleAction} props.schedule - Schedule action
|
||||
* @param {function():Promise} props.onSubmit - Submit function
|
||||
* @returns {ReactElement} Button
|
||||
*/
|
||||
@ -156,7 +157,7 @@ const DeleteSchedButton = memo(({ onSubmit, schedule }) => {
|
||||
* @returns {ReactElement} Button
|
||||
*/
|
||||
const CharterButton = memo(({ relative, onSubmit }) => {
|
||||
const { config } = useAuth()
|
||||
const { data: config } = useGetSunstoneConfigQuery()
|
||||
|
||||
const leases = useMemo(
|
||||
() =>
|
||||
@ -164,7 +165,7 @@ const CharterButton = memo(({ relative, onSubmit }) => {
|
||||
Object.entries(config?.leases ?? {}).filter(([action]) =>
|
||||
VM_ACTIONS_IN_CHARTER.includes(action)
|
||||
),
|
||||
[config.leases]
|
||||
[config?.leases]
|
||||
)
|
||||
|
||||
return (
|
||||
@ -178,7 +179,7 @@ const CharterButton = memo(({ relative, onSubmit }) => {
|
||||
options={[
|
||||
{
|
||||
dialogProps: {
|
||||
title: T.ScheduledAction,
|
||||
title: T.ScheduleAction,
|
||||
dataCy: 'modal-sched-actions',
|
||||
},
|
||||
form: () =>
|
||||
|
@ -13,106 +13,90 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import { memo, ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Typography } from '@mui/material'
|
||||
import makeStyles from '@mui/styles/makeStyles'
|
||||
import { Folder as DatastoreIcon } from 'iconoir-react'
|
||||
import { User, Group, Lock, Cloud, Server } from 'iconoir-react'
|
||||
|
||||
import SelectCard, { Action } from 'client/components/Cards/SelectCard'
|
||||
import {
|
||||
StatusBadge,
|
||||
StatusCircle,
|
||||
StatusChip,
|
||||
LinearProgressWithLabel,
|
||||
} from 'client/components/Status'
|
||||
|
||||
import * as DatastoreModel from 'client/models/Datastore'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
title: {
|
||||
display: 'flex',
|
||||
gap: '0.5rem',
|
||||
},
|
||||
content: {
|
||||
padding: '2em',
|
||||
display: 'flex',
|
||||
flexFlow: 'column',
|
||||
gap: '1em',
|
||||
},
|
||||
})
|
||||
import { getState, getType, getCapacityInfo } from 'client/models/Datastore'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { Datastore } from 'client/constants'
|
||||
|
||||
const DatastoreCard = memo(
|
||||
({ value, isSelected, handleClick, actions }) => {
|
||||
const classes = useStyles()
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {Datastore} props.datastore - Datastore resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @param {ReactElement} props.actions - Actions
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ datastore, rootProps, actions }) => {
|
||||
const classes = rowStyles()
|
||||
|
||||
const { ID, NAME } = value
|
||||
const { ID, NAME, UNAME, GNAME, CLUSTERS, LOCK, PROVISION_ID } = datastore
|
||||
|
||||
const type = DatastoreModel.getType(value)
|
||||
const state = DatastoreModel.getState(value)
|
||||
|
||||
const { percentOfUsed, percentLabel } =
|
||||
DatastoreModel.getCapacityInfo(value)
|
||||
const type = getType(datastore)
|
||||
const { color: stateColor, name: stateName } = getState(datastore)
|
||||
const { percentOfUsed, percentLabel } = getCapacityInfo(datastore)
|
||||
const totalClusters = [CLUSTERS?.ID ?? []].flat().join(',')
|
||||
|
||||
return (
|
||||
<SelectCard
|
||||
action={actions?.map((action) => (
|
||||
<Action key={action?.cy} {...action} />
|
||||
))}
|
||||
icon={
|
||||
<StatusBadge stateColor={state.color}>
|
||||
<DatastoreIcon />
|
||||
</StatusBadge>
|
||||
}
|
||||
title={
|
||||
<span className={classes.title}>
|
||||
<Typography title={NAME} noWrap component="span">
|
||||
{NAME}
|
||||
</Typography>
|
||||
<StatusChip text={type} />
|
||||
</span>
|
||||
}
|
||||
subheader={`#${ID}`}
|
||||
isSelected={isSelected}
|
||||
handleClick={handleClick}
|
||||
>
|
||||
<div className={classes.content}>
|
||||
<div {...rootProps} data-cy={`datastore-${ID}`}>
|
||||
<div>
|
||||
<StatusCircle color={stateColor} tooltip={stateName} />
|
||||
</div>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span">{NAME}</Typography>
|
||||
<span className={classes.labels}>
|
||||
{LOCK && <Lock />}
|
||||
<StatusChip text={type} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${ID}`}</span>
|
||||
<span title={`Owner: ${UNAME}`}>
|
||||
<User />
|
||||
<span>{` ${UNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Group: ${GNAME}`}>
|
||||
<Group />
|
||||
<span>{` ${GNAME}`}</span>
|
||||
</span>
|
||||
{PROVISION_ID && (
|
||||
<span title={`Provision ID: #${PROVISION_ID}`}>
|
||||
<Cloud />
|
||||
<span>{` ${PROVISION_ID}`}</span>
|
||||
</span>
|
||||
)}
|
||||
<span title={`Cluster IDs: ${totalClusters}`}>
|
||||
<Server />
|
||||
<span>{` ${totalClusters}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.secondary}>
|
||||
<LinearProgressWithLabel value={percentOfUsed} label={percentLabel} />
|
||||
</div>
|
||||
</SelectCard>
|
||||
{actions && <div className={classes.actions}>{actions}</div>}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
(prev, next) =>
|
||||
prev.isSelected === next.isSelected &&
|
||||
prev.value?.STATE === next.value?.STATE
|
||||
}
|
||||
)
|
||||
|
||||
DatastoreCard.propTypes = {
|
||||
value: PropTypes.shape({
|
||||
ID: PropTypes.string.isRequired,
|
||||
NAME: PropTypes.string.isRequired,
|
||||
TYPE: PropTypes.string,
|
||||
STATE: PropTypes.string,
|
||||
TOTAL_MB: PropTypes.string,
|
||||
FREE_MB: PropTypes.string,
|
||||
USED_MB: PropTypes.string,
|
||||
datastore: PropTypes.object,
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
isSelected: PropTypes.bool,
|
||||
handleClick: PropTypes.func,
|
||||
actions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
handleClick: PropTypes.func.isRequired,
|
||||
icon: PropTypes.node.isRequired,
|
||||
cy: PropTypes.string,
|
||||
})
|
||||
),
|
||||
}
|
||||
|
||||
DatastoreCard.defaultProps = {
|
||||
value: {},
|
||||
isSelected: false,
|
||||
handleClick: undefined,
|
||||
actions: undefined,
|
||||
actions: PropTypes.any,
|
||||
}
|
||||
|
||||
DatastoreCard.displayName = 'DatastoreCard'
|
||||
|
174
src/fireedge/src/client/components/Cards/DiskCard.js
Normal file
174
src/fireedge/src/client/components/Cards/DiskCard.js
Normal file
@ -0,0 +1,174 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { DatabaseSettings, Folder, ModernTv } from 'iconoir-react'
|
||||
import { Box, Typography, Paper } from '@mui/material'
|
||||
|
||||
import DiskSnapshotCard from 'client/components/Cards/DiskSnapshotCard'
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
|
||||
import { stringToBoolean } from 'client/models/Helper'
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import { Disk } from 'client/constants'
|
||||
|
||||
const DiskCard = memo(
|
||||
({
|
||||
disk = {},
|
||||
actions = [],
|
||||
extraActionProps = {},
|
||||
snapshotActions = [],
|
||||
extraSnapshotActionProps = {},
|
||||
}) => {
|
||||
const classes = rowStyles()
|
||||
|
||||
/** @type {Disk} */
|
||||
const {
|
||||
DISK_ID,
|
||||
DATASTORE,
|
||||
TARGET,
|
||||
IMAGE,
|
||||
TYPE,
|
||||
FORMAT,
|
||||
SIZE,
|
||||
MONITOR_SIZE,
|
||||
READONLY,
|
||||
PERSISTENT,
|
||||
SAVE,
|
||||
CLONE,
|
||||
IS_CONTEXT,
|
||||
SNAPSHOTS,
|
||||
} = disk
|
||||
|
||||
const size = +SIZE ? prettyBytes(+SIZE, 'MB') : '-'
|
||||
const monitorSize = +MONITOR_SIZE ? prettyBytes(+MONITOR_SIZE, 'MB') : '-'
|
||||
|
||||
const type = String(TYPE).toLowerCase()
|
||||
|
||||
const image =
|
||||
IMAGE ??
|
||||
{
|
||||
fs: `${FORMAT} - ${size}`,
|
||||
swap: size,
|
||||
}[type]
|
||||
|
||||
const labels = useMemo(
|
||||
() =>
|
||||
[
|
||||
{ label: TYPE, dataCy: 'type' },
|
||||
{
|
||||
label: stringToBoolean(PERSISTENT) && 'PERSISTENT',
|
||||
dataCy: 'persistent',
|
||||
},
|
||||
{
|
||||
label: stringToBoolean(READONLY) && 'READONLY',
|
||||
dataCy: 'readonly',
|
||||
},
|
||||
{
|
||||
label: stringToBoolean(SAVE) && 'SAVE',
|
||||
dataCy: 'save',
|
||||
},
|
||||
{
|
||||
label: stringToBoolean(CLONE) && 'CLONE',
|
||||
dataCy: 'clone',
|
||||
},
|
||||
].filter(({ label } = {}) => Boolean(label)),
|
||||
[TYPE, PERSISTENT, READONLY, SAVE, CLONE]
|
||||
)
|
||||
|
||||
return (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
className={classes.root}
|
||||
sx={{ flexWrap: 'wrap' }}
|
||||
data-cy={`disk-${DISK_ID}`}
|
||||
>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span" data-cy="name">
|
||||
{image}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
{labels.map(({ label, dataCy }) => (
|
||||
<StatusChip
|
||||
key={label}
|
||||
text={label}
|
||||
{...(dataCy && { dataCy: dataCy })}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${DISK_ID}`}</span>
|
||||
{TARGET && (
|
||||
<span title={`Target: ${TARGET}`}>
|
||||
<DatabaseSettings />
|
||||
<span data-cy="target">{` ${TARGET}`}</span>
|
||||
</span>
|
||||
)}
|
||||
{DATASTORE && (
|
||||
<span title={`Datastore Name: ${DATASTORE}`}>
|
||||
<Folder />
|
||||
<span data-cy="datastore">{` ${DATASTORE}`}</span>
|
||||
</span>
|
||||
)}
|
||||
<span title={`Monitor Size / Disk Size: ${monitorSize}/${size}`}>
|
||||
<ModernTv />
|
||||
<span data-cy="monitorsize">{` ${monitorSize}/${size}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!IS_CONTEXT && !!actions.length && (
|
||||
<div className={classes.actions}>
|
||||
{actions.map((Action, idx) => (
|
||||
<Action
|
||||
key={`${Action.displayName ?? idx}-${DISK_ID}`}
|
||||
{...extraActionProps}
|
||||
name={image}
|
||||
disk={disk}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!!SNAPSHOTS?.length && (
|
||||
<Box flexBasis="100%">
|
||||
{SNAPSHOTS?.map((snapshot) => (
|
||||
<DiskSnapshotCard
|
||||
key={`${DISK_ID}-${snapshot.ID}`}
|
||||
snapshot={snapshot}
|
||||
actions={snapshotActions}
|
||||
extraActionProps={extraSnapshotActionProps}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
DiskCard.propTypes = {
|
||||
disk: PropTypes.object.isRequired,
|
||||
actions: PropTypes.any,
|
||||
extraActionProps: PropTypes.object,
|
||||
extraSnapshotActionProps: PropTypes.object,
|
||||
snapshotActions: PropTypes.any,
|
||||
}
|
||||
|
||||
DiskCard.displayName = 'DiskCard'
|
||||
|
||||
export default DiskCard
|
94
src/fireedge/src/client/components/Cards/DiskSnapshotCard.js
Normal file
94
src/fireedge/src/client/components/Cards/DiskSnapshotCard.js
Normal file
@ -0,0 +1,94 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ModernTv } from 'iconoir-react'
|
||||
import { Typography, Paper } from '@mui/material'
|
||||
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
|
||||
import * as Helper from 'client/models/Helper'
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import { T, DiskSnapshot } from 'client/constants'
|
||||
|
||||
const DiskSnapshotCard = memo(
|
||||
({ snapshot = {}, actions = [], extraActionProps = {} }) => {
|
||||
const classes = rowStyles()
|
||||
|
||||
/** @type {DiskSnapshot} */
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
ACTIVE,
|
||||
DATE,
|
||||
SIZE: SNAPSHOT_SIZE,
|
||||
MONITOR_SIZE: SNAPSHOT_MONITOR_SIZE,
|
||||
} = snapshot
|
||||
|
||||
const isActive = Helper.stringToBoolean(ACTIVE)
|
||||
const time = Helper.timeFromMilliseconds(+DATE)
|
||||
const timeAgo = `created ${time.toRelative()}`
|
||||
|
||||
const size = +SNAPSHOT_SIZE ? prettyBytes(+SNAPSHOT_SIZE, 'MB') : '-'
|
||||
const monitorSize = +SNAPSHOT_MONITOR_SIZE
|
||||
? prettyBytes(+SNAPSHOT_MONITOR_SIZE, 'MB')
|
||||
: '-'
|
||||
|
||||
return (
|
||||
<Paper variant="outlined" className={classes.root}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span">{NAME}</Typography>
|
||||
<span className={classes.labels}>
|
||||
{isActive && <StatusChip text={<Translate word={T.Active} />} />}
|
||||
<StatusChip text={<Translate word={T.Snapshot} />} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span title={time.toFormat('ff')}>{`#${ID} ${timeAgo}`}</span>
|
||||
<span title={`Monitor Size / Disk Size: ${monitorSize}/${size}`}>
|
||||
<ModernTv />
|
||||
<span>{` ${monitorSize}/${size}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!!actions.length && (
|
||||
<div className={classes.actions}>
|
||||
{actions.map((Action, idx) => (
|
||||
<Action
|
||||
key={`${Action.displayName ?? idx}-${ID}`}
|
||||
{...extraActionProps}
|
||||
snapshot={snapshot}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
DiskSnapshotCard.propTypes = {
|
||||
snapshot: PropTypes.object.isRequired,
|
||||
extraActionProps: PropTypes.object,
|
||||
actions: PropTypes.arrayOf(PropTypes.string),
|
||||
}
|
||||
|
||||
DiskSnapshotCard.displayName = 'DiskSnapshotCard'
|
||||
|
||||
export default DiskSnapshotCard
|
@ -13,119 +13,96 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import { memo, ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Server, ModernTv } from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
import makeStyles from '@mui/styles/makeStyles'
|
||||
import { HardDrive as HostIcon } from 'iconoir-react'
|
||||
|
||||
import SelectCard, { Action } from 'client/components/Cards/SelectCard'
|
||||
import {
|
||||
StatusBadge,
|
||||
StatusCircle,
|
||||
StatusChip,
|
||||
LinearProgressWithLabel,
|
||||
} from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
|
||||
import * as HostModel from 'client/models/Host'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
title: {
|
||||
display: 'flex',
|
||||
gap: '0.5rem',
|
||||
},
|
||||
content: {
|
||||
padding: '2em',
|
||||
display: 'flex',
|
||||
flexFlow: 'column',
|
||||
gap: '1em',
|
||||
},
|
||||
})
|
||||
import { getAllocatedInfo, getState } from 'client/models/Host'
|
||||
import { T, Host } from 'client/constants'
|
||||
|
||||
const HostCard = memo(
|
||||
({ value, isSelected, handleClick, actions }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const { ID, NAME, IM_MAD, VM_MAD } = value
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {Host} props.host - Host resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @param {ReactElement} props.actions - Actions
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ host, rootProps, actions }) => {
|
||||
const classes = rowStyles()
|
||||
const { ID, NAME, IM_MAD, VM_MAD, HOST_SHARE, CLUSTER, TEMPLATE } = host
|
||||
|
||||
const { percentCpuUsed, percentCpuLabel, percentMemUsed, percentMemLabel } =
|
||||
HostModel.getAllocatedInfo(value)
|
||||
getAllocatedInfo(host)
|
||||
|
||||
const state = HostModel.getState(value)
|
||||
const runningVms = HOST_SHARE?.RUNNING_VMS || 0
|
||||
const totalVms = [host?.VMS?.ID ?? []].flat().length || 0
|
||||
const { color: stateColor, name: stateName } = getState(host)
|
||||
|
||||
const mad = IM_MAD === VM_MAD ? IM_MAD : `${IM_MAD}/${VM_MAD}`
|
||||
const labels = [...new Set([IM_MAD, VM_MAD])]
|
||||
|
||||
return (
|
||||
<SelectCard
|
||||
action={actions?.map((action) => (
|
||||
<Action key={action?.cy} {...action} />
|
||||
))}
|
||||
icon={
|
||||
<StatusBadge title={state?.name} stateColor={state.color}>
|
||||
<HostIcon />
|
||||
</StatusBadge>
|
||||
}
|
||||
title={
|
||||
<span className={classes.title}>
|
||||
<Typography title={NAME} noWrap component="span">
|
||||
{NAME}
|
||||
<div {...rootProps} data-cy={`host-${ID}`}>
|
||||
<div>
|
||||
<StatusCircle color={stateColor} tooltip={stateName} />
|
||||
</div>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography noWrap component="span">
|
||||
{TEMPLATE?.NAME ?? NAME}
|
||||
</Typography>
|
||||
<StatusChip text={mad} />
|
||||
</span>
|
||||
}
|
||||
subheader={`#${ID}`}
|
||||
isSelected={isSelected}
|
||||
handleClick={handleClick}
|
||||
>
|
||||
<div className={classes.content}>
|
||||
<span className={classes.labels}>
|
||||
{labels.map((label) => (
|
||||
<StatusChip key={label} text={label} />
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${ID}`}</span>
|
||||
<span data-cy="cluster" title={`Cluster: ${CLUSTER}`}>
|
||||
<Server />
|
||||
<span>{` ${CLUSTER}`}</span>
|
||||
</span>
|
||||
<span title={`Running VMs: ${runningVms} / ${totalVms}`}>
|
||||
<ModernTv />
|
||||
<span>{` ${runningVms} / ${totalVms}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.secondary}>
|
||||
<LinearProgressWithLabel
|
||||
value={percentCpuUsed}
|
||||
label={percentCpuLabel}
|
||||
title={`${Tr(T.AllocatedCpu)}`}
|
||||
/>
|
||||
<LinearProgressWithLabel
|
||||
value={percentMemUsed}
|
||||
label={percentMemLabel}
|
||||
title={`${Tr(T.AllocatedMemory)}`}
|
||||
/>
|
||||
</div>
|
||||
</SelectCard>
|
||||
{actions && <div className={classes.actions}>{actions}</div>}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
(prev, next) =>
|
||||
prev.isSelected === next.isSelected &&
|
||||
prev.value?.STATE === next.value?.STATE
|
||||
}
|
||||
)
|
||||
|
||||
HostCard.propTypes = {
|
||||
value: PropTypes.shape({
|
||||
ID: PropTypes.string.isRequired,
|
||||
NAME: PropTypes.string.isRequired,
|
||||
TYPE: PropTypes.string,
|
||||
STATE: PropTypes.string,
|
||||
IM_MAD: PropTypes.string,
|
||||
VM_MAD: PropTypes.string,
|
||||
HOST_SHARE: PropTypes.shape({
|
||||
CPU_USAGE: PropTypes.string,
|
||||
TOTAL_CPU: PropTypes.string,
|
||||
MEM_USAGE: PropTypes.string,
|
||||
TOTAL_MEM: PropTypes.string,
|
||||
}),
|
||||
host: PropTypes.object,
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
isSelected: PropTypes.bool,
|
||||
handleClick: PropTypes.func,
|
||||
actions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
handleClick: PropTypes.func.isRequired,
|
||||
icon: PropTypes.node.isRequired,
|
||||
cy: PropTypes.string,
|
||||
})
|
||||
),
|
||||
}
|
||||
|
||||
HostCard.defaultProps = {
|
||||
value: {},
|
||||
isSelected: false,
|
||||
handleClick: undefined,
|
||||
actions: undefined,
|
||||
actions: PropTypes.any,
|
||||
}
|
||||
|
||||
HostCard.displayName = 'HostCard'
|
||||
|
@ -13,73 +13,82 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import { memo, ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Typography } from '@mui/material'
|
||||
import { User, Group, Lock, Server, Cloud } from 'iconoir-react'
|
||||
|
||||
import { NetworkAlt as NetworkIcon } from 'iconoir-react'
|
||||
|
||||
import SelectCard, { Action } from 'client/components/Cards/SelectCard'
|
||||
import { LinearProgressWithLabel } from 'client/components/Status'
|
||||
import { getLeasesInfo, getTotalLeases } from 'client/models/VirtualNetwork'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
|
||||
const NetworkCard = memo(
|
||||
({ value, isSelected, handleClick, actions }) => {
|
||||
const { ID, NAME, USED_LEASES = '', AR_POOL } = value
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {object} props.network - Network resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @param {ReactElement} props.actions - Actions
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ network, rootProps, actions }) => {
|
||||
const classes = rowStyles()
|
||||
|
||||
const addresses = [AR_POOL?.AR ?? []].flat()
|
||||
const totalLeases = addresses.reduce((res, ar) => +ar.SIZE + res, 0)
|
||||
const { ID, NAME, UNAME, GNAME, LOCK, CLUSTERS, USED_LEASES, TEMPLATE } =
|
||||
network
|
||||
|
||||
const percentOfUsed = (+USED_LEASES * 100) / +totalLeases || 0
|
||||
const percentLabel = `${USED_LEASES} / ${totalLeases} (${Math.round(
|
||||
percentOfUsed
|
||||
)}%)`
|
||||
const totalLeases = getTotalLeases(network)
|
||||
const { percentOfUsed, percentLabel } = getLeasesInfo(network)
|
||||
const totalClusters = [CLUSTERS?.ID ?? []].flat().length || 0
|
||||
const provisionId = TEMPLATE?.PROVISION?.ID
|
||||
|
||||
return (
|
||||
<SelectCard
|
||||
action={actions?.map((action) => (
|
||||
<Action key={action?.cy} {...action} />
|
||||
))}
|
||||
icon={<NetworkIcon />}
|
||||
title={NAME}
|
||||
subheader={`#${ID}`}
|
||||
isSelected={isSelected}
|
||||
handleClick={handleClick}
|
||||
>
|
||||
<div style={{ padding: '2em' }}>
|
||||
<LinearProgressWithLabel value={percentOfUsed} label={percentLabel} />
|
||||
<div {...rootProps} data-cy={`network-${ID}`}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span">{NAME}</Typography>
|
||||
<span className={classes.labels}>{LOCK && <Lock />}</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${ID}`}</span>
|
||||
<span title={`Owner: ${UNAME}`}>
|
||||
<User />
|
||||
<span>{` ${UNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Group: ${GNAME}`}>
|
||||
<Group />
|
||||
<span>{` ${GNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Total Clusters: ${totalClusters}`}>
|
||||
<Server />
|
||||
<span>{` ${totalClusters}`}</span>
|
||||
</span>
|
||||
{provisionId && (
|
||||
<span title={`Provision ID: #${provisionId}`}>
|
||||
<Cloud />
|
||||
<span>{` ${provisionId}`}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</SelectCard>
|
||||
<div className={classes.secondary}>
|
||||
<LinearProgressWithLabel
|
||||
title={`Used / Total Leases: ${USED_LEASES} / ${totalLeases}`}
|
||||
value={percentOfUsed}
|
||||
label={percentLabel}
|
||||
/>
|
||||
</div>
|
||||
{actions && <div className={classes.actions}>{actions}</div>}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
(prev, next) => prev.isSelected === next.isSelected
|
||||
}
|
||||
)
|
||||
|
||||
NetworkCard.propTypes = {
|
||||
value: PropTypes.shape({
|
||||
ID: PropTypes.string.isRequired,
|
||||
NAME: PropTypes.string.isRequired,
|
||||
TYPE: PropTypes.string,
|
||||
STATE: PropTypes.string,
|
||||
USED_LEASES: PropTypes.string,
|
||||
AR_POOL: PropTypes.shape({
|
||||
AR: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||
}),
|
||||
network: PropTypes.object,
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
isSelected: PropTypes.bool,
|
||||
handleClick: PropTypes.func,
|
||||
actions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
handleClick: PropTypes.func.isRequired,
|
||||
icon: PropTypes.node.isRequired,
|
||||
cy: PropTypes.string,
|
||||
})
|
||||
),
|
||||
}
|
||||
|
||||
NetworkCard.defaultProps = {
|
||||
value: {},
|
||||
isSelected: false,
|
||||
handleClick: undefined,
|
||||
actions: undefined,
|
||||
actions: PropTypes.any,
|
||||
}
|
||||
|
||||
NetworkCard.displayName = 'NetworkCard'
|
||||
|
161
src/fireedge/src/client/components/Cards/NicCard.js
Normal file
161
src/fireedge/src/client/components/Cards/NicCard.js
Normal file
@ -0,0 +1,161 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
useMediaQuery,
|
||||
Typography,
|
||||
Box,
|
||||
Paper,
|
||||
Stack,
|
||||
Divider,
|
||||
} from '@mui/material'
|
||||
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import SecurityGroupCard from 'client/components/Cards/SecurityGroupCard'
|
||||
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T, Nic, NicAlias } from 'client/constants'
|
||||
|
||||
const NicCard = memo(
|
||||
({
|
||||
nic = {},
|
||||
actions = [],
|
||||
extraActionProps = {},
|
||||
aliasActions = [],
|
||||
extraAliasActionProps = {},
|
||||
}) => {
|
||||
const classes = rowStyles()
|
||||
const isMobile = useMediaQuery((theme) => theme.breakpoints.down('md'))
|
||||
|
||||
/** @type {Nic|NicAlias} */
|
||||
const {
|
||||
NIC_ID,
|
||||
NETWORK = '-',
|
||||
IP,
|
||||
MAC,
|
||||
PCI_ID,
|
||||
PARENT,
|
||||
ADDRESS,
|
||||
ALIAS,
|
||||
SECURITY_GROUPS,
|
||||
} = nic
|
||||
|
||||
const isAlias = !!PARENT?.length
|
||||
const isPciDevice = PCI_ID !== undefined
|
||||
|
||||
const dataCy = isAlias ? 'alias' : 'nic'
|
||||
const tags = [
|
||||
{ text: IP, dataCy: `${dataCy}-ip` },
|
||||
{ text: MAC, dataCy: `${dataCy}-mac` },
|
||||
{ text: ADDRESS, dataCy: `${dataCy}-address` },
|
||||
].filter(({ text } = {}) => Boolean(text))
|
||||
|
||||
return (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
className={classes.root}
|
||||
data-cy={`${dataCy}-${NIC_ID}`}
|
||||
sx={{
|
||||
flexWrap: 'wrap',
|
||||
...(isAlias && { boxShadow: 'none !important' }),
|
||||
}}
|
||||
>
|
||||
<Box className={classes.main} {...(!isAlias && { pl: '1em' })}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span" data-cy={`${dataCy}-name`}>
|
||||
{`${NIC_ID} | ${NETWORK}`}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
<MultipleTags
|
||||
clipboard
|
||||
limitTags={isMobile ? 1 : 3}
|
||||
tags={tags}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</Box>
|
||||
{!isMobile &&
|
||||
!isPciDevice &&
|
||||
actions.map((Action, idx) => (
|
||||
<Action
|
||||
key={`${Action.displayName ?? idx}-${NIC_ID}`}
|
||||
{...extraActionProps}
|
||||
nic={nic}
|
||||
/>
|
||||
))}
|
||||
{!!ALIAS?.length && (
|
||||
<Box flexBasis="100%">
|
||||
{ALIAS?.map((alias) => (
|
||||
<NicCard
|
||||
key={alias.NIC_ID}
|
||||
nic={alias}
|
||||
actions={aliasActions}
|
||||
extraActionProps={extraAliasActionProps}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{Array.isArray(SECURITY_GROUPS) && !!SECURITY_GROUPS?.length && (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexBasis: '100%',
|
||||
flexDirection: 'column',
|
||||
gap: '0.5em',
|
||||
p: '0.8em',
|
||||
}}
|
||||
>
|
||||
<Typography variant="body1">
|
||||
<Translate word={T.SecurityGroups} />
|
||||
</Typography>
|
||||
|
||||
<Stack direction="column" divider={<Divider />} spacing={1}>
|
||||
{SECURITY_GROUPS?.map((securityGroup, idx) => {
|
||||
const key = `nic${NIC_ID}-${idx}-${securityGroup.NAME}`
|
||||
|
||||
return (
|
||||
<SecurityGroupCard
|
||||
key={key}
|
||||
data-cy={key}
|
||||
securityGroup={securityGroup}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
NicCard.propTypes = {
|
||||
nic: PropTypes.object,
|
||||
actions: PropTypes.array,
|
||||
extraActionProps: PropTypes.object,
|
||||
aliasActions: PropTypes.array,
|
||||
extraAliasActionProps: PropTypes.object,
|
||||
}
|
||||
|
||||
NicCard.displayName = 'NicCard'
|
||||
|
||||
NicCard.displayName = 'NicCard'
|
||||
|
||||
export default NicCard
|
@ -110,7 +110,7 @@ ProvisionCard.propTypes = {
|
||||
handleClick: PropTypes.func,
|
||||
isProvider: PropTypes.bool,
|
||||
image: PropTypes.string,
|
||||
deleteAction: PropTypes.func,
|
||||
deleteAction: PropTypes.object,
|
||||
actions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
handleClick: PropTypes.func.isRequired,
|
||||
|
@ -20,7 +20,6 @@ import { useTheme, Typography, Paper, Stack } from '@mui/material'
|
||||
|
||||
import Timer from 'client/components/Timer'
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { UpdateSchedButton, DeleteSchedButton } from 'client/components/Buttons'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
|
||||
import {
|
||||
@ -32,92 +31,77 @@ import { timeFromMilliseconds } from 'client/models/Helper'
|
||||
import { sentenceCase } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const ScheduleActionCard = memo(
|
||||
({ vm, schedule, handleRemove, handleUpdate }) => {
|
||||
const classes = rowStyles()
|
||||
const { palette } = useTheme()
|
||||
const ScheduleActionCard = memo(({ schedule, actions }) => {
|
||||
const classes = rowStyles()
|
||||
const { palette } = useTheme()
|
||||
|
||||
const { ID, ACTION, TIME, MESSAGE, DONE, WARNING } = schedule
|
||||
const { ID, ACTION, TIME, MESSAGE, DONE, WARNING } = schedule
|
||||
|
||||
const titleAction = `#${ID} ${sentenceCase(ACTION)}`
|
||||
const timeIsRelative = isRelative(TIME)
|
||||
const titleAction = `#${ID} ${sentenceCase(ACTION)}`
|
||||
const timeIsRelative = isRelative(TIME)
|
||||
|
||||
const time = timeIsRelative ? getPeriodicityByTimeInSeconds(TIME) : TIME
|
||||
const formatTime =
|
||||
!timeIsRelative && timeFromMilliseconds(+TIME).toFormat('ff')
|
||||
const formatDoneTime = DONE && timeFromMilliseconds(+DONE).toFormat('ff')
|
||||
const time = timeIsRelative ? getPeriodicityByTimeInSeconds(TIME) : TIME
|
||||
const formatTime =
|
||||
!timeIsRelative && timeFromMilliseconds(+TIME).toFormat('ff')
|
||||
const formatDoneTime = DONE && timeFromMilliseconds(+DONE).toFormat('ff')
|
||||
|
||||
const { repeat, end } = getRepeatInformation(schedule)
|
||||
const { repeat, end } = getRepeatInformation(schedule)
|
||||
|
||||
const noMore = !repeat && DONE
|
||||
// const timeIsPast = new Date(+TIME * 1000) < new Date()
|
||||
const noMore = !repeat && DONE
|
||||
|
||||
return (
|
||||
<Paper variant="outlined" className={classes.root}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span">{titleAction}</Typography>
|
||||
{MESSAGE && (
|
||||
<span className={classes.labels}>
|
||||
<StatusChip text={MESSAGE} />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Stack
|
||||
mt={0.5}
|
||||
spacing={2}
|
||||
alignItems="center"
|
||||
flexWrap="wrap"
|
||||
direction="row"
|
||||
>
|
||||
{repeat && <Typography variant="caption">{repeat}</Typography>}
|
||||
{end && <Typography variant="caption">{end}</Typography>}
|
||||
{DONE && (
|
||||
<Typography variant="caption" title={formatDoneTime}>
|
||||
<Timer initial={DONE} translateWord={T.DoneAgo} />
|
||||
</Typography>
|
||||
)}
|
||||
{!noMore && (
|
||||
<>
|
||||
<Typography variant="caption">
|
||||
{timeIsRelative ? (
|
||||
<span>{Object.values(time).join(' ')}</span>
|
||||
) : (
|
||||
<span title={formatTime}>
|
||||
<Timer initial={TIME} translateWord={T.FirstTime} />
|
||||
</span>
|
||||
)}
|
||||
</Typography>
|
||||
{WARNING && <WarningIcon color={palette.warning.main} />}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
return (
|
||||
<Paper variant="outlined" className={classes.root}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span">{titleAction}</Typography>
|
||||
{MESSAGE && (
|
||||
<span className={classes.labels}>
|
||||
<StatusChip text={MESSAGE} />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{(handleUpdate || handleRemove) && (
|
||||
<div className={classes.actions}>
|
||||
{!noMore && handleUpdate && (
|
||||
<UpdateSchedButton
|
||||
vm={vm}
|
||||
relative={timeIsRelative}
|
||||
schedule={schedule}
|
||||
onSubmit={handleUpdate}
|
||||
/>
|
||||
)}
|
||||
{handleRemove && (
|
||||
<DeleteSchedButton onSubmit={handleRemove} schedule={schedule} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
)
|
||||
<Stack
|
||||
mt={0.5}
|
||||
spacing={2}
|
||||
alignItems="center"
|
||||
flexWrap="wrap"
|
||||
direction="row"
|
||||
>
|
||||
{repeat && <Typography variant="caption">{repeat}</Typography>}
|
||||
{end && <Typography variant="caption">{end}</Typography>}
|
||||
{DONE && (
|
||||
<Typography variant="caption" title={formatDoneTime}>
|
||||
<Timer initial={DONE} translateWord={T.DoneAgo} />
|
||||
</Typography>
|
||||
)}
|
||||
{!noMore && (
|
||||
<>
|
||||
<Typography variant="caption">
|
||||
{timeIsRelative ? (
|
||||
<span>{Object.values(time).join(' ')}</span>
|
||||
) : (
|
||||
<span title={formatTime}>
|
||||
<Timer initial={TIME} translateWord={T.FirstTime} />
|
||||
</span>
|
||||
)}
|
||||
</Typography>
|
||||
{WARNING && <WarningIcon color={palette.warning.main} />}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
{actions && (
|
||||
<div className={classes.actions}>
|
||||
{typeof actions === 'function' ? actions({ noMore }) : actions}
|
||||
</div>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
})
|
||||
|
||||
ScheduleActionCard.propTypes = {
|
||||
vm: PropTypes.object,
|
||||
schedule: PropTypes.object.isRequired,
|
||||
handleRemove: PropTypes.func,
|
||||
handleUpdate: PropTypes.func,
|
||||
actions: PropTypes.any,
|
||||
}
|
||||
|
||||
ScheduleActionCard.displayName = 'ScheduleActionCard'
|
||||
|
@ -16,82 +16,45 @@
|
||||
import { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { styled, useMediaQuery } from '@mui/material'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { useMediaQuery, Typography } from '@mui/material'
|
||||
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { SecurityGroup } from 'client/constants'
|
||||
|
||||
const DATACY_SECGROUP = 'securitygroup-'
|
||||
|
||||
const Row = styled('div')({
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
gap: '0.5em',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'nowrap',
|
||||
})
|
||||
|
||||
const Labels = styled('span')({
|
||||
display: 'inline-flex',
|
||||
gap: '0.5em',
|
||||
alignItems: 'center',
|
||||
})
|
||||
|
||||
const SecGroup = memo(({ index, securityGroup }) => {
|
||||
const SecurityGroupCard = memo(({ securityGroup, ...props }) => {
|
||||
const classes = rowStyles()
|
||||
const isMobile = useMediaQuery((theme) => theme.breakpoints.down('md'))
|
||||
|
||||
/** @type {SecurityGroup} */
|
||||
const { ID, NAME, PROTOCOL, RULE_TYPE, ICMP_TYPE, RANGE, NETWORK_ID } =
|
||||
securityGroup
|
||||
|
||||
const tags = [
|
||||
{
|
||||
text: PROTOCOL,
|
||||
dataCy: `${DATACY_SECGROUP}protocol`,
|
||||
},
|
||||
{
|
||||
text: RULE_TYPE,
|
||||
dataCy: `${DATACY_SECGROUP}ruletype`,
|
||||
},
|
||||
{
|
||||
text: RANGE,
|
||||
dataCy: `${DATACY_SECGROUP}range`,
|
||||
},
|
||||
{
|
||||
text: NETWORK_ID,
|
||||
dataCy: `${DATACY_SECGROUP}networkid`,
|
||||
},
|
||||
{
|
||||
text: ICMP_TYPE,
|
||||
dataCy: `${DATACY_SECGROUP}icmp_type`,
|
||||
},
|
||||
{ text: PROTOCOL, dataCy: 'protocol' },
|
||||
{ text: RULE_TYPE, dataCy: 'ruletype' },
|
||||
{ text: RANGE, dataCy: 'range' },
|
||||
{ text: NETWORK_ID, dataCy: 'networkid' },
|
||||
{ text: ICMP_TYPE, dataCy: 'icmp-type' },
|
||||
].filter(({ text } = {}) => Boolean(text))
|
||||
|
||||
return (
|
||||
<Row data-cy={`${DATACY_SECGROUP}${index}`}>
|
||||
<Typography noWrap variant="body2" data-cy={`${DATACY_SECGROUP}name`}>
|
||||
<div data-cy={props['data-cy']} className={classes.title}>
|
||||
<Typography noWrap component="span" data-cy="name" variant="body2">
|
||||
{`${ID} | ${NAME}`}
|
||||
</Typography>
|
||||
<Labels>
|
||||
<span className={classes.labels}>
|
||||
<MultipleTags limitTags={isMobile ? 2 : 5} tags={tags} />
|
||||
</Labels>
|
||||
</Row>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
SecGroup.displayName = 'SecGroup'
|
||||
|
||||
SecGroup.propTypes = {
|
||||
index: PropTypes.number,
|
||||
securityGroup: PropTypes.shape({
|
||||
ID: PropTypes.string,
|
||||
SECURITY_GROUP_ID: PropTypes.string,
|
||||
NAME: PropTypes.string,
|
||||
PROTOCOL: PropTypes.string,
|
||||
RULE_TYPE: PropTypes.string,
|
||||
ICMP_TYPE: PropTypes.string,
|
||||
RANGE: PropTypes.string,
|
||||
NETWORK_ID: PropTypes.string,
|
||||
}),
|
||||
SecurityGroupCard.propTypes = {
|
||||
securityGroup: PropTypes.object,
|
||||
'data-cy': PropTypes.string,
|
||||
}
|
||||
|
||||
export default SecGroup
|
||||
SecurityGroupCard.displayName = 'SecurityGroupCard'
|
||||
|
||||
export default SecurityGroupCard
|
@ -13,55 +13,58 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Typography, Paper } from '@mui/material'
|
||||
|
||||
import * as Actions from 'client/components/Tabs/Vm/Snapshot/Actions'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
|
||||
import * as Helper from 'client/models/Helper'
|
||||
import { VM_ACTIONS } from 'client/constants'
|
||||
import { Snapshot } from 'client/constants'
|
||||
|
||||
const SnapshotItem = ({ snapshot, actions = [] }) => {
|
||||
const classes = rowStyles()
|
||||
const SnapshotCard = memo(
|
||||
({ snapshot, actions = [], extraActionProps = {} }) => {
|
||||
const classes = rowStyles()
|
||||
|
||||
const { SNAPSHOT_ID, NAME, TIME } = snapshot
|
||||
/** @type {Snapshot} */
|
||||
const { SNAPSHOT_ID, NAME, TIME } = snapshot
|
||||
|
||||
const time = Helper.timeFromMilliseconds(+TIME)
|
||||
const timeAgo = `created ${time.toRelative()}`
|
||||
const time = Helper.timeFromMilliseconds(+TIME)
|
||||
const timeAgo = `created ${time.toRelative()}`
|
||||
|
||||
return (
|
||||
<Paper variant="outlined" className={classes.root}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span">{NAME}</Typography>
|
||||
return (
|
||||
<Paper variant="outlined" className={classes.root}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span">{NAME}</Typography>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span title={time.toFormat('ff')}>
|
||||
{`#${SNAPSHOT_ID} ${timeAgo}`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span title={time.toFormat('ff')}>
|
||||
{`#${SNAPSHOT_ID} ${timeAgo}`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!!actions.length && (
|
||||
<div className={classes.actions}>
|
||||
{actions?.includes?.(VM_ACTIONS.SNAPSHOT_REVERT) && (
|
||||
<Actions.RevertAction snapshot={snapshot} />
|
||||
)}
|
||||
{actions?.includes?.(VM_ACTIONS.SNAPSHOT_DELETE) && (
|
||||
<Actions.DeleteAction snapshot={snapshot} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
{!!actions.length && (
|
||||
<div className={classes.actions}>
|
||||
{actions.map((Action, idx) => (
|
||||
<Action
|
||||
key={`${Action.displayName ?? idx}-${SNAPSHOT_ID}`}
|
||||
{...extraActionProps}
|
||||
snapshot={snapshot}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
SnapshotItem.propTypes = {
|
||||
SnapshotCard.propTypes = {
|
||||
snapshot: PropTypes.object.isRequired,
|
||||
actions: PropTypes.arrayOf(PropTypes.string),
|
||||
actions: PropTypes.array,
|
||||
extraActionProps: PropTypes.object,
|
||||
}
|
||||
|
||||
SnapshotItem.displayName = 'SnapshotItem'
|
||||
SnapshotCard.displayName = 'SnapshotCard'
|
||||
|
||||
export default SnapshotItem
|
||||
export default SnapshotCard
|
@ -13,62 +13,86 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import { ReactElement, memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ViewGrid as VmIcon } from 'iconoir-react'
|
||||
|
||||
import SelectCard, { Action } from 'client/components/Cards/SelectCard'
|
||||
import { StatusBadge } from 'client/components/Status'
|
||||
import { getState } from 'client/models/VirtualMachine'
|
||||
import { User, Group, Lock, HardDrive } from 'iconoir-react'
|
||||
import { Stack, Typography } from '@mui/material'
|
||||
|
||||
import Timer from 'client/components/Timer'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import { StatusCircle } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { getState, getLastHistory } from 'client/models/VirtualMachine'
|
||||
import { timeFromMilliseconds } from 'client/models/Helper'
|
||||
import { VM } from 'client/constants'
|
||||
|
||||
const VirtualMachineCard = memo(
|
||||
({ value, isSelected, handleClick, actions }) => {
|
||||
const { ID, NAME } = value
|
||||
const { color, name } = getState(value) ?? {}
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {VM} props.vm - Virtual machine resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ vm, rootProps }) => {
|
||||
const classes = rowStyles()
|
||||
const { ID, NAME, UNAME, GNAME, IPS, STIME, ETIME, LOCK } = vm
|
||||
|
||||
const HOSTNAME = getLastHistory(vm)?.HOSTNAME ?? '--'
|
||||
const time = timeFromMilliseconds(+ETIME || +STIME)
|
||||
|
||||
const { color: stateColor, name: stateName } = getState(vm)
|
||||
|
||||
return (
|
||||
<SelectCard
|
||||
action={actions?.map((action) => (
|
||||
<Action key={action?.cy} {...action} />
|
||||
))}
|
||||
skeletonHeight={75}
|
||||
dataCy={`vm-${ID}`}
|
||||
handleClick={handleClick}
|
||||
icon={
|
||||
<StatusBadge title={name} stateColor={color}>
|
||||
<VmIcon />
|
||||
</StatusBadge>
|
||||
}
|
||||
isSelected={isSelected}
|
||||
subheader={`#${ID}`}
|
||||
title={NAME}
|
||||
/>
|
||||
<div {...rootProps} data-cy={`vm-${ID}`}>
|
||||
<div>
|
||||
<StatusCircle color={stateColor} tooltip={stateName} />
|
||||
</div>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography noWrap component="span">
|
||||
{NAME}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
{LOCK && <Lock data-cy="lock" />}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span title={time.toFormat('ff')}>
|
||||
{`#${ID} ${+ETIME ? 'done' : 'started'} `}
|
||||
<Timer initial={time} />
|
||||
</span>
|
||||
<span title={`Owner: ${UNAME}`}>
|
||||
<User />
|
||||
<span data-cy="uname">{` ${UNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Group: ${GNAME}`}>
|
||||
<Group />
|
||||
<span data-cy="gname">{` ${GNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Hostname: ${HOSTNAME}`}>
|
||||
<HardDrive />
|
||||
<span data-cy="hostname">{` ${HOSTNAME}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!!IPS?.length && (
|
||||
<div className={classes.secondary}>
|
||||
<Stack flexWrap="wrap" justifyContent="end" alignItems="center">
|
||||
<MultipleTags tags={IPS.split(',')} />
|
||||
</Stack>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
(prev, next) =>
|
||||
prev.isSelected === next.isSelected &&
|
||||
prev.value.STATE === next.value.STATE &&
|
||||
prev.value?.LCM_STATE === next.value?.LCM_STATE
|
||||
}
|
||||
)
|
||||
|
||||
VirtualMachineCard.propTypes = {
|
||||
handleClick: PropTypes.func,
|
||||
isSelected: PropTypes.bool,
|
||||
value: PropTypes.object,
|
||||
actions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
handleClick: PropTypes.func.isRequired,
|
||||
icon: PropTypes.object.isRequired,
|
||||
cy: PropTypes.string,
|
||||
})
|
||||
),
|
||||
}
|
||||
|
||||
VirtualMachineCard.defaultProps = {
|
||||
handleClick: undefined,
|
||||
isSelected: false,
|
||||
value: {},
|
||||
actions: undefined,
|
||||
vm: PropTypes.object,
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
}
|
||||
|
||||
VirtualMachineCard.displayName = 'VirtualMachineCard'
|
||||
|
107
src/fireedge/src/client/components/Cards/VmTemplateCard.js
Normal file
107
src/fireedge/src/client/components/Cards/VmTemplateCard.js
Normal file
@ -0,0 +1,107 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement, memo, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { User, Group, Lock } from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
|
||||
import Timer from 'client/components/Timer'
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import Image from 'client/components/Image'
|
||||
|
||||
import { timeFromMilliseconds } from 'client/models/Helper'
|
||||
import { isExternalURL } from 'client/utils'
|
||||
import { VM, LOGO_IMAGES_URL } from 'client/constants'
|
||||
|
||||
const VmTemplateCard = memo(
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {VM} props.template - Virtual machine resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ template, rootProps }) => {
|
||||
const classes = rowStyles()
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
UNAME,
|
||||
GNAME,
|
||||
REGTIME,
|
||||
LOCK,
|
||||
VROUTER,
|
||||
LOGO = '',
|
||||
} = template
|
||||
|
||||
const [logoSource] = useMemo(() => {
|
||||
const external = isExternalURL(LOGO)
|
||||
const cleanLogoAttribute = String(LOGO).split('/').at(-1)
|
||||
const src = external ? LOGO : `${LOGO_IMAGES_URL}/${cleanLogoAttribute}`
|
||||
|
||||
return [src, external]
|
||||
}, [LOGO])
|
||||
|
||||
const time = timeFromMilliseconds(+REGTIME)
|
||||
|
||||
return (
|
||||
<div {...rootProps} data-cy={`template-${ID}`}>
|
||||
<div className={classes.figure}>
|
||||
<Image
|
||||
alt="logo"
|
||||
src={logoSource}
|
||||
imgProps={{ className: classes.image }}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span">{NAME}</Typography>
|
||||
<span className={classes.labels}>
|
||||
{LOCK && <Lock />}
|
||||
{VROUTER && <StatusChip text={VROUTER} />}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span title={time.toFormat('ff')} className="full-width">
|
||||
{`#${ID} registered `}
|
||||
<Timer initial={time} />
|
||||
</span>
|
||||
<span title={`Owner: ${UNAME}`}>
|
||||
<User />
|
||||
<span>{` ${UNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Group: ${GNAME}`}>
|
||||
<Group />
|
||||
<span>{` ${GNAME}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
VmTemplateCard.propTypes = {
|
||||
template: PropTypes.object,
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
}
|
||||
|
||||
VmTemplateCard.displayName = 'VmTemplateCard'
|
||||
|
||||
export default VmTemplateCard
|
@ -18,16 +18,22 @@ import ApplicationNetworkCard from 'client/components/Cards/ApplicationNetworkCa
|
||||
import ApplicationTemplateCard from 'client/components/Cards/ApplicationTemplateCard'
|
||||
import ClusterCard from 'client/components/Cards/ClusterCard'
|
||||
import DatastoreCard from 'client/components/Cards/DatastoreCard'
|
||||
import DiskCard from 'client/components/Cards/DiskCard'
|
||||
import DiskSnapshotCard from 'client/components/Cards/DiskSnapshotCard'
|
||||
import EmptyCard from 'client/components/Cards/EmptyCard'
|
||||
import HostCard from 'client/components/Cards/HostCard'
|
||||
import NetworkCard from 'client/components/Cards/NetworkCard'
|
||||
import NicCard from 'client/components/Cards/NicCard'
|
||||
import PolicyCard from 'client/components/Cards/PolicyCard'
|
||||
import ProvisionCard from 'client/components/Cards/ProvisionCard'
|
||||
import ProvisionTemplateCard from 'client/components/Cards/ProvisionTemplateCard'
|
||||
import ScheduleActionCard from 'client/components/Cards/ScheduleActionCard'
|
||||
import SecurityGroupCard from 'client/components/Cards/SecurityGroupCard'
|
||||
import SnapshotCard from 'client/components/Cards/SnapshotCard'
|
||||
import SelectCard from 'client/components/Cards/SelectCard'
|
||||
import TierCard from 'client/components/Cards/TierCard'
|
||||
import VirtualMachineCard from 'client/components/Cards/VirtualMachineCard'
|
||||
import VmTemplateCard from 'client/components/Cards/VmTemplateCard'
|
||||
import WavesCard from 'client/components/Cards/WavesCard'
|
||||
|
||||
export {
|
||||
@ -36,15 +42,21 @@ export {
|
||||
ApplicationTemplateCard,
|
||||
ClusterCard,
|
||||
DatastoreCard,
|
||||
DiskCard,
|
||||
DiskSnapshotCard,
|
||||
EmptyCard,
|
||||
HostCard,
|
||||
NetworkCard,
|
||||
NicCard,
|
||||
PolicyCard,
|
||||
ProvisionCard,
|
||||
ProvisionTemplateCard,
|
||||
ScheduleActionCard,
|
||||
SecurityGroupCard,
|
||||
SnapshotCard,
|
||||
SelectCard,
|
||||
TierCard,
|
||||
VirtualMachineCard,
|
||||
VmTemplateCard,
|
||||
WavesCard,
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ const CircleChart = memo(
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="div"
|
||||
style={{ cursor: 'pointer' }}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
{...labelProps}
|
||||
>
|
||||
<NumberEasing value={label} />
|
||||
|
@ -28,6 +28,7 @@ const debugLogStyles = makeStyles((theme) => ({
|
||||
display: 'flex',
|
||||
flexFlow: 'column',
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
},
|
||||
containerScroll: {
|
||||
width: '100%',
|
||||
|
@ -122,7 +122,11 @@ const DialogConfirmation = memo(
|
||||
)}
|
||||
</DialogTitle>
|
||||
{children && (
|
||||
<DialogContent dividers {...contentProps}>
|
||||
<DialogContent
|
||||
dividers
|
||||
sx={{ display: 'flex', flexDirection: 'column' }}
|
||||
{...contentProps}
|
||||
>
|
||||
{children}
|
||||
</DialogContent>
|
||||
)}
|
||||
|
@ -13,12 +13,11 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useEffect } from 'react'
|
||||
import { memo } from 'react'
|
||||
|
||||
import { styled, Link, Typography } from '@mui/material'
|
||||
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useSystem, useSystemApi } from 'client/features/One'
|
||||
import { useGetOneVersionQuery } from 'client/features/OneApi/system'
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { BY } from 'client/constants'
|
||||
|
||||
@ -44,13 +43,7 @@ const HeartIcon = styled('span')(({ theme }) => ({
|
||||
}))
|
||||
|
||||
const Footer = memo(() => {
|
||||
const { version } = useSystem()
|
||||
const { getOneVersion } = useSystemApi()
|
||||
const { fetchRequest } = useFetch(getOneVersion)
|
||||
|
||||
useEffect(() => {
|
||||
!version && fetchRequest()
|
||||
}, [])
|
||||
const { data: version } = useGetOneVersionQuery()
|
||||
|
||||
return (
|
||||
<FooterBox>
|
||||
|
@ -63,7 +63,7 @@ const ButtonComponent = forwardRef(
|
||||
)
|
||||
)
|
||||
|
||||
const TooltipComponent = ({ tooltip, tooltipProps, children }) => (
|
||||
const TooltipComponent = ({ tooltip, tooltipprops, children }) => (
|
||||
<ConditionalWrap
|
||||
condition={tooltip && tooltip !== ''}
|
||||
wrap={(wrapperChildren) => (
|
||||
@ -71,7 +71,7 @@ const TooltipComponent = ({ tooltip, tooltipProps, children }) => (
|
||||
arrow
|
||||
placement="bottom"
|
||||
title={<Typography variant="subtitle2">{tooltip}</Typography>}
|
||||
{...tooltipProps}
|
||||
{...tooltipprops}
|
||||
>
|
||||
<span>{wrapperChildren}</span>
|
||||
</Tooltip>
|
||||
@ -118,7 +118,7 @@ export const SubmitButtonPropTypes = {
|
||||
endicon: PropTypes.node,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
tooltip: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
tooltipProps: PropTypes.object,
|
||||
tooltipprops: PropTypes.object,
|
||||
isSubmitting: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
NavArrowRight as NextIcon,
|
||||
} from 'iconoir-react'
|
||||
|
||||
import { Translate, labelCanBeTranslated } from 'client/components/HOC'
|
||||
import { Tr, Translate, labelCanBeTranslated } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
@ -64,11 +64,9 @@ const CustomMobileStepper = ({
|
||||
</Typography>
|
||||
{Boolean(errors[id]) && (
|
||||
<Typography className={classes.error} variant="caption" color="error">
|
||||
{labelCanBeTranslated(label) ? (
|
||||
<Translate word={errors[id]?.message} />
|
||||
) : (
|
||||
errors[id]?.message
|
||||
)}
|
||||
{labelCanBeTranslated(label)
|
||||
? Tr(errors[id]?.message)
|
||||
: errors[id]?.message}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -112,7 +112,7 @@ const CustomStepper = ({
|
||||
StepIconComponent={StepIconStyled}
|
||||
error={Boolean(errors[id]?.message)}
|
||||
>
|
||||
{labelCanBeTranslated(label) ? <Translate word={label} /> : label}
|
||||
{labelCanBeTranslated(label) ? Tr(label) : label}
|
||||
</StepLabel>
|
||||
</StepButton>
|
||||
</Step>
|
||||
|
@ -22,17 +22,15 @@ import {
|
||||
} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { sprintf } from 'sprintf-js'
|
||||
import { BaseSchema } from 'yup'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
import { DevTool } from '@hookform/devtools'
|
||||
import { useMediaQuery } from '@mui/material'
|
||||
|
||||
import { useGeneral } from 'client/features/General'
|
||||
import CustomMobileStepper from 'client/components/FormStepper/MobileStepper'
|
||||
import CustomStepper from 'client/components/FormStepper/Stepper'
|
||||
import SkeletonStepsForm from 'client/components/FormStepper/Skeleton'
|
||||
import { groupBy, Step, isDevelopment } from 'client/utils'
|
||||
import { groupBy, Step } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const FIRST_STEP = 0
|
||||
@ -50,7 +48,6 @@ const FIRST_STEP = 0
|
||||
const FormStepper = ({ steps = [], schema, onSubmit }) => {
|
||||
const isMobile = useMediaQuery((theme) => theme.breakpoints.only('xs'))
|
||||
const {
|
||||
control,
|
||||
watch,
|
||||
reset,
|
||||
formState: { errors },
|
||||
@ -82,28 +79,17 @@ const FormStepper = ({ steps = [], schema, onSubmit }) => {
|
||||
return { id, data: stepData, ...step }
|
||||
}
|
||||
|
||||
const setErrors = ({ inner = [], ...rest } = {}) => {
|
||||
const setErrors = ({ inner = [], message = { word: 'Error' } } = {}) => {
|
||||
const errorsByPath = groupBy(inner, 'path') ?? {}
|
||||
const totalErrors = Object.keys(errorsByPath).length
|
||||
|
||||
totalErrors > 0
|
||||
? setError(stepId, {
|
||||
type: 'manual',
|
||||
message: [T.ErrorsOcurred, totalErrors],
|
||||
})
|
||||
: setError(stepId, rest)
|
||||
const translationError =
|
||||
totalErrors > 0 ? [T.ErrorsOcurred, totalErrors] : Object.values(message)
|
||||
|
||||
inner?.forEach(({ path, type, errors: message }) => {
|
||||
if (isDevelopment()) {
|
||||
// the package @hookform/devtools requires message as string
|
||||
const [key, ...values] = [message].flat()
|
||||
setError(`${stepId}.${path}`, {
|
||||
type,
|
||||
message: sprintf(key, ...values),
|
||||
})
|
||||
} else {
|
||||
setError(`${stepId}.${path}`, { type, message })
|
||||
}
|
||||
setError(stepId, { type: 'manual', message: translationError })
|
||||
|
||||
inner?.forEach(({ path, type, errors: innerMessage }) => {
|
||||
setError(`${stepId}.${path}`, { type, message: innerMessage })
|
||||
})
|
||||
}
|
||||
|
||||
@ -202,8 +188,6 @@ const FormStepper = ({ steps = [], schema, onSubmit }) => {
|
||||
)}
|
||||
{/* FORM CONTENT */}
|
||||
{Content && <Content data={formData[stepId]} setFormData={setFormData} />}
|
||||
|
||||
{isDevelopment() && <DevTool control={control} placement="top-left" />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -16,7 +16,8 @@
|
||||
import { string, boolean, ObjectSchema } from 'yup'
|
||||
import { makeStyles } from '@mui/styles'
|
||||
|
||||
import { useSystem, useDatastore } from 'client/features/One'
|
||||
import { useGetOneConfigQuery } from 'client/features/OneApi/system'
|
||||
import { useGetDatastoresQuery } from 'client/features/OneApi/datastore'
|
||||
import {
|
||||
ImagesTable,
|
||||
VmsTable,
|
||||
@ -134,8 +135,8 @@ const RES_TABLE = {
|
||||
.default(() => undefined),
|
||||
grid: { md: 12 },
|
||||
fieldProps: (type) => {
|
||||
const { config: oneConfig } = useSystem()
|
||||
const datastores = useDatastore()
|
||||
const { data: oneConfig = {} } = useGetOneConfigQuery()
|
||||
const { data: datastores = [] } = useGetDatastoresQuery()
|
||||
const classes = useTableStyles()
|
||||
|
||||
return {
|
||||
|
@ -16,7 +16,7 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
import { useSystem } from 'client/features/One'
|
||||
import { useGetOneConfigQuery } from 'client/features/OneApi/system'
|
||||
import { MarketplacesTable } from 'client/components/Tables'
|
||||
import { SCHEMA } from 'client/components/Forms/MarketplaceApp/CreateForm/Steps/MarketplacesTable/schema'
|
||||
import { Step } from 'client/utils'
|
||||
@ -27,7 +27,7 @@ export const STEP_ID = 'marketplace'
|
||||
const Content = ({ data }) => {
|
||||
const { NAME } = data?.[0] ?? {}
|
||||
const { setValue } = useFormContext()
|
||||
const { config: oneConfig } = useSystem()
|
||||
const { data: oneConfig = {} } = useGetOneConfigQuery()
|
||||
|
||||
const handleSelectedRows = (rows) => {
|
||||
const { original = {} } = rows?.[0] ?? {}
|
||||
|
@ -13,13 +13,12 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useEffect, useMemo, JSXElementConstructor } from 'react'
|
||||
import { useMemo, JSXElementConstructor } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import { useDatastoreApi } from 'client/features/One'
|
||||
import FormStepper from 'client/components/FormStepper'
|
||||
import Steps from 'client/components/Forms/MarketplaceApp/CreateForm/Steps'
|
||||
|
||||
@ -32,7 +31,6 @@ import Steps from 'client/components/Forms/MarketplaceApp/CreateForm/Steps'
|
||||
* @returns {JSXElementConstructor} Form component
|
||||
*/
|
||||
const CreateForm = ({ initialValues, onSubmit }) => {
|
||||
const { getDatastores } = useDatastoreApi()
|
||||
const stepsForm = useMemo(() => Steps(initialValues, initialValues), [])
|
||||
const { steps, defaultValues, resolver, transformBeforeSubmit } = stepsForm
|
||||
|
||||
@ -42,10 +40,6 @@ const CreateForm = ({ initialValues, onSubmit }) => {
|
||||
resolver: yupResolver(resolver?.()),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
getDatastores()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<FormStepper
|
||||
|
@ -13,33 +13,37 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import * as yup from 'yup'
|
||||
import { INPUT_TYPES } from 'client/constants'
|
||||
import { getValidationFromFields } from 'client/utils'
|
||||
import { string, object, ObjectSchema } from 'yup'
|
||||
import { INPUT_TYPES, T } from 'client/constants'
|
||||
import { Field, getValidationFromFields } from 'client/utils'
|
||||
|
||||
const NAME = {
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
label: T.Name,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: yup
|
||||
.string()
|
||||
.min(1, 'Name field is required')
|
||||
.trim()
|
||||
.required('Name field is required')
|
||||
.default(''),
|
||||
validation: string().min(1).trim().required().default(''),
|
||||
}
|
||||
|
||||
const DESCRIPTION = {
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
label: T.Description,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
multiline: true,
|
||||
validation: yup.string().trim().default(''),
|
||||
validation: string().trim().default(''),
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} config - Form configuration
|
||||
* @param {boolean} [config.isUpdate] - Form is updating the provider
|
||||
* @returns {Field[]} - List of fields
|
||||
*/
|
||||
export const FORM_FIELDS = ({ isUpdate }) =>
|
||||
[!isUpdate && NAME, DESCRIPTION].filter(Boolean)
|
||||
|
||||
/**
|
||||
* @param {object} config - Form configuration
|
||||
* @param {boolean} [config.isUpdate] - Form is updating the provider
|
||||
* @returns {ObjectSchema} - Schema
|
||||
*/
|
||||
export const STEP_FORM_SCHEMA = ({ isUpdate }) =>
|
||||
yup.object(getValidationFromFields(FORM_FIELDS({ isUpdate })))
|
||||
object(getValidationFromFields(FORM_FIELDS({ isUpdate })))
|
||||
|
@ -18,7 +18,7 @@ import { useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useGetProviderConfigQuery } from 'client/features/OneApi/provider'
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { EmptyCard } from 'client/components/Cards'
|
||||
|
||||
@ -39,7 +39,7 @@ let fileCredentials = false
|
||||
|
||||
const Content = ({ isUpdate }) => {
|
||||
const [fields, setFields] = useState([])
|
||||
const { providerConfig } = useAuth()
|
||||
const { data: providerConfig } = useGetProviderConfigQuery()
|
||||
const { watch } = useFormContext()
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -13,21 +13,26 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import * as yup from 'yup'
|
||||
import { string, object, ObjectSchema } from 'yup'
|
||||
import { INPUT_TYPES } from 'client/constants'
|
||||
import { getValidationFromFields, prettyBytes } from 'client/utils'
|
||||
import { Field, getValidationFromFields, prettyBytes } from 'client/utils'
|
||||
|
||||
const MAX_SIZE_JSON = 102_400
|
||||
const JSON_FORMAT = 'application/json'
|
||||
const CREDENTIAL_INPUT = 'credentials'
|
||||
|
||||
/**
|
||||
* @param {object} config - Form configuration
|
||||
* @param {object} config.connection - Provider connection
|
||||
* @param {boolean} config.fileCredentials - Provider needs file with credentials
|
||||
* @returns {Field[]} - List of fields
|
||||
*/
|
||||
export const FORM_FIELDS = ({ connection, fileCredentials }) =>
|
||||
Object.entries(connection)?.map(([name, label]) => {
|
||||
const isInputFile =
|
||||
fileCredentials && String(name).toLowerCase() === CREDENTIAL_INPUT
|
||||
|
||||
let validation = yup.string().trim().required().default(undefined)
|
||||
let validation = string().trim().required().default(undefined)
|
||||
|
||||
if (isInputFile) {
|
||||
validation = validation.isBase64()
|
||||
@ -62,5 +67,9 @@ export const FORM_FIELDS = ({ connection, fileCredentials }) =>
|
||||
}
|
||||
})
|
||||
|
||||
export const STEP_FORM_SCHEMA = (props) =>
|
||||
yup.object(getValidationFromFields(FORM_FIELDS(props)))
|
||||
/**
|
||||
* @param {object} config - Form configuration
|
||||
* @returns {ObjectSchema} - Schema
|
||||
*/
|
||||
export const STEP_FORM_SCHEMA = (config) =>
|
||||
object(getValidationFromFields(FORM_FIELDS(config)))
|
||||
|
@ -27,8 +27,8 @@ import { NavArrowRight } from 'iconoir-react'
|
||||
import { marked } from 'marked'
|
||||
|
||||
import { useListForm } from 'client/hooks'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useProvisionTemplate } from 'client/features/One'
|
||||
import { useGetProviderConfigQuery } from 'client/features/OneApi/provider'
|
||||
import { useGetProvisionTemplatesQuery } from 'client/features/OneApi/provision'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { ProvisionTemplateCard } from 'client/components/Cards'
|
||||
import { Step, sanitize, deepmerge } from 'client/utils'
|
||||
@ -76,8 +76,8 @@ Description.propTypes = { description: PropTypes.string }
|
||||
// ----------------------------------------------------------
|
||||
|
||||
const Content = ({ data, setFormData }) => {
|
||||
const provisionTemplates = useProvisionTemplate()
|
||||
const { providerConfig } = useAuth()
|
||||
const { data: provisionTemplates = {} } = useGetProvisionTemplatesQuery()
|
||||
const { data: providerConfig = {} } = useGetProviderConfigQuery()
|
||||
const templateSelected = data?.[0]
|
||||
|
||||
const provisionTypes = useMemo(
|
||||
@ -174,7 +174,7 @@ const Content = ({ data, setFormData }) => {
|
||||
inputProps={{ 'data-cy': 'select-provision-type' }}
|
||||
labelId="select-provision-type-label"
|
||||
native
|
||||
style={{ marginTop: '1em', minWidth: '8em' }}
|
||||
sx={{ marginTop: '1em', minWidth: '8em' }}
|
||||
onChange={handleChangeProvision}
|
||||
value={provisionSelected}
|
||||
variant="outlined"
|
||||
@ -195,7 +195,7 @@ const Content = ({ data, setFormData }) => {
|
||||
inputProps={{ 'data-cy': 'select-provider-type' }}
|
||||
labelId="select-provider-type-label"
|
||||
native
|
||||
style={{ marginTop: '1em', minWidth: '8em' }}
|
||||
sx={{ marginTop: '1em', minWidth: '8em' }}
|
||||
onChange={handleChangeProvider}
|
||||
value={providerSelected}
|
||||
variant="outlined"
|
||||
@ -212,7 +212,7 @@ const Content = ({ data, setFormData }) => {
|
||||
{/* -- DESCRIPTION -- */}
|
||||
{providerDescription && <Description description={providerDescription} />}
|
||||
|
||||
<Divider style={{ margin: '1rem 0' }} />
|
||||
<Divider sx={{ margin: '1rem 0' }} />
|
||||
|
||||
{/* -- LIST -- */}
|
||||
<ListCards
|
||||
|
@ -13,11 +13,11 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import * as yup from 'yup'
|
||||
import { array, object, ArraySchema } from 'yup'
|
||||
|
||||
export const STEP_FORM_SCHEMA = yup
|
||||
.array(yup.object())
|
||||
.min(1, 'Select provider template')
|
||||
.max(1, 'Max. one template selected')
|
||||
.required('Provider template field is required')
|
||||
/** @type {ArraySchema} - Schema */
|
||||
export const STEP_FORM_SCHEMA = array(object())
|
||||
.min(1)
|
||||
.max(1)
|
||||
.required()
|
||||
.default([])
|
||||
|
@ -21,9 +21,11 @@ import { Redirect } from 'react-router'
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import { useFetchAll } from 'client/hooks'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useProviderApi } from 'client/features/One'
|
||||
import {
|
||||
useGetProviderConfigQuery,
|
||||
useLazyGetProviderConnectionQuery,
|
||||
useLazyGetProviderQuery,
|
||||
} from 'client/features/OneApi/provider'
|
||||
import FormStepper, { SkeletonStepsForm } from 'client/components/FormStepper'
|
||||
import Steps from 'client/components/Forms/Provider/CreateForm/Steps'
|
||||
import { PATH } from 'client/apps/provision/routes'
|
||||
@ -55,29 +57,28 @@ const CreateForm = ({ provider, providerConfig, connection, onSubmit }) => {
|
||||
}
|
||||
|
||||
const PreFetchingForm = ({ providerId, onSubmit }) => {
|
||||
const { providerConfig } = useAuth()
|
||||
const { getProvider, getProviderConnection } = useProviderApi()
|
||||
const { data, fetchRequestAll, error } = useFetchAll()
|
||||
const [provider, connection] = data ?? []
|
||||
const { data: config, error: errorConfig } = useGetProviderConfigQuery()
|
||||
|
||||
const [getConnection, { data: connection, error: errorConnection }] =
|
||||
useLazyGetProviderConnectionQuery()
|
||||
const [getProvider, { data: provider, error: errorProvider }] =
|
||||
useLazyGetProviderQuery()
|
||||
|
||||
useEffect(() => {
|
||||
providerId &&
|
||||
fetchRequestAll([
|
||||
getProvider(providerId),
|
||||
getProviderConnection(providerId),
|
||||
])
|
||||
providerId && getProvider(providerId)
|
||||
providerId && getConnection(providerId)
|
||||
}, [])
|
||||
|
||||
if (error) {
|
||||
if (errorConfig || errorConnection || errorProvider) {
|
||||
return <Redirect to={PATH.PROVIDERS.LIST} />
|
||||
}
|
||||
|
||||
return providerId && !data ? (
|
||||
return providerId && (!config || !connection || !provider) ? (
|
||||
<SkeletonStepsForm />
|
||||
) : (
|
||||
<CreateForm
|
||||
provider={provider}
|
||||
providerConfig={providerConfig}
|
||||
providerConfig={config}
|
||||
connection={connection}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
|
@ -14,8 +14,6 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
@ -31,11 +29,8 @@ const BasicConfiguration = () => ({
|
||||
label: T.ProvisionOverview,
|
||||
resolver: () => STEP_FORM_SCHEMA,
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: useCallback(
|
||||
() => (
|
||||
<FormWithSchema cy="form-provision" fields={FORM_FIELDS} id={STEP_ID} />
|
||||
),
|
||||
[]
|
||||
content: () => (
|
||||
<FormWithSchema cy="form-provision" fields={FORM_FIELDS} id={STEP_ID} />
|
||||
),
|
||||
})
|
||||
|
||||
|
@ -13,30 +13,25 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import * as yup from 'yup'
|
||||
import { INPUT_TYPES } from 'client/constants'
|
||||
import { string, object } from 'yup'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
import { getValidationFromFields } from 'client/utils'
|
||||
|
||||
const NAME = {
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
label: T.Name,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: yup
|
||||
.string()
|
||||
.min(1, 'Name field is required')
|
||||
.trim()
|
||||
.required('Name field is required')
|
||||
.default(''),
|
||||
validation: string().min(1).trim().required().default(''),
|
||||
}
|
||||
|
||||
const DESCRIPTION = {
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
label: T.Description,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
multiline: true,
|
||||
validation: yup.string().trim().default(''),
|
||||
validation: string().trim().default(''),
|
||||
}
|
||||
|
||||
export const FORM_FIELDS = [NAME, DESCRIPTION]
|
||||
|
||||
export const STEP_FORM_SCHEMA = yup.object(getValidationFromFields(FORM_FIELDS))
|
||||
export const STEP_FORM_SCHEMA = object(getValidationFromFields(FORM_FIELDS))
|
||||
|
@ -14,14 +14,13 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
import { LinearProgress } from '@mui/material'
|
||||
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { useProviderApi } from 'client/features/One'
|
||||
import { useLazyGetProviderQuery } from 'client/features/OneApi/provider'
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { EmptyCard } from 'client/components/Cards'
|
||||
import { T } from 'client/constants'
|
||||
@ -43,12 +42,11 @@ const Inputs = () => ({
|
||||
label: T.ConfigureInputs,
|
||||
resolver: () => STEP_FORM_SCHEMA(inputs),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: useCallback(() => {
|
||||
content: () => {
|
||||
const [fields, setFields] = useState(undefined)
|
||||
const { changeLoading } = useGeneralApi()
|
||||
const { getProvider } = useProviderApi()
|
||||
const { data: fetchData, fetchRequest } = useFetch(getProvider)
|
||||
const { watch, reset } = useFormContext()
|
||||
const { changeLoading } = useGeneralApi()
|
||||
const [getProvider, { data: fetchData }] = useLazyGetProviderQuery()
|
||||
|
||||
useEffect(() => {
|
||||
const { [PROVIDER_ID]: providerSelected = [], [STEP_ID]: currentInputs } =
|
||||
@ -56,7 +54,7 @@ const Inputs = () => ({
|
||||
|
||||
if (!currentInputs) {
|
||||
changeLoading(true) // disable finish button until provider is fetched
|
||||
fetchRequest(providerSelected[0]?.ID)
|
||||
getProvider(providerSelected[0]?.ID)
|
||||
} else {
|
||||
setFields(FORM_FIELDS(inputs))
|
||||
}
|
||||
@ -92,7 +90,7 @@ const Inputs = () => ({
|
||||
) : (
|
||||
<FormWithSchema cy="form-provision" fields={fields} id={STEP_ID} />
|
||||
)
|
||||
}, []),
|
||||
},
|
||||
})
|
||||
|
||||
export default Inputs
|
||||
|
@ -14,12 +14,14 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useWatch } from 'react-hook-form'
|
||||
|
||||
import { useListForm } from 'client/hooks'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useProvider } from 'client/features/One'
|
||||
import {
|
||||
useGetProvidersQuery,
|
||||
useGetProviderConfigQuery,
|
||||
} from 'client/features/OneApi/provider'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { EmptyCard, ProvisionCard } from 'client/components/Cards'
|
||||
import { T } from 'client/constants'
|
||||
@ -34,9 +36,9 @@ const Provider = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.Provider,
|
||||
resolver: () => STEP_FORM_SCHEMA,
|
||||
content: useCallback(({ data, setFormData }) => {
|
||||
const providers = useProvider()
|
||||
const { providerConfig } = useAuth()
|
||||
content: ({ data, setFormData }) => {
|
||||
const { data: providers } = useGetProvidersQuery()
|
||||
const { data: providerConfig } = useGetProviderConfigQuery()
|
||||
|
||||
const provisionTemplateSelected = useWatch({ name: TEMPLATE_ID })?.[0] ?? {}
|
||||
|
||||
@ -87,7 +89,7 @@ const Provider = () => ({
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}, []),
|
||||
},
|
||||
})
|
||||
|
||||
export default Provider
|
||||
|
@ -13,11 +13,10 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import * as yup from 'yup'
|
||||
import { array, object } from 'yup'
|
||||
|
||||
export const STEP_FORM_SCHEMA = yup
|
||||
.array(yup.object())
|
||||
.min(1, 'Select provider')
|
||||
.max(1, 'Max. one provider selected')
|
||||
.required('Provider field is required')
|
||||
export const STEP_FORM_SCHEMA = array(object())
|
||||
.min(1)
|
||||
.max(1)
|
||||
.required()
|
||||
.default([])
|
||||
|
@ -26,8 +26,11 @@ import { NavArrowRight } from 'iconoir-react'
|
||||
import { marked } from 'marked'
|
||||
|
||||
import { useListForm } from 'client/hooks'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useProvider, useProvisionTemplate } from 'client/features/One'
|
||||
import {
|
||||
useGetProvidersQuery,
|
||||
useGetProviderConfigQuery,
|
||||
} from 'client/features/OneApi/provider'
|
||||
import { useGetProvisionTemplatesQuery } from 'client/features/OneApi/provision'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { ProvisionTemplateCard } from 'client/components/Cards'
|
||||
import { Step, sanitize } from 'client/utils'
|
||||
@ -75,9 +78,9 @@ Description.propTypes = { description: PropTypes.string }
|
||||
// ----------------------------------------------------------
|
||||
|
||||
const Content = ({ data, setFormData }) => {
|
||||
const providers = useProvider()
|
||||
const provisionTemplates = useProvisionTemplate()
|
||||
const { providerConfig } = useAuth()
|
||||
const { data: provisionTemplates } = useGetProvisionTemplatesQuery()
|
||||
const { data: providers } = useGetProvidersQuery()
|
||||
const { data: providerConfig } = useGetProviderConfigQuery()
|
||||
const templateSelected = data?.[0]
|
||||
|
||||
const provisionTypes = useMemo(
|
||||
@ -177,7 +180,7 @@ const Content = ({ data, setFormData }) => {
|
||||
inputProps={{ 'data-cy': 'select-provision-type' }}
|
||||
labelId="select-provision-type-label"
|
||||
native
|
||||
style={{ marginTop: '1em', minWidth: '8em' }}
|
||||
sx={{ marginTop: '1em', minWidth: '8em' }}
|
||||
onChange={handleChangeProvision}
|
||||
value={provisionSelected}
|
||||
variant="outlined"
|
||||
@ -198,7 +201,7 @@ const Content = ({ data, setFormData }) => {
|
||||
inputProps={{ 'data-cy': 'select-provider-type' }}
|
||||
labelId="select-provider-type-label"
|
||||
native
|
||||
style={{ marginTop: '1em', minWidth: '8em' }}
|
||||
sx={{ marginTop: '1em', minWidth: '8em' }}
|
||||
onChange={handleChangeProvider}
|
||||
value={providerSelected}
|
||||
variant="outlined"
|
||||
@ -221,7 +224,7 @@ const Content = ({ data, setFormData }) => {
|
||||
[providerDescription]
|
||||
)}
|
||||
|
||||
<Divider style={{ margin: '1rem 0' }} />
|
||||
<Divider sx={{ margin: '1rem 0' }} />
|
||||
|
||||
{/* -- LIST -- */}
|
||||
<ListCards
|
||||
|
@ -13,11 +13,10 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import * as yup from 'yup'
|
||||
import { array, object } from 'yup'
|
||||
|
||||
export const STEP_FORM_SCHEMA = yup
|
||||
.array(yup.object())
|
||||
.min(1, 'Select provision template')
|
||||
.max(1, 'Max. one template selected')
|
||||
.required('Provision template field is required')
|
||||
export const STEP_FORM_SCHEMA = array(object())
|
||||
.min(1)
|
||||
.max(1)
|
||||
.required()
|
||||
.default([])
|
||||
|
@ -14,6 +14,7 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
@ -23,7 +24,8 @@ import FormStepper from 'client/components/FormStepper'
|
||||
import Steps from 'client/components/Forms/Provision/CreateForm/Steps'
|
||||
|
||||
const CreateForm = ({ onSubmit }) => {
|
||||
const { steps, defaultValues, resolver, transformBeforeSubmit } = Steps()
|
||||
const stepsForm = useMemo(() => Steps(), [])
|
||||
const { steps, defaultValues, resolver, transformBeforeSubmit } = stepsForm
|
||||
|
||||
const methods = useForm({
|
||||
mode: 'onSubmit',
|
||||
|
@ -44,7 +44,6 @@ const Content = ({ data, setFormData }) => {
|
||||
onlyGlobalSelectedRows
|
||||
initialState={{ selectedRowIds: { [ID]: true } }}
|
||||
onSelectedRowsChange={handleSelectedRows}
|
||||
searchProps={{ 'data-cy': 'search-images' }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ const GROUP = {
|
||||
name: 'group',
|
||||
label: 'Select the new group',
|
||||
type: INPUT_TYPES.TABLE,
|
||||
Table: GroupsTable,
|
||||
Table: () => GroupsTable,
|
||||
validation: string()
|
||||
.trim()
|
||||
.required('You must select a group')
|
||||
|
@ -24,7 +24,7 @@ const USER = {
|
||||
name: 'user',
|
||||
label: 'Select the new owner',
|
||||
type: INPUT_TYPES.TABLE,
|
||||
Table: UsersTable,
|
||||
Table: () => UsersTable,
|
||||
validation: string()
|
||||
.trim()
|
||||
.required('You must select an user')
|
||||
|
@ -21,15 +21,15 @@ import {
|
||||
INPUT_TYPES,
|
||||
VM_ACTIONS_IN_CHARTER,
|
||||
VM_ACTIONS_WITH_SCHEDULE,
|
||||
END_TYPE_VALUES,
|
||||
REPEAT_VALUES,
|
||||
ARGS_TYPES,
|
||||
PERIOD_TYPES,
|
||||
} from 'client/constants'
|
||||
import { Field, sentenceCase, arrayToOptions } from 'client/utils'
|
||||
import {
|
||||
isRelative,
|
||||
END_TYPE_VALUES,
|
||||
REPEAT_VALUES,
|
||||
ARGS_TYPES,
|
||||
getRequiredArgsByAction,
|
||||
PERIOD_TYPES,
|
||||
getPeriodicityByTimeInSeconds,
|
||||
} from 'client/models/Scheduler'
|
||||
import {
|
||||
|
@ -15,12 +15,13 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { string, object, ObjectSchema } from 'yup'
|
||||
|
||||
import { ARGS_TYPES, getRequiredArgsByAction } from 'client/models/Scheduler'
|
||||
import { getRequiredArgsByAction } from 'client/models/Scheduler'
|
||||
import { Field, getObjectSchemaFromFields } from 'client/utils'
|
||||
import {
|
||||
PUNCTUAL_FIELDS,
|
||||
RELATIVE_FIELDS,
|
||||
} from 'client/components/Forms/Vm/CreateSchedActionForm/fields'
|
||||
import { ARGS_TYPES } from 'client/constants'
|
||||
|
||||
const { ACTION_FIELD } = PUNCTUAL_FIELDS
|
||||
|
||||
|
@ -35,7 +35,7 @@ const DATASTORE = {
|
||||
name: 'datastore',
|
||||
label: 'Select the new datastore',
|
||||
type: INPUT_TYPES.TABLE,
|
||||
Table: DatastoresTable,
|
||||
Table: () => DatastoresTable,
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired()
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { string, boolean } from 'yup'
|
||||
|
||||
import { useHost } from 'client/features/One'
|
||||
import { useGetHostsQuery } from 'client/features/OneApi/host'
|
||||
import { getKvmMachines, getKvmCpuModels } from 'client/models/Host'
|
||||
import { Field, arrayToOptions } from 'client/utils'
|
||||
import {
|
||||
@ -64,7 +64,7 @@ export const MACHINE_TYPES = {
|
||||
notOnHypervisors: [vcenter, firecracker, lxc],
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: () => {
|
||||
const hosts = useHost()
|
||||
const { data: hosts = [] } = useGetHostsQuery()
|
||||
const kvmMachines = getKvmMachines(hosts)
|
||||
|
||||
return arrayToOptions(kvmMachines)
|
||||
@ -82,7 +82,7 @@ export const CPU_MODEL = {
|
||||
notOnHypervisors: [vcenter, firecracker, lxc],
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: () => {
|
||||
const hosts = useHost()
|
||||
const { data: hosts = [] } = useGetHostsQuery()
|
||||
const kvmCpuModels = getKvmCpuModels(hosts)
|
||||
|
||||
return arrayToOptions(kvmCpuModels)
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { boolean, string } from 'yup'
|
||||
|
||||
import { useImage } from 'client/features/One'
|
||||
import { useGetImagesQuery } from 'client/features/OneApi/image'
|
||||
import { getType } from 'client/models/Image'
|
||||
import { Field, clearNames } from 'client/utils'
|
||||
import { T, INPUT_TYPES, HYPERVISORS, IMAGE_TYPES_STR } from 'client/constants'
|
||||
@ -47,7 +47,7 @@ export const KERNEL_DS = {
|
||||
dependOf: KERNEL_PATH_ENABLED.name,
|
||||
htmlType: (enabled) => enabled && INPUT_TYPES.HIDDEN,
|
||||
values: () => {
|
||||
const images = useImage()
|
||||
const { data: images = [] } = useGetImagesQuery()
|
||||
|
||||
return images
|
||||
?.filter((image) => getType(image) === IMAGE_TYPES_STR.KERNEL)
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { string, boolean } from 'yup'
|
||||
|
||||
import { useImage } from 'client/features/One'
|
||||
import { useGetImagesQuery } from 'client/features/OneApi/image'
|
||||
import { getType } from 'client/models/Image'
|
||||
import { Field, clearNames } from 'client/utils'
|
||||
import { T, INPUT_TYPES, HYPERVISORS, IMAGE_TYPES_STR } from 'client/constants'
|
||||
@ -47,7 +47,7 @@ export const RAMDISK_DS = {
|
||||
dependOf: RAMDISK_PATH_ENABLED.name,
|
||||
htmlType: (enabled) => enabled && INPUT_TYPES.HIDDEN,
|
||||
values: () => {
|
||||
const images = useImage()
|
||||
const { data: images = [] } = useGetImagesQuery()
|
||||
|
||||
return images
|
||||
?.filter((image) => getType(image) === IMAGE_TYPES_STR.RAMDISK)
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { object, string, array, ObjectSchema } from 'yup'
|
||||
|
||||
import { useHost } from 'client/features/One'
|
||||
import { useGetHostsQuery } from 'client/features/OneApi/host'
|
||||
import { getPciDevices } from 'client/models/Host'
|
||||
import {
|
||||
Field,
|
||||
@ -46,7 +46,7 @@ const NAME_FIELD = {
|
||||
notOnHypervisors: [vcenter, lxc, firecracker],
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: () => {
|
||||
const hosts = useHost()
|
||||
const { data: hosts = [] } = useGetHostsQuery()
|
||||
const pciDevices = hosts.map(getPciDevices).flat()
|
||||
|
||||
return arrayToOptions(pciDevices, {
|
||||
|
@ -23,7 +23,7 @@ import { DeleteCircledOutline, AddCircledOutline } from 'iconoir-react'
|
||||
import { useFieldArray, useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import { useHost } from 'client/features/One'
|
||||
import { useGetHostsQuery } from 'client/features/OneApi/host'
|
||||
import { FormWithSchema, Legend } from 'client/components/Forms'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { getPciDevices } from 'client/models/Host'
|
||||
@ -40,7 +40,7 @@ export const SECTION_ID = 'PCI'
|
||||
* @returns {JSXElementConstructor} - Inputs section
|
||||
*/
|
||||
const PciDevicesSection = ({ fields }) => {
|
||||
const hosts = useHost()
|
||||
const { data: hosts = [] } = useGetHostsQuery()
|
||||
const pciDevicesAvailable = useMemo(
|
||||
() => hosts.map(getPciDevices).flat(),
|
||||
[hosts.length]
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { string, number } from 'yup'
|
||||
|
||||
import { useHost } from 'client/features/One'
|
||||
import { useGetHostsQuery } from 'client/features/OneApi/host'
|
||||
import { getHugepageSizes } from 'client/models/Host'
|
||||
import {
|
||||
T,
|
||||
@ -144,7 +144,7 @@ const HUGEPAGES = {
|
||||
notOnHypervisors: [vcenter, firecracker],
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: () => {
|
||||
const hosts = useHost()
|
||||
const { data: hosts = [] } = useGetHostsQuery()
|
||||
const sizes = hosts
|
||||
.reduce((res, host) => res.concat(getHugepageSizes(host)), [])
|
||||
.flat()
|
||||
|
@ -18,7 +18,12 @@ import { Calendar as ActionIcon } from 'iconoir-react'
|
||||
import { useFieldArray } from 'react-hook-form'
|
||||
|
||||
import { ScheduleActionCard } from 'client/components/Cards'
|
||||
import { CreateSchedButton, CharterButton } from 'client/components/Buttons'
|
||||
import {
|
||||
CreateSchedButton,
|
||||
CharterButton,
|
||||
UpdateSchedButton,
|
||||
DeleteSchedButton,
|
||||
} from 'client/components/Buttons/ScheduleAction'
|
||||
|
||||
import {
|
||||
STEP_ID as EXTRA_ID,
|
||||
@ -54,11 +59,11 @@ const ScheduleAction = () => {
|
||||
append(mappedActions)
|
||||
}
|
||||
|
||||
const handleUpdateAction = (action, index) => {
|
||||
const handleUpdate = (action, index) => {
|
||||
update(index, mapNameFunction(action, index))
|
||||
}
|
||||
|
||||
const handleRemoveAction = (index) => {
|
||||
const handleRemove = (index) => {
|
||||
remove(index)
|
||||
}
|
||||
|
||||
@ -78,14 +83,26 @@ const ScheduleAction = () => {
|
||||
>
|
||||
{scheduleActions?.map((schedule, index) => {
|
||||
const { ID, NAME } = schedule
|
||||
const fakeValues = { ...schedule, ID: index }
|
||||
|
||||
return (
|
||||
<ScheduleActionCard
|
||||
key={ID ?? NAME}
|
||||
relative
|
||||
schedule={{ ...schedule, ID: index }}
|
||||
handleUpdate={(newAction) => handleUpdateAction(newAction, index)}
|
||||
handleRemove={() => handleRemoveAction(index)}
|
||||
schedule={fakeValues}
|
||||
actions={
|
||||
<>
|
||||
<UpdateSchedButton
|
||||
relative
|
||||
vm={{}}
|
||||
schedule={fakeValues}
|
||||
onSubmit={(newAction) => handleUpdate(newAction, index)}
|
||||
/>
|
||||
<DeleteSchedButton
|
||||
schedule={fakeValues}
|
||||
onSubmit={() => handleRemove(index)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
@ -97,7 +114,7 @@ const ScheduleAction = () => {
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'sched_action',
|
||||
name: T.ScheduledAction,
|
||||
name: T.ScheduleAction,
|
||||
icon: ActionIcon,
|
||||
Content: ScheduleAction,
|
||||
getError: (error) => !!error?.[TAB_ID],
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { object, string, array, ObjectSchema } from 'yup'
|
||||
|
||||
import { useDatastore } from 'client/features/One'
|
||||
import { useGetDatastoresQuery } from 'client/features/OneApi/datastore'
|
||||
import { getDeployMode } from 'client/models/Datastore'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
import { Field, arrayToOptions, getValidationFromFields } from 'client/utils'
|
||||
@ -28,8 +28,8 @@ const TM_MAD_SYSTEM = {
|
||||
tooltip: T.DeployModeConcept,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: () => {
|
||||
const datastores = useDatastore()
|
||||
const modes = datastores?.map(getDeployMode)?.flat()
|
||||
const { data: datastores = [] } = useGetDatastoresQuery()
|
||||
const modes = datastores.map(getDeployMode)?.flat()
|
||||
|
||||
return arrayToOptions([...new Set(modes)], { addEmpty: 'Default' })
|
||||
},
|
||||
|
@ -15,7 +15,8 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { string } from 'yup'
|
||||
|
||||
import { useGroup, useUser } from 'client/features/One'
|
||||
import { useGetUsersQuery } from 'client/features/OneApi/user'
|
||||
import { useGetGroupsQuery } from 'client/features/OneApi/group'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
import { Field } from 'client/utils'
|
||||
|
||||
@ -25,7 +26,7 @@ export const UID_FIELD = {
|
||||
label: T.InstantiateAsUser,
|
||||
type: INPUT_TYPES.AUTOCOMPLETE,
|
||||
values: () => {
|
||||
const users = useUser()
|
||||
const { data: users = [] } = useGetUsersQuery()
|
||||
|
||||
return users
|
||||
.map(({ ID: value, NAME: text }) => ({ text, value }))
|
||||
@ -41,7 +42,7 @@ export const GID_FIELD = {
|
||||
label: T.InstantiateAsGroup,
|
||||
type: INPUT_TYPES.AUTOCOMPLETE,
|
||||
values: () => {
|
||||
const groups = useGroup()
|
||||
const { data: groups = [] } = useGetGroupsQuery()
|
||||
|
||||
return groups
|
||||
.map(({ ID: value, NAME: text }) => ({ text, value }))
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { string } from 'yup'
|
||||
|
||||
import { useVmGroup } from 'client/features/One'
|
||||
import { useGetVMGroupsQuery } from 'client/features/OneApi/vmGroup'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
import { Field } from 'client/utils'
|
||||
|
||||
@ -25,7 +25,7 @@ export const VM_GROUP_FIELD = {
|
||||
label: T.AssociateToVMGroup,
|
||||
type: INPUT_TYPES.AUTOCOMPLETE,
|
||||
values: () => {
|
||||
const vmGroups = useVmGroup()
|
||||
const { data: vmGroups = [] } = useGetVMGroupsQuery()
|
||||
|
||||
return vmGroups
|
||||
?.map(({ ID, NAME }) => ({ text: `#${ID} ${NAME}`, value: String(ID) }))
|
||||
@ -51,7 +51,7 @@ export const ROLE_FIELD = {
|
||||
htmlType: (vmGroup) =>
|
||||
vmGroup && vmGroup !== '' ? undefined : INPUT_TYPES.HIDDEN,
|
||||
values: (vmGroupSelected) => {
|
||||
const vmGroups = useVmGroup()
|
||||
const { data: vmGroups = [] } = useGetVMGroupsQuery()
|
||||
|
||||
const roles = vmGroups
|
||||
?.filter(({ ID }) => ID === vmGroupSelected)
|
||||
|
@ -20,16 +20,13 @@ import PropTypes from 'prop-types'
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import {
|
||||
useUserApi,
|
||||
useVmGroupApi,
|
||||
useVmTemplateApi,
|
||||
useHostApi,
|
||||
useImageApi,
|
||||
useDatastoreApi,
|
||||
} from 'client/features/One'
|
||||
import { useGetVMGroupsQuery } from 'client/features/OneApi/vmGroup'
|
||||
import { useGetHostsQuery } from 'client/features/OneApi/host'
|
||||
import { useGetImagesQuery } from 'client/features/OneApi/image'
|
||||
import { useGetUsersQuery } from 'client/features/OneApi/user'
|
||||
import { useGetDatastoresQuery } from 'client/features/OneApi/datastore'
|
||||
import { useLazyGetTemplateQuery } from 'client/features/OneApi/vmTemplate'
|
||||
|
||||
import { useFetch } from 'client/hooks'
|
||||
import FormStepper, { SkeletonStepsForm } from 'client/components/FormStepper'
|
||||
import Steps from 'client/components/Forms/VmTemplate/CreateForm/Steps'
|
||||
|
||||
@ -55,24 +52,15 @@ const CreateForm = ({ template, onSubmit }) => {
|
||||
}
|
||||
|
||||
const PreFetchingForm = ({ templateId, onSubmit }) => {
|
||||
const { getUsers } = useUserApi()
|
||||
const { getVmGroups } = useVmGroupApi()
|
||||
const { getHosts } = useHostApi()
|
||||
const { getImages } = useImageApi()
|
||||
const { getDatastores } = useDatastoreApi()
|
||||
const { getVmTemplate } = useVmTemplateApi()
|
||||
|
||||
const { fetchRequest, data } = useFetch(() =>
|
||||
getVmTemplate(templateId, { extended: true })
|
||||
)
|
||||
useGetVMGroupsQuery()
|
||||
useGetHostsQuery()
|
||||
useGetImagesQuery()
|
||||
useGetUsersQuery()
|
||||
useGetDatastoresQuery()
|
||||
const [getTemplate, { data }] = useLazyGetTemplateQuery()
|
||||
|
||||
useEffect(() => {
|
||||
templateId && fetchRequest()
|
||||
getHosts()
|
||||
getImages()
|
||||
getDatastores()
|
||||
getUsers()
|
||||
getVmGroups()
|
||||
templateId && getTemplate({ id: templateId, extended: true })
|
||||
}, [])
|
||||
|
||||
return templateId && !data ? (
|
||||
|
@ -16,7 +16,8 @@
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { string } from 'yup'
|
||||
|
||||
import { useGroup, useUser } from 'client/features/One'
|
||||
import { useGetUsersQuery } from 'client/features/OneApi/user'
|
||||
import { useGetGroupsQuery } from 'client/features/OneApi/group'
|
||||
import { INPUT_TYPES } from 'client/constants'
|
||||
|
||||
export const UID_FIELD = {
|
||||
@ -24,7 +25,7 @@ export const UID_FIELD = {
|
||||
label: 'Instantiate as different User',
|
||||
type: INPUT_TYPES.AUTOCOMPLETE,
|
||||
values: () => {
|
||||
const users = useUser()
|
||||
const { data: users = [] } = useGetUsersQuery()
|
||||
|
||||
return users
|
||||
.map(({ ID: value, NAME: text }) => ({ text, value }))
|
||||
@ -39,7 +40,7 @@ export const GID_FIELD = {
|
||||
label: 'Instantiate as different Group',
|
||||
type: INPUT_TYPES.AUTOCOMPLETE,
|
||||
values: () => {
|
||||
const groups = useGroup()
|
||||
const { data: groups = [] } = useGetGroupsQuery()
|
||||
|
||||
return groups
|
||||
.map(({ ID: value, NAME: text }) => ({ text, value }))
|
||||
|
@ -16,7 +16,7 @@
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { string } from 'yup'
|
||||
|
||||
import { useVmGroup } from 'client/features/One'
|
||||
import { useGetVMGroupsQuery } from 'client/features/OneApi/vmGroup'
|
||||
import { INPUT_TYPES } from 'client/constants'
|
||||
|
||||
export const VM_GROUP_FIELD = {
|
||||
@ -24,7 +24,7 @@ export const VM_GROUP_FIELD = {
|
||||
label: 'Associate VM to a VM Group',
|
||||
type: INPUT_TYPES.AUTOCOMPLETE,
|
||||
values: () => {
|
||||
const vmGroups = useVmGroup()
|
||||
const { data: vmGroups = [] } = useGetVMGroupsQuery()
|
||||
|
||||
return vmGroups
|
||||
?.map(({ ID, NAME }) => ({ text: `#${ID} ${NAME}`, value: String(ID) }))
|
||||
@ -49,7 +49,7 @@ export const ROLE_FIELD = {
|
||||
htmlType: (vmGroup) =>
|
||||
vmGroup && vmGroup !== '' ? undefined : INPUT_TYPES.HIDDEN,
|
||||
values: (vmGroupSelected) => {
|
||||
const vmGroups = useVmGroup()
|
||||
const { data: vmGroups = [] } = useGetVMGroupsQuery()
|
||||
|
||||
const roles = vmGroups
|
||||
?.filter(({ ID }) => ID === vmGroupSelected)
|
||||
|
@ -18,7 +18,7 @@ import PropTypes from 'prop-types'
|
||||
import makeStyles from '@mui/styles/makeStyles'
|
||||
|
||||
import { useListForm } from 'client/hooks'
|
||||
import { useVmTemplateApi } from 'client/features/One'
|
||||
import { useLazyGetTemplateQuery } from 'client/features/OneApi/vmTemplate'
|
||||
import { VmTemplatesTable } from 'client/components/Tables'
|
||||
|
||||
import { SCHEMA } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/VmTemplatesTable/schema'
|
||||
@ -39,7 +39,7 @@ const useStyles = makeStyles({
|
||||
const Content = ({ data, setFormData }) => {
|
||||
const classes = useStyles()
|
||||
const selectedTemplate = data?.[0]
|
||||
const { getVmTemplate } = useVmTemplateApi()
|
||||
const [getVmTemplate] = useLazyGetTemplateQuery()
|
||||
|
||||
const { handleSelect, handleClear } = useListForm({
|
||||
key: STEP_ID,
|
||||
@ -48,12 +48,12 @@ const Content = ({ data, setFormData }) => {
|
||||
|
||||
const handleSelectedRows = async (rows) => {
|
||||
const { original: templateSelected } = rows?.[0] ?? {}
|
||||
const { ID } = templateSelected ?? {}
|
||||
const { ID: id } = templateSelected ?? {}
|
||||
|
||||
if (!ID) return handleClear()
|
||||
if (!id) return handleClear()
|
||||
|
||||
const extendedTemplate = ID
|
||||
? await getVmTemplate(ID, { extended: true })
|
||||
const extendedTemplate = id
|
||||
? await getVmTemplate({ id, extended: true }).unwrap()
|
||||
: {}
|
||||
|
||||
setFormData((prev) => ({
|
||||
|
@ -20,12 +20,9 @@ import PropTypes from 'prop-types'
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import { useFetch } from 'client/hooks'
|
||||
import {
|
||||
useUserApi,
|
||||
useVmGroupApi,
|
||||
useVmTemplateApi,
|
||||
} from 'client/features/One'
|
||||
import { useGetUsersQuery } from 'client/features/OneApi/user'
|
||||
import { useGetGroupsQuery } from 'client/features/OneApi/group'
|
||||
import { useLazyGetTemplateQuery } from 'client/features/OneApi/vmTemplate'
|
||||
import FormStepper, { SkeletonStepsForm } from 'client/components/FormStepper'
|
||||
import Steps from 'client/components/Forms/VmTemplate/InstantiateForm/Steps'
|
||||
|
||||
@ -53,17 +50,12 @@ const InstantiateForm = ({ template, onSubmit }) => {
|
||||
}
|
||||
|
||||
const PreFetchingForm = ({ templateId, onSubmit }) => {
|
||||
const { getUsers } = useUserApi()
|
||||
const { getVmGroups } = useVmGroupApi()
|
||||
const { getVmTemplate } = useVmTemplateApi()
|
||||
const { fetchRequest, data } = useFetch(() =>
|
||||
getVmTemplate(templateId, { extended: true })
|
||||
)
|
||||
useGetUsersQuery()
|
||||
useGetGroupsQuery()
|
||||
const [getTemplate, { data }] = useLazyGetTemplateQuery()
|
||||
|
||||
useEffect(() => {
|
||||
templateId && fetchRequest()
|
||||
getUsers()
|
||||
getVmGroups()
|
||||
templateId && getTemplate({ id: templateId, extended: true })
|
||||
}, [])
|
||||
|
||||
return templateId && !data ? (
|
||||
|
@ -109,8 +109,7 @@ const HeaderPopover = memo(
|
||||
<Fade {...TransitionProps} timeout={300}>
|
||||
<Paper
|
||||
variant="outlined"
|
||||
style={mobileStyles}
|
||||
sx={{ p: headerTitle ? 2 : 0 }}
|
||||
sx={{ p: headerTitle ? 2 : 0, ...mobileStyles }}
|
||||
>
|
||||
{(headerTitle || isMobile) && (
|
||||
<Box
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useMemo, memo, JSXElementConstructor } from 'react'
|
||||
import { useMemo, memo, ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Button } from '@mui/material'
|
||||
@ -70,7 +70,7 @@ ButtonView.displayName = 'ButtonView'
|
||||
*
|
||||
* These views are defined in yaml config.
|
||||
*
|
||||
* @returns {JSXElementConstructor} Returns interface views list
|
||||
* @returns {ReactElement} Returns interface views list
|
||||
*/
|
||||
const View = () => {
|
||||
const { view: currentView, views = {} } = useAuth()
|
||||
|
@ -13,13 +13,12 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useEffect, JSXElementConstructor } from 'react'
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
import { MenuList, MenuItem, LinearProgress } from '@mui/material'
|
||||
import { Language as ZoneIcon } from 'iconoir-react'
|
||||
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useZone, useZoneApi } from 'client/features/One'
|
||||
import { useGetZonesQuery } from 'client/features/OneApi/zone'
|
||||
import HeaderPopover from 'client/components/Header/Popover'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
@ -27,12 +26,10 @@ import { T } from 'client/constants'
|
||||
/**
|
||||
* Menu to select the OpenNebula Zone.
|
||||
*
|
||||
* @returns {JSXElementConstructor} Returns Zone list
|
||||
* @returns {ReactElement} Returns Zone list
|
||||
*/
|
||||
const Zone = () => {
|
||||
const zones = useZone()
|
||||
const { getZones } = useZoneApi()
|
||||
const { fetchRequest, loading } = useFetch(getZones)
|
||||
const { data: zones = [], isLoading } = useGetZonesQuery()
|
||||
|
||||
return (
|
||||
<HeaderPopover
|
||||
@ -42,30 +39,24 @@ const Zone = () => {
|
||||
buttonProps={{ 'data-cy': 'header-zone-button' }}
|
||||
headerTitle={<Translate word={T.Zones} />}
|
||||
>
|
||||
{({ handleClose }) => {
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && <LinearProgress color="secondary" />}
|
||||
<MenuList>
|
||||
{zones?.length ? (
|
||||
zones?.map(({ ID, NAME }) => (
|
||||
<MenuItem key={`zone-${ID}`} onClick={handleClose}>
|
||||
{NAME}
|
||||
</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled>
|
||||
<Translate word={T.NotFound} />
|
||||
{({ handleClose }) => (
|
||||
<>
|
||||
{isLoading && <LinearProgress color="secondary" />}
|
||||
<MenuList>
|
||||
{zones?.length ? (
|
||||
zones?.map(({ ID, NAME }) => (
|
||||
<MenuItem key={`zone-${ID}`} onClick={handleClose}>
|
||||
{NAME}
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuList>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled>
|
||||
<Translate word={T.NotFound} />
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuList>
|
||||
</>
|
||||
)}
|
||||
</HeaderPopover>
|
||||
)
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ const ListInfiniteScroll = ({ list, renderResult }) => {
|
||||
<LinearProgress
|
||||
ref={loaderRef}
|
||||
color="secondary"
|
||||
style={{ width: '100%', marginTop: 10 }}
|
||||
sx={{ width: '100%', marginTop: 10 }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -13,46 +13,55 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useCluster, useClusterApi } from 'client/features/One'
|
||||
import { useGetClustersQuery } from 'client/features/OneApi/cluster'
|
||||
|
||||
import { SkeletonTable, EnhancedTable } from 'client/components/Tables'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import ClusterColumns from 'client/components/Tables/Clusters/columns'
|
||||
import ClusterRow from 'client/components/Tables/Clusters/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'clusters'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Clusters table
|
||||
*/
|
||||
const ClustersTable = (props) => {
|
||||
const columns = useMemo(() => ClusterColumns, [])
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const clusters = useCluster()
|
||||
const { getClusters } = useClusterApi()
|
||||
const { filterPool } = useAuth()
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { data = [], isFetching, refetch } = useGetClustersQuery()
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } =
|
||||
useFetch(getClusters)
|
||||
const { INIT, PENDING } = STATUS
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [filterPool])
|
||||
|
||||
if (clusters?.length === 0 && [INIT, PENDING].includes(status)) {
|
||||
return <SkeletonTable />
|
||||
}
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView(RESOURCE_NAMES.CLUSTER)?.filters,
|
||||
columns: ClusterColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={clusters}
|
||||
isLoading={loading || reloading}
|
||||
data={data}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={ClusterRow}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
ClustersTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
ClustersTable.displayName = 'ClustersTable'
|
||||
|
||||
export default ClustersTable
|
||||
|
@ -13,55 +13,60 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useDatastore, useDatastoreApi } from 'client/features/One'
|
||||
import { useGetDatastoresQuery } from 'client/features/OneApi/datastore'
|
||||
|
||||
import { SkeletonTable, EnhancedTable } from 'client/components/Tables'
|
||||
import { createColumns } from 'client/components/Tables/Enhanced/Utils'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import DatastoreColumns from 'client/components/Tables/Datastores/columns'
|
||||
import DatastoreRow from 'client/components/Tables/Datastores/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'datastores'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Datastores table
|
||||
*/
|
||||
const DatastoresTable = (props) => {
|
||||
const { view, getResourceView, filterPool } = useAuth()
|
||||
const {
|
||||
rootProps = {},
|
||||
searchProps = {},
|
||||
useQuery = useGetDatastoresQuery,
|
||||
...rest
|
||||
} = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { data = [], isFetching, refetch } = useQuery()
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView('DATASTORE')?.filters,
|
||||
filters: getResourceView(RESOURCE_NAMES.DATASTORE)?.filters,
|
||||
columns: DatastoreColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
const datastores = useDatastore()
|
||||
const { getDatastores } = useDatastoreApi()
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } =
|
||||
useFetch(getDatastores)
|
||||
const { INIT, PENDING } = STATUS
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [filterPool])
|
||||
|
||||
if (datastores?.length === 0 && [INIT, PENDING].includes(status)) {
|
||||
return <SkeletonTable />
|
||||
}
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={datastores}
|
||||
isLoading={loading || reloading}
|
||||
data={data}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={DatastoreRow}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
DatastoresTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
DatastoresTable.displayName = 'DatastoresTable'
|
||||
|
||||
export default DatastoresTable
|
||||
|
@ -13,71 +13,22 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import api from 'client/features/OneApi/datastore'
|
||||
import { DatastoreCard } from 'client/components/Cards'
|
||||
|
||||
import { User, Group, Lock, Cloud, Server } from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
const Row = memo(
|
||||
({ original, ...props }) => {
|
||||
const state = api.endpoints.getDatastores.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((datastore) => +datastore.ID === +original.ID),
|
||||
})
|
||||
|
||||
import {
|
||||
StatusCircle,
|
||||
LinearProgressWithLabel,
|
||||
StatusChip,
|
||||
} from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
|
||||
import * as DatastoreModel from 'client/models/Datastore'
|
||||
|
||||
const Row = ({ original, value, ...props }) => {
|
||||
const classes = rowStyles()
|
||||
const { ID, NAME, UNAME, GNAME, TYPE, CLUSTERS, LOCK, PROVISION_ID } = value
|
||||
|
||||
const { percentOfUsed, percentLabel } = DatastoreModel.getCapacityInfo(value)
|
||||
|
||||
const { color: stateColor, name: stateName } =
|
||||
DatastoreModel.getState(original)
|
||||
|
||||
return (
|
||||
<div {...props} data-cy={`datastore-${ID}`}>
|
||||
<div>
|
||||
<StatusCircle color={stateColor} tooltip={stateName} />
|
||||
</div>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span">{NAME}</Typography>
|
||||
<span className={classes.labels}>
|
||||
{LOCK && <Lock />}
|
||||
<StatusChip text={TYPE} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${ID}`}</span>
|
||||
<span title={`Owner: ${UNAME}`}>
|
||||
<User />
|
||||
<span>{` ${UNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Group: ${GNAME}`}>
|
||||
<Group />
|
||||
<span>{` ${GNAME}`}</span>
|
||||
</span>
|
||||
{PROVISION_ID && (
|
||||
<span title={`Provision ID: #${PROVISION_ID}`}>
|
||||
<Cloud />
|
||||
<span>{` ${PROVISION_ID}`}</span>
|
||||
</span>
|
||||
)}
|
||||
<span title={`Cluster IDs: ${CLUSTERS.join(',')}`}>
|
||||
<Server />
|
||||
<span>{` ${CLUSTERS.join(',')}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.secondary}>
|
||||
<LinearProgressWithLabel value={percentOfUsed} label={percentLabel} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <DatastoreCard datastore={state ?? original} rootProps={props} />
|
||||
},
|
||||
(prev, next) => prev.className === next.className
|
||||
)
|
||||
|
||||
Row.propTypes = {
|
||||
original: PropTypes.object,
|
||||
@ -86,4 +37,6 @@ Row.propTypes = {
|
||||
handleClick: PropTypes.func,
|
||||
}
|
||||
|
||||
Row.displayName = 'DatastoreRow'
|
||||
|
||||
export default Row
|
||||
|
@ -13,15 +13,16 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useEffect, useState, useCallback } from 'react'
|
||||
import { useMemo, useEffect, useState, useCallback, ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useMarketplaceAppApi } from 'client/features/One'
|
||||
import { useLazyGetDockerHubTagsQuery } from 'client/features/OneApi/marketplaceApp'
|
||||
|
||||
import EnhancedTable from 'client/components/Tables/Enhanced'
|
||||
import DockerHubTagsRow from 'client/components/Tables/DockerHubTags/row'
|
||||
import { MarketplaceApp } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'docker-tags'
|
||||
|
||||
const getNextPageFromUrl = (url) => {
|
||||
try {
|
||||
@ -33,49 +34,49 @@ const getNextPageFromUrl = (url) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {MarketplaceApp} props.app - Marketplace App
|
||||
* @returns {ReactElement} Datastores table
|
||||
*/
|
||||
const DockerHubTagsTable = ({ app, ...props } = {}) => {
|
||||
const { getDockerHubTags } = useMarketplaceAppApi()
|
||||
const appId = app?.ID
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const [tags, setTags] = useState(() => [])
|
||||
|
||||
const {
|
||||
data: { next, results = [] } = {},
|
||||
fetchRequest,
|
||||
loading,
|
||||
status,
|
||||
STATUS: { INIT, FETCHED },
|
||||
} = useFetch(getDockerHubTags)
|
||||
const [fetchRequest, { data: { next } = {}, isSuccess, isFetching }] =
|
||||
useLazyGetDockerHubTagsQuery()
|
||||
|
||||
useEffect(() => {
|
||||
const appId = app?.ID
|
||||
const requests = app?.ID && {
|
||||
[INIT]: () => fetchRequest({ id: appId }),
|
||||
[FETCHED]: () => {
|
||||
if (!next) return
|
||||
;(async () => {
|
||||
if (!appId || (isSuccess && !next)) return
|
||||
|
||||
const page = getNextPageFromUrl(next)
|
||||
fetchRequest({ id: appId, page })
|
||||
},
|
||||
}
|
||||
|
||||
requests[status]?.()
|
||||
}, [app?.ID, status])
|
||||
|
||||
useEffect(() => {
|
||||
status === FETCHED && setTags((prev) => prev.concat(results))
|
||||
}, [status])
|
||||
const page = next ? getNextPageFromUrl(next) : undefined
|
||||
const { results = [] } = await fetchRequest({ id: appId, page }).unwrap()
|
||||
setTags((prev) => prev.concat(results))
|
||||
})()
|
||||
}, [appId, next])
|
||||
|
||||
const memoData = useMemo(() => tags, [tags?.length])
|
||||
|
||||
const memoColumns = useMemo(() => [{ accessor: 'name' }], [])
|
||||
|
||||
if (!appId) {
|
||||
return <>{'App ID is required'}</>
|
||||
}
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={memoColumns}
|
||||
data={memoData}
|
||||
isLoading={loading}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={fetchRequest}
|
||||
isLoading={isFetching}
|
||||
getRowId={useCallback((row) => String(row.name), [])}
|
||||
RowComponent={DockerHubTagsRow}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
import { Cancel } from 'iconoir-react'
|
||||
import { UseFiltersInstanceProps } from 'react-table'
|
||||
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
/**
|
||||
@ -96,10 +96,9 @@ const CategoryFilter = ({
|
||||
<ListSubheader
|
||||
disableSticky
|
||||
disableGutters
|
||||
title={Tr(title)}
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
sx={{ display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
{Tr(title)}
|
||||
<Translate word={title} />
|
||||
{isFiltered && (
|
||||
<Tooltip title={<Translate word={T.Clear} />}>
|
||||
<IconButton disableRipple size="small" onClick={handleClear}>
|
||||
|
@ -13,10 +13,13 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, JSXElementConstructor } from 'react'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { memo, ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { Row } from 'react-table'
|
||||
|
||||
import QueryButton from 'client/components/Buttons/QueryButton'
|
||||
import { Action } from 'client/components/Cards/SelectCard'
|
||||
import { ButtonToTriggerForm } from 'client/components/Forms'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
@ -29,7 +32,7 @@ import { CreateStepsCallback, CreateFormCallback } from 'client/utils'
|
||||
* @typedef {object} Option
|
||||
* @property {string} name - Label of option
|
||||
* @property {DialogProps} [dialogProps] - Dialog properties
|
||||
* @property {JSXElementConstructor} [icon] - Icon
|
||||
* @property {ReactElement} [icon] - Icon
|
||||
* @property {boolean} [isConfirmDialog] - If `true`, the form will be a dialog with confirmation buttons
|
||||
* @property {boolean|function(Row[]):boolean} [disabled] - If `true`, option will be disabled
|
||||
* @property {function(object, Row[])} onSubmit - Function to handle after finish the form
|
||||
@ -48,18 +51,12 @@ import { CreateStepsCallback, CreateFormCallback } from 'client/utils'
|
||||
* @property {function(Row[])} [action] - Singular action without form
|
||||
* @property {boolean|{min: number, max: number}} [selected] - Condition for selected rows
|
||||
* @property {boolean|function(Row[]):boolean} [disabled] - If `true`, action will be disabled
|
||||
* @property {function(Row[]):object} [useQuery] - Function to get rtk query result
|
||||
*/
|
||||
|
||||
/**
|
||||
* Render global action.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {GlobalAction[]} props.item - Item action
|
||||
* @param {Row[]} props.selectedRows - Selected rows
|
||||
* @returns {JSXElementConstructor} Component JSX
|
||||
*/
|
||||
const ActionItem = memo(
|
||||
({ item, selectedRows }) => {
|
||||
/** @type {GlobalAction} */
|
||||
const {
|
||||
accessor,
|
||||
dataCy,
|
||||
@ -71,6 +68,7 @@ const ActionItem = memo(
|
||||
options,
|
||||
action,
|
||||
disabled,
|
||||
useQuery,
|
||||
} = item
|
||||
|
||||
const buttonProps = {
|
||||
@ -87,6 +85,8 @@ const ActionItem = memo(
|
||||
|
||||
return action ? (
|
||||
<Action {...buttonProps} handleClick={() => action?.(selectedRows)} />
|
||||
) : useQuery ? (
|
||||
<QueryButton {...buttonProps} useQuery={() => useQuery?.(selectedRows)} />
|
||||
) : (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={buttonProps}
|
||||
@ -141,48 +141,11 @@ const ActionItem = memo(
|
||||
}
|
||||
)
|
||||
|
||||
export const ActionPropTypes = PropTypes.shape({
|
||||
accessor: PropTypes.string,
|
||||
dataCy: PropTypes.string,
|
||||
variant: PropTypes.string,
|
||||
color: PropTypes.string,
|
||||
size: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
icon: PropTypes.any,
|
||||
disabled: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|
||||
selected: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.shape({
|
||||
min: PropTypes.number,
|
||||
max: PropTypes.number,
|
||||
}),
|
||||
]),
|
||||
action: PropTypes.func,
|
||||
isConfirmDialog: PropTypes.bool,
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
accessor: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
icon: PropTypes.any,
|
||||
disabled: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|
||||
form: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
dialogProps: PropTypes.shape({
|
||||
...DialogPropTypes,
|
||||
description: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
subheader: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
}),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
ActionItem.propTypes = {
|
||||
item: ActionPropTypes,
|
||||
item: PropTypes.object,
|
||||
selectedRows: PropTypes.array,
|
||||
}
|
||||
|
||||
ActionItem.displayName = 'ActionItem'
|
||||
|
||||
export default ActionItem
|
||||
export { ActionItem as Action }
|
||||
|
@ -16,7 +16,7 @@
|
||||
import { JSXElementConstructor, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Stack, Checkbox } from '@mui/material'
|
||||
import { Checkbox } from '@mui/material'
|
||||
import {
|
||||
UseTableInstanceProps,
|
||||
UseRowSelectState,
|
||||
@ -24,8 +24,8 @@ import {
|
||||
UseRowSelectInstanceProps,
|
||||
} from 'react-table'
|
||||
|
||||
import Action, {
|
||||
ActionPropTypes,
|
||||
import {
|
||||
Action,
|
||||
GlobalAction,
|
||||
} from 'client/components/Tables/Enhanced/Utils/GlobalActions/Action'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
@ -37,9 +37,14 @@ import { T } from 'client/constants'
|
||||
* @param {object} props - Props
|
||||
* @param {GlobalAction[]} props.globalActions - Possible bulk actions
|
||||
* @param {UseTableInstanceProps} props.useTableProps - Table props
|
||||
* @param {boolean} props.disableRowSelect - Rows can't select
|
||||
* @returns {JSXElementConstructor} Component JSX with all actions
|
||||
*/
|
||||
const GlobalActions = ({ globalActions = [], useTableProps }) => {
|
||||
const GlobalActions = ({
|
||||
disableRowSelect = false,
|
||||
globalActions = [],
|
||||
useTableProps,
|
||||
}) => {
|
||||
/** @type {UseRowSelectInstanceProps} */
|
||||
const { getToggleAllPageRowsSelectedProps, getToggleAllRowsSelectedProps } =
|
||||
useTableProps
|
||||
@ -69,18 +74,20 @@ const GlobalActions = ({ globalActions = [], useTableProps }) => {
|
||||
)
|
||||
|
||||
return (
|
||||
<Stack direction="row" flexWrap="wrap" alignItems="center" gap={1.5}>
|
||||
<Checkbox
|
||||
{...getToggleAllPageRowsSelectedProps()}
|
||||
title={Tr(T.ToggleAllCurrentPageRowsSelected)}
|
||||
indeterminate={getToggleAllRowsSelectedProps().indeterminate}
|
||||
color="secondary"
|
||||
/>
|
||||
|
||||
<>
|
||||
{!disableRowSelect && (
|
||||
<Checkbox
|
||||
{...getToggleAllPageRowsSelectedProps()}
|
||||
title={Tr(T.ToggleAllCurrentPageRowsSelected)}
|
||||
indeterminate={getToggleAllRowsSelectedProps().indeterminate}
|
||||
color="secondary"
|
||||
/>
|
||||
)}
|
||||
{actionsNoSelected?.map((item) => (
|
||||
<Action key={item.accessor} item={item} />
|
||||
))}
|
||||
{numberOfRowSelected > 0 &&
|
||||
{!disableRowSelect &&
|
||||
numberOfRowSelected > 0 &&
|
||||
actionsSelected?.map((item, idx) => {
|
||||
const { min = 1, max = Number.MAX_SAFE_INTEGER } =
|
||||
item?.selected ?? {}
|
||||
@ -92,15 +99,14 @@ const GlobalActions = ({ globalActions = [], useTableProps }) => {
|
||||
|
||||
return <Action key={key} item={item} selectedRows={selectedRows} />
|
||||
})}
|
||||
</Stack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
GlobalActions.propTypes = {
|
||||
globalActions: PropTypes.arrayOf(ActionPropTypes),
|
||||
globalActions: PropTypes.array,
|
||||
useTableProps: PropTypes.object,
|
||||
disableRowSelect: PropTypes.bool,
|
||||
}
|
||||
|
||||
export { Action, ActionPropTypes }
|
||||
|
||||
export default GlobalActions
|
||||
|
@ -79,9 +79,8 @@ const GlobalFilter = ({ useTableProps, className, searchProps }) => {
|
||||
|
||||
const handleChange = useCallback(
|
||||
// Set undefined to remove the filter entirely
|
||||
debounce((newFilter) => {
|
||||
setGlobalFilter(newFilter || undefined)
|
||||
}, 200)
|
||||
debounce((newFilter) => setGlobalFilter(newFilter || undefined), 200),
|
||||
[setGlobalFilter]
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useEffect, useMemo, JSXElementConstructor } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
@ -44,8 +43,6 @@ const GlobalSort = ({ useTableProps }) => {
|
||||
/** @type {UseSortByState} */
|
||||
const { sortBy } = state
|
||||
|
||||
useEffect(() => () => setSortBy([]), [])
|
||||
|
||||
const headersNotSorted = useMemo(
|
||||
() =>
|
||||
headers.filter(
|
||||
@ -66,7 +63,9 @@ const GlobalSort = ({ useTableProps }) => {
|
||||
setSortBy(sortBy.map((sort) => (sort.id === id ? { ...sort, desc } : sort)))
|
||||
}
|
||||
|
||||
return (
|
||||
useEffect(() => () => setSortBy([]), [])
|
||||
|
||||
return !headersNotSorted.length && !sortBy.length ? null : (
|
||||
<Stack direction="row" gap="0.5em" flexWrap="wrap">
|
||||
{useMemo(
|
||||
() => (
|
||||
|
@ -22,7 +22,7 @@ import { List, ListSubheader, IconButton } from '@mui/material'
|
||||
import { TreeView, TreeItem } from '@mui/lab'
|
||||
import { UseFiltersInstanceProps } from 'react-table'
|
||||
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
|
||||
const buildTree = (data = [], separator = '/') => {
|
||||
const mapper = {}
|
||||
@ -101,10 +101,9 @@ const LabelFilter = ({ title, column }) => {
|
||||
<ListSubheader
|
||||
disableSticky
|
||||
disableGutters
|
||||
title={Tr(title)}
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
sx={{ display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
{Tr(title)}
|
||||
<Translate word={title} />
|
||||
{isFiltered && (
|
||||
<IconButton
|
||||
disableRipple
|
||||
|
@ -14,20 +14,16 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import CategoryFilter from 'client/components/Tables/Enhanced/Utils/CategoryFilter'
|
||||
import GlobalActions, {
|
||||
Action,
|
||||
ActionPropTypes,
|
||||
} from 'client/components/Tables/Enhanced/Utils/GlobalActions'
|
||||
import GlobalActions from 'client/components/Tables/Enhanced/Utils/GlobalActions'
|
||||
import GlobalFilter from 'client/components/Tables/Enhanced/Utils/GlobalFilter'
|
||||
import GlobalSelectedRows from 'client/components/Tables/Enhanced/Utils/GlobalSelectedRows'
|
||||
import GlobalSort from 'client/components/Tables/Enhanced/Utils/GlobalSort'
|
||||
import LabelFilter from 'client/components/Tables/Enhanced/Utils/LabelFilter'
|
||||
|
||||
export * from 'client/components/Tables/Enhanced/Utils/GlobalActions/Action'
|
||||
export * from 'client/components/Tables/Enhanced/Utils/utils'
|
||||
|
||||
export {
|
||||
Action,
|
||||
ActionPropTypes,
|
||||
CategoryFilter,
|
||||
GlobalActions,
|
||||
GlobalFilter,
|
||||
|
@ -19,7 +19,7 @@ import PropTypes from 'prop-types'
|
||||
|
||||
import clsx from 'clsx'
|
||||
import { InfoEmpty } from 'iconoir-react'
|
||||
import { Box, LinearProgress } from '@mui/material'
|
||||
import { Box } from '@mui/material'
|
||||
import {
|
||||
useGlobalFilter,
|
||||
useFilters,
|
||||
@ -35,14 +35,12 @@ import {
|
||||
import Toolbar from 'client/components/Tables/Enhanced/toolbar'
|
||||
import Pagination from 'client/components/Tables/Enhanced/pagination'
|
||||
import Filters from 'client/components/Tables/Enhanced/filters'
|
||||
import { ActionPropTypes } from 'client/components/Tables/Enhanced/Utils'
|
||||
import EnhancedTableStyles from 'client/components/Tables/Enhanced/styles'
|
||||
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const EnhancedTable = ({
|
||||
canFetchMore,
|
||||
columns,
|
||||
globalActions,
|
||||
globalFilter,
|
||||
@ -50,9 +48,12 @@ const EnhancedTable = ({
|
||||
fetchMore,
|
||||
getRowId,
|
||||
initialState,
|
||||
refetch,
|
||||
isLoading,
|
||||
onlyGlobalSearch,
|
||||
onlyGlobalSelectedRows,
|
||||
disableRowSelect,
|
||||
disableGlobalSort,
|
||||
onSelectedRowsChange,
|
||||
pageSize = 10,
|
||||
RowComponent,
|
||||
@ -64,7 +65,7 @@ const EnhancedTable = ({
|
||||
}) => {
|
||||
const styles = EnhancedTableStyles()
|
||||
|
||||
const isFetching = isLoading && data === undefined
|
||||
const isUninitialized = isLoading && data === undefined
|
||||
|
||||
const defaultColumn = useMemo(
|
||||
() => ({
|
||||
@ -140,7 +141,7 @@ const EnhancedTable = ({
|
||||
const canNextPage =
|
||||
pageCount === -1 ? page.length >= pageSize : newPage < pageCount - 1
|
||||
|
||||
newPage > pageIndex && canFetchMore && !canNextPage && fetchMore?.()
|
||||
newPage > pageIndex && !canNextPage && fetchMore?.()
|
||||
}
|
||||
|
||||
return (
|
||||
@ -151,13 +152,15 @@ const EnhancedTable = ({
|
||||
>
|
||||
<div className={styles.toolbar}>
|
||||
{/* TOOLBAR */}
|
||||
{!isFetching && (
|
||||
<Toolbar
|
||||
globalActions={globalActions}
|
||||
onlyGlobalSelectedRows={onlyGlobalSelectedRows}
|
||||
useTableProps={useTableProps}
|
||||
/>
|
||||
)}
|
||||
<Toolbar
|
||||
refetch={refetch}
|
||||
isLoading={isLoading}
|
||||
globalActions={globalActions}
|
||||
disableRowSelect={disableRowSelect}
|
||||
disableGlobalSort={disableGlobalSort}
|
||||
onlyGlobalSelectedRows={onlyGlobalSelectedRows}
|
||||
useTableProps={useTableProps}
|
||||
/>
|
||||
|
||||
{/* PAGINATION */}
|
||||
<div className={styles.pagination}>
|
||||
@ -170,17 +173,9 @@ const EnhancedTable = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isLoading && (
|
||||
<LinearProgress
|
||||
size="1em"
|
||||
color="secondary"
|
||||
className={styles.loading}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={styles.table}>
|
||||
{/* FILTERS */}
|
||||
{!isFetching && (
|
||||
{!isUninitialized && (
|
||||
<Filters
|
||||
onlyGlobalSearch={onlyGlobalSearch}
|
||||
useTableProps={useTableProps}
|
||||
@ -190,7 +185,7 @@ const EnhancedTable = ({
|
||||
|
||||
<div className={clsx(styles.body, classes.body)}>
|
||||
{/* NO DATA MESSAGE */}
|
||||
{!isFetching && page?.length === 0 && (
|
||||
{!isUninitialized && page?.length === 0 && (
|
||||
<span className={styles.noDataMessage}>
|
||||
<InfoEmpty />
|
||||
<Translate word={T.NoDataAvailable} />
|
||||
@ -219,8 +214,10 @@ const EnhancedTable = ({
|
||||
value={values}
|
||||
className={isSelected ? 'selected' : ''}
|
||||
onClick={() => {
|
||||
singleSelect && toggleAllRowsSelected(false)
|
||||
toggleRowSelected(!isSelected)
|
||||
if (!disableRowSelect) {
|
||||
singleSelect && toggleAllRowsSelected?.(false)
|
||||
toggleRowSelected?.(!isSelected)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
@ -231,9 +228,9 @@ const EnhancedTable = ({
|
||||
)
|
||||
}
|
||||
|
||||
export const EnhancedTableProps = {
|
||||
EnhancedTable.propTypes = {
|
||||
canFetchMore: PropTypes.bool,
|
||||
globalActions: PropTypes.arrayOf(ActionPropTypes),
|
||||
globalActions: PropTypes.array,
|
||||
columns: PropTypes.array,
|
||||
data: PropTypes.array,
|
||||
globalFilter: PropTypes.func,
|
||||
@ -250,20 +247,19 @@ export const EnhancedTableProps = {
|
||||
searchProps: PropTypes.shape({
|
||||
'data-cy': PropTypes.string,
|
||||
}),
|
||||
refetch: PropTypes.func,
|
||||
isLoading: PropTypes.bool,
|
||||
disableGlobalSort: PropTypes.bool,
|
||||
disableRowSelect: PropTypes.bool,
|
||||
onlyGlobalSearch: PropTypes.bool,
|
||||
onlyGlobalSelectedRows: PropTypes.bool,
|
||||
onSelectedRowsChange: PropTypes.func,
|
||||
pageSize: PropTypes.number,
|
||||
RowComponent: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.func,
|
||||
]),
|
||||
RowComponent: PropTypes.any,
|
||||
showPageCount: PropTypes.bool,
|
||||
singleSelect: PropTypes.bool,
|
||||
}
|
||||
|
||||
EnhancedTable.propTypes = EnhancedTableProps
|
||||
export * from 'client/components/Tables/Enhanced/Utils'
|
||||
|
||||
export default EnhancedTable
|
||||
|
@ -18,66 +18,97 @@ import PropTypes from 'prop-types'
|
||||
|
||||
import { Stack, useMediaQuery } from '@mui/material'
|
||||
import { UseTableInstanceProps, UseRowSelectState } from 'react-table'
|
||||
import { RefreshDouble } from 'iconoir-react'
|
||||
|
||||
import {
|
||||
GlobalActions,
|
||||
GlobalAction,
|
||||
ActionPropTypes,
|
||||
GlobalSelectedRows,
|
||||
GlobalSort,
|
||||
} from 'client/components/Tables/Enhanced/Utils'
|
||||
import { SubmitButton } from 'client/components/FormControl'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {GlobalAction[]} props.globalActions - Global actions
|
||||
* @param {object} props.onlyGlobalSelectedRows - Show only the selected rows
|
||||
* @param {boolean} props.onlyGlobalSelectedRows - Show only the selected rows
|
||||
* @param {boolean} props.disableRowSelect - Rows can't select
|
||||
* @param {boolean} props.disableGlobalSort - Hide the sort filters
|
||||
* @param {UseTableInstanceProps} props.useTableProps - Table props
|
||||
* @param {function():Promise} props.refetch - Function to refetch data
|
||||
* @param {boolean} props.isLoading - The data is fetching
|
||||
* @returns {JSXElementConstructor} Returns table toolbar
|
||||
*/
|
||||
const Toolbar = ({ globalActions, onlyGlobalSelectedRows, useTableProps }) => {
|
||||
const isMobile = useMediaQuery((theme) => theme.breakpoints.down('sm'))
|
||||
const Toolbar = ({
|
||||
globalActions,
|
||||
onlyGlobalSelectedRows,
|
||||
disableGlobalSort = false,
|
||||
disableRowSelect = false,
|
||||
useTableProps,
|
||||
refetch,
|
||||
isLoading,
|
||||
}) => {
|
||||
const isSmallDevice = useMediaQuery((theme) => theme.breakpoints.down('md'))
|
||||
|
||||
/** @type {UseRowSelectState} */
|
||||
const { selectedRowIds } = useTableProps?.state ?? {}
|
||||
|
||||
if (onlyGlobalSelectedRows) {
|
||||
return <GlobalSelectedRows useTableProps={useTableProps} />
|
||||
}
|
||||
const enableGlobalSort = !isSmallDevice && !disableGlobalSort
|
||||
const enableGlobalSelect =
|
||||
!onlyGlobalSelectedRows && !!Object.keys(selectedRowIds).length
|
||||
|
||||
return isMobile ? null : (
|
||||
return (
|
||||
<>
|
||||
<Stack alignItems="start" gap="1em">
|
||||
<GlobalActions
|
||||
globalActions={globalActions}
|
||||
useTableProps={useTableProps}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
className="summary"
|
||||
direction="row"
|
||||
flexWrap="wrap"
|
||||
alignItems="center"
|
||||
gap={'1em'}
|
||||
width={1}
|
||||
>
|
||||
{!isSmallDevice && (
|
||||
<div>
|
||||
<GlobalSort useTableProps={useTableProps} />
|
||||
</div>
|
||||
<Stack direction="row" flexWrap="wrap" alignItems="center" gap="1em">
|
||||
{refetch && (
|
||||
<SubmitButton
|
||||
data-cy="refresh"
|
||||
icon={<RefreshDouble />}
|
||||
title={T.Tooltip}
|
||||
isSubmitting={isLoading}
|
||||
onClick={refetch}
|
||||
/>
|
||||
)}
|
||||
{!!Object.keys(selectedRowIds).length && (
|
||||
<GlobalSelectedRows withAlert useTableProps={useTableProps} />
|
||||
{onlyGlobalSelectedRows && !disableRowSelect ? (
|
||||
<GlobalSelectedRows useTableProps={useTableProps} />
|
||||
) : (
|
||||
<GlobalActions
|
||||
refetch={refetch}
|
||||
isLoading={isLoading}
|
||||
disableRowSelect={disableRowSelect}
|
||||
globalActions={globalActions}
|
||||
useTableProps={useTableProps}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
{(enableGlobalSort || enableGlobalSelect) && (
|
||||
<Stack
|
||||
className="summary"
|
||||
direction="row"
|
||||
flexWrap="wrap"
|
||||
alignItems="center"
|
||||
gap={'1em'}
|
||||
width={1}
|
||||
>
|
||||
{enableGlobalSort && <GlobalSort useTableProps={useTableProps} />}
|
||||
{enableGlobalSelect && (
|
||||
<GlobalSelectedRows withAlert useTableProps={useTableProps} />
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Toolbar.propTypes = {
|
||||
globalActions: PropTypes.arrayOf(ActionPropTypes),
|
||||
globalActions: PropTypes.array,
|
||||
onlyGlobalSelectedRows: PropTypes.bool,
|
||||
disableRowSelect: PropTypes.bool,
|
||||
disableGlobalSort: PropTypes.bool,
|
||||
useTableProps: PropTypes.object,
|
||||
refetch: PropTypes.func,
|
||||
isLoading: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default Toolbar
|
||||
|
@ -13,55 +13,55 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useGroup, useGroupApi } from 'client/features/One'
|
||||
import { useGetGroupsQuery } from 'client/features/OneApi/group'
|
||||
|
||||
import { SkeletonTable, EnhancedTable } from 'client/components/Tables'
|
||||
import { createColumns } from 'client/components/Tables/Enhanced/Utils'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import GroupColumns from 'client/components/Tables/Groups/columns'
|
||||
import GroupRow from 'client/components/Tables/Groups/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'groups'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Groups table
|
||||
*/
|
||||
const GroupsTable = (props) => {
|
||||
const { view, getResourceView, filterPool } = useAuth()
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { data = [], isFetching, refetch } = useGetGroupsQuery()
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView('GROUP')?.filters,
|
||||
filters: getResourceView(RESOURCE_NAMES.GROUP)?.filters,
|
||||
columns: GroupColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
const groups = useGroup()
|
||||
const { getGroups } = useGroupApi()
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } =
|
||||
useFetch(getGroups)
|
||||
const { INIT, PENDING } = STATUS
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [filterPool])
|
||||
|
||||
if (groups?.length === 0 && [INIT, PENDING].includes(status)) {
|
||||
return <SkeletonTable />
|
||||
}
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={groups}
|
||||
isLoading={loading || reloading}
|
||||
data={data}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={GroupRow}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
GroupsTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
GroupsTable.displayName = 'GroupsTable'
|
||||
|
||||
export default GroupsTable
|
||||
|
@ -13,62 +13,60 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useHost, useHostApi } from 'client/features/One'
|
||||
import { useGetHostsQuery } from 'client/features/OneApi/host'
|
||||
|
||||
import {
|
||||
SkeletonTable,
|
||||
EnhancedTable,
|
||||
EnhancedTableProps,
|
||||
} from 'client/components/Tables'
|
||||
import { createColumns } from 'client/components/Tables/Enhanced/Utils'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import HostColumns from 'client/components/Tables/Hosts/columns'
|
||||
import HostRow from 'client/components/Tables/Hosts/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'hosts'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Hosts table
|
||||
*/
|
||||
const HostsTable = (props) => {
|
||||
const { view, getResourceView, filterPool } = useAuth()
|
||||
const {
|
||||
rootProps = {},
|
||||
searchProps = {},
|
||||
useQuery = useGetHostsQuery,
|
||||
...rest
|
||||
} = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { data = [], isFetching, refetch } = useQuery()
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView('HOST')?.filters,
|
||||
filters: getResourceView(RESOURCE_NAMES.HOST)?.filters,
|
||||
columns: HostColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
const hosts = useHost()
|
||||
const { getHosts } = useHostApi()
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } =
|
||||
useFetch(getHosts)
|
||||
const { INIT, PENDING } = STATUS
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [filterPool])
|
||||
|
||||
if (hosts?.length === 0 && [INIT, PENDING].includes(status)) {
|
||||
return <SkeletonTable />
|
||||
}
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={hosts}
|
||||
isLoading={loading || reloading}
|
||||
data={data}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={HostRow}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
HostsTable.propTypes = EnhancedTableProps
|
||||
HostsTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
HostsTable.displayName = 'HostsTable'
|
||||
|
||||
export default HostsTable
|
||||
|
@ -13,86 +13,22 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import hostApi from 'client/features/OneApi/host'
|
||||
import { HostCard } from 'client/components/Cards'
|
||||
|
||||
import { Server, ModernTv } from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
const Row = memo(
|
||||
({ original, ...props }) => {
|
||||
const state = hostApi.endpoints.getHosts.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((host) => +host.ID === +original.ID),
|
||||
})
|
||||
|
||||
import {
|
||||
StatusCircle,
|
||||
LinearProgressWithLabel,
|
||||
StatusChip,
|
||||
} from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
|
||||
import * as HostModel from 'client/models/Host'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const Row = ({ original, value, ...props }) => {
|
||||
const classes = rowStyles()
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
IM_MAD,
|
||||
VM_MAD,
|
||||
RUNNING_VMS,
|
||||
TOTAL_VMS,
|
||||
CLUSTER,
|
||||
TEMPLATE,
|
||||
} = value
|
||||
|
||||
const { percentCpuUsed, percentCpuLabel, percentMemUsed, percentMemLabel } =
|
||||
HostModel.getAllocatedInfo(value)
|
||||
|
||||
const { color: stateColor, name: stateName } = HostModel.getState(original)
|
||||
|
||||
const labels = [...new Set([IM_MAD, VM_MAD])]
|
||||
|
||||
return (
|
||||
<div {...props} data-cy={`host-${ID}`}>
|
||||
<div>
|
||||
<StatusCircle color={stateColor} tooltip={stateName} />
|
||||
</div>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography noWrap component="span">
|
||||
{TEMPLATE?.NAME ?? NAME}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
{labels.map((label) => (
|
||||
<StatusChip key={label} text={label} />
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${ID}`}</span>
|
||||
<span title={`Cluster: ${CLUSTER}`}>
|
||||
<Server />
|
||||
<span>{` ${CLUSTER}`}</span>
|
||||
</span>
|
||||
<span title={`Running VMs: ${RUNNING_VMS} / ${TOTAL_VMS}`}>
|
||||
<ModernTv />
|
||||
<span>{` ${RUNNING_VMS} / ${TOTAL_VMS}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.secondary}>
|
||||
<LinearProgressWithLabel
|
||||
value={percentCpuUsed}
|
||||
label={percentCpuLabel}
|
||||
title={`${Tr(T.AllocatedCpu)}`}
|
||||
/>
|
||||
<LinearProgressWithLabel
|
||||
value={percentMemUsed}
|
||||
label={percentMemLabel}
|
||||
title={`${Tr(T.AllocatedMemory)}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <HostCard host={state ?? original} rootProps={props} />
|
||||
},
|
||||
(prev, next) => prev.className === next.className
|
||||
)
|
||||
|
||||
Row.propTypes = {
|
||||
original: PropTypes.object,
|
||||
@ -101,4 +37,6 @@ Row.propTypes = {
|
||||
handleClick: PropTypes.func,
|
||||
}
|
||||
|
||||
Row.displayName = 'HostRow'
|
||||
|
||||
export default Row
|
||||
|
@ -1,108 +0,0 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { LinearProgress } from '@mui/material'
|
||||
|
||||
import Tabs from 'client/components/Tabs'
|
||||
import { StatusBadge } from 'client/components/Status'
|
||||
|
||||
import { useFetch, useSocket } from 'client/hooks'
|
||||
import { useImageApi } from 'client/features/One'
|
||||
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import * as ImageModel from 'client/models/Image'
|
||||
import * as Helper from 'client/models/Helper'
|
||||
|
||||
const ImageDetail = ({ id }) => {
|
||||
const { getImage } = useImageApi()
|
||||
|
||||
const { getHooksSocket } = useSocket()
|
||||
const socket = getHooksSocket({ resource: 'image', id })
|
||||
|
||||
const { data, fetchRequest, loading, error } = useFetch(getImage, socket)
|
||||
const isLoading = (!data && !error) || loading
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest(id)
|
||||
}, [id])
|
||||
|
||||
if (isLoading) {
|
||||
return <LinearProgress color="secondary" style={{ width: '100%' }} />
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div>{error}</div>
|
||||
}
|
||||
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
UNAME,
|
||||
GNAME,
|
||||
REGTIME,
|
||||
SIZE,
|
||||
PERSISTENT,
|
||||
LOCK,
|
||||
DATASTORE,
|
||||
VMS,
|
||||
RUNNING_VMS,
|
||||
} = data
|
||||
|
||||
const { name: stateName, color: stateColor } = ImageModel.getState(data)
|
||||
const type = ImageModel.getType(data)
|
||||
const size = +SIZE ? prettyBytes(+SIZE, 'MB') : '-'
|
||||
|
||||
const usedByVms = [VMS?.ID ?? []].flat().length || 0
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
name: 'info',
|
||||
renderContent: (
|
||||
<div>
|
||||
<div>
|
||||
<StatusBadge
|
||||
title={stateName}
|
||||
stateColor={stateColor}
|
||||
customTransform="translate(150%, 50%)"
|
||||
/>
|
||||
<span style={{ marginLeft: 20 }}>{`#${ID} - ${NAME}`}</span>
|
||||
</div>
|
||||
<div>
|
||||
<p>Owner: {UNAME}</p>
|
||||
<p>Group: {GNAME}</p>
|
||||
<p>Datastore: {DATASTORE}</p>
|
||||
<p>Persistent: {type}</p>
|
||||
<p>Size: {size}</p>
|
||||
<p>Register time: {Helper.timeToString(REGTIME)}</p>
|
||||
<p>Locked: {Helper.levelLockToString(LOCK?.LOCKED)}</p>
|
||||
<p>Persistent: {Helper.booleanToString(PERSISTENT)}</p>
|
||||
<p>Running VMS: {` ${RUNNING_VMS} / ${usedByVms}`}</p>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return <Tabs tabs={tabs} />
|
||||
}
|
||||
|
||||
ImageDetail.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export default ImageDetail
|
@ -13,53 +13,55 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useImage, useImageApi } from 'client/features/One'
|
||||
import { useGetImagesQuery } from 'client/features/OneApi/image'
|
||||
|
||||
import {
|
||||
SkeletonTable,
|
||||
EnhancedTable,
|
||||
EnhancedTableProps,
|
||||
} from 'client/components/Tables'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import ImageColumns from 'client/components/Tables/Images/columns'
|
||||
import ImageRow from 'client/components/Tables/Images/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'images'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Images table
|
||||
*/
|
||||
const ImagesTable = (props) => {
|
||||
const columns = useMemo(() => ImageColumns, [])
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const images = useImage()
|
||||
const { getImages } = useImageApi()
|
||||
const { filterPool } = useAuth()
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { data = [], isFetching, refetch } = useGetImagesQuery()
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } =
|
||||
useFetch(getImages)
|
||||
const { INIT, PENDING } = STATUS
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [filterPool])
|
||||
|
||||
if (images?.length === 0 && [INIT, PENDING].includes(status)) {
|
||||
return <SkeletonTable />
|
||||
}
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView(RESOURCE_NAMES.IMAGE)?.filters,
|
||||
columns: ImageColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={images}
|
||||
isLoading={loading || reloading}
|
||||
data={data}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={ImageRow}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
ImagesTable.propTypes = EnhancedTableProps
|
||||
ImagesTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
ImagesTable.displayName = 'ImagesTable'
|
||||
|
||||
export default ImagesTable
|
||||
|
@ -16,18 +16,17 @@
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { RefreshDouble, AddSquare, CloudDownload } from 'iconoir-react'
|
||||
import { AddSquare, CloudDownload } from 'iconoir-react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { useMarketplaceAppApi } from 'client/features/One'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { useExportAppMutation } from 'client/features/OneApi/marketplaceApp'
|
||||
|
||||
import { ExportForm } from 'client/components/Forms/MarketplaceApp'
|
||||
|
||||
import { createActions } from 'client/components/Tables/Enhanced/Utils'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { T, MARKETPLACE_APP_ACTIONS } from 'client/constants'
|
||||
import { T, RESOURCE_NAMES, MARKETPLACE_APP_ACTIONS } from 'client/constants'
|
||||
|
||||
const MessageToConfirmAction = (rows) => {
|
||||
const names = rows?.map?.(({ original }) => original?.NAME)
|
||||
@ -51,21 +50,13 @@ const Actions = () => {
|
||||
const history = useHistory()
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
const { getMarketplaceApps, exportApp } = useMarketplaceAppApi()
|
||||
const [exportApp] = useExportAppMutation()
|
||||
|
||||
const marketplaceAppActions = useMemo(
|
||||
() =>
|
||||
createActions({
|
||||
filters: getResourceView('MARKETPLACE-APP')?.actions,
|
||||
filters: getResourceView(RESOURCE_NAMES.APP)?.actions,
|
||||
actions: [
|
||||
{
|
||||
accessor: MARKETPLACE_APP_ACTIONS.REFRESH,
|
||||
tooltip: T.Refresh,
|
||||
icon: RefreshDouble,
|
||||
action: async () => {
|
||||
await getMarketplaceApps()
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: MARKETPLACE_APP_ACTIONS.CREATE_DIALOG,
|
||||
tooltip: T.CreateMarketApp,
|
||||
@ -88,9 +79,9 @@ const Actions = () => {
|
||||
return ExportForm(app, app)
|
||||
},
|
||||
onSubmit: async (formData, rows) => {
|
||||
const appId = rows?.[0]?.original?.ID
|
||||
const response = await exportApp(appId, formData)
|
||||
enqueueSuccess(response)
|
||||
const id = rows?.[0]?.original?.ID
|
||||
const res = await exportApp({ id, ...formData }).unwrap()
|
||||
enqueueSuccess(res)
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -13,55 +13,55 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useMarketplaceApp, useMarketplaceAppApi } from 'client/features/One'
|
||||
import { useGetMarketplaceAppsQuery } from 'client/features/OneApi/marketplaceApp'
|
||||
|
||||
import { SkeletonTable, EnhancedTable } from 'client/components/Tables'
|
||||
import { createColumns } from 'client/components/Tables/Enhanced/Utils'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import MarketplaceAppColumns from 'client/components/Tables/MarketplaceApps/columns'
|
||||
import MarketplaceAppRow from 'client/components/Tables/MarketplaceApps/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'apps'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Marketplace Apps table
|
||||
*/
|
||||
const MarketplaceAppsTable = (props) => {
|
||||
const { view, getResourceView, filterPool } = useAuth()
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { data = [], isFetching, refetch } = useGetMarketplaceAppsQuery()
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView('MARKETPLACE-APP')?.filters,
|
||||
filters: getResourceView(RESOURCE_NAMES.APP)?.filters,
|
||||
columns: MarketplaceAppColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
const marketplaceApps = useMarketplaceApp()
|
||||
const { getMarketplaceApps } = useMarketplaceAppApi()
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } =
|
||||
useFetch(getMarketplaceApps)
|
||||
const { INIT, PENDING } = STATUS
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [filterPool])
|
||||
|
||||
if (marketplaceApps?.length === 0 && [INIT, PENDING].includes(status)) {
|
||||
return <SkeletonTable />
|
||||
}
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={marketplaceApps}
|
||||
isLoading={loading || reloading}
|
||||
data={data}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={MarketplaceAppRow}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
MarketplaceAppsTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
MarketplaceAppsTable.displayName = 'MarketplaceAppsTable'
|
||||
|
||||
export default MarketplaceAppsTable
|
||||
|
@ -13,58 +13,52 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useMarketplace, useMarketplaceApi } from 'client/features/One'
|
||||
import { useGetMarketplacesQuery } from 'client/features/OneApi/marketplace'
|
||||
|
||||
import { SkeletonTable, EnhancedTable } from 'client/components/Tables'
|
||||
import { createColumns } from 'client/components/Tables/Enhanced/Utils'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import MarketplaceColumns from 'client/components/Tables/Marketplaces/columns'
|
||||
import MarketplaceRow from 'client/components/Tables/Marketplaces/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'marketplaces'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {function():Array} [props.filter] - Function to filter the data
|
||||
* @returns {ReactElement} Marketplaces table
|
||||
*/
|
||||
const MarketplacesTable = ({ filter, ...props }) => {
|
||||
const { view, getResourceView, filterPool } = useAuth()
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { data = [], isFetching, refetch } = useGetMarketplacesQuery()
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView('MARKETPLACE')?.filters,
|
||||
filters: getResourceView(RESOURCE_NAMES.MARKETPLACE)?.filters,
|
||||
columns: MarketplaceColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
const marketplaces = useMarketplace()
|
||||
const { getMarketplaces } = useMarketplaceApi()
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } =
|
||||
useFetch(getMarketplaces)
|
||||
const { INIT, PENDING } = STATUS
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [filterPool])
|
||||
|
||||
if (marketplaces?.length === 0 && [INIT, PENDING].includes(status)) {
|
||||
return <SkeletonTable />
|
||||
}
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={
|
||||
typeof filter === 'function'
|
||||
? marketplaces?.filter(filter)
|
||||
: marketplaces
|
||||
}
|
||||
isLoading={loading || reloading}
|
||||
data={typeof filter === 'function' ? data?.filter(filter) : data}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={MarketplaceRow}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -73,7 +67,6 @@ MarketplacesTable.propTypes = {
|
||||
filter: PropTypes.func,
|
||||
...EnhancedTable.propTypes,
|
||||
}
|
||||
|
||||
MarketplacesTable.displayName = 'MarketplacesTable'
|
||||
|
||||
export default MarketplacesTable
|
||||
|
@ -39,7 +39,7 @@ const SkeletonTable = memo(() => {
|
||||
const rowClasses = rowStyles()
|
||||
|
||||
const SkeletonRow = () => (
|
||||
<Card style={{ padding: '1em' }}>
|
||||
<Card sx={{ p: '1em' }}>
|
||||
<div className={rowClasses.main}>
|
||||
<div className={rowClasses.title}>
|
||||
<Skeleton width={'40%'} height={30} />
|
||||
@ -67,18 +67,10 @@ const SkeletonTable = memo(() => {
|
||||
</div>
|
||||
<div className={classes.table}>
|
||||
{isMobile ? (
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
height={40}
|
||||
style={{ marginBottom: '1em' }}
|
||||
/>
|
||||
<Skeleton variant="rectangular" height={40} sx={{ mb: '1em' }} />
|
||||
) : (
|
||||
<Card variant="outlined" style={{ padding: '1em' }}>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
height={40}
|
||||
style={{ marginBottom: '1em' }}
|
||||
/>
|
||||
<Card variant="outlined" sx={{ p: '1em' }}>
|
||||
<Skeleton variant="rectangular" height={40} sx={{ mb: '1em' }} />
|
||||
<div>
|
||||
<SkeletonCategory />
|
||||
<SkeletonCategory numberOfItems={3} />
|
||||
|
@ -13,55 +13,55 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useUser, useUserApi } from 'client/features/One'
|
||||
import { useGetUsersQuery } from 'client/features/OneApi/user'
|
||||
|
||||
import { SkeletonTable, EnhancedTable } from 'client/components/Tables'
|
||||
import { createColumns } from 'client/components/Tables/Enhanced/Utils'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import UserColumns from 'client/components/Tables/Users/columns'
|
||||
import UserRow from 'client/components/Tables/Users/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'users'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Users table
|
||||
*/
|
||||
const UsersTable = (props) => {
|
||||
const { view, getResourceView, filterPool } = useAuth()
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { data = [], isFetching, refetch } = useGetUsersQuery()
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView('USER')?.filters,
|
||||
filters: getResourceView(RESOURCE_NAMES.USER)?.filters,
|
||||
columns: UserColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
const users = useUser()
|
||||
const { getUsers } = useUserApi()
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } =
|
||||
useFetch(getUsers)
|
||||
const { INIT, PENDING } = STATUS
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [filterPool])
|
||||
|
||||
if (users?.length === 0 && [INIT, PENDING].includes(status)) {
|
||||
return <SkeletonTable />
|
||||
}
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={users}
|
||||
isLoading={loading || reloading}
|
||||
data={data}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={UserRow}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
UsersTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
UsersTable.displayName = 'UsersTable'
|
||||
|
||||
export default UsersTable
|
||||
|
@ -13,46 +13,56 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useEffect } from 'react'
|
||||
|
||||
import { useFetch } from 'client/hooks'
|
||||
import {
|
||||
useVNetworkTemplate,
|
||||
useVNetworkTemplateApi,
|
||||
} from 'client/features/One'
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
|
||||
import { SkeletonTable, EnhancedTable } from 'client/components/Tables'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useGetVNTemplatesQuery } from 'client/features/OneApi/networkTemplate'
|
||||
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import VNetworkTemplateColumns from 'client/components/Tables/VNetworkTemplates/columns'
|
||||
import VNetworkTemplateRow from 'client/components/Tables/VNetworkTemplates/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const VNetworkTemplatesTable = () => {
|
||||
const columns = useMemo(() => VNetworkTemplateColumns, [])
|
||||
const DEFAULT_DATA_CY = 'vnet-templates'
|
||||
|
||||
const vNetworkTemplates = useVNetworkTemplate()
|
||||
const { getVNetworkTemplates } = useVNetworkTemplateApi()
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Virtual Network Templates table
|
||||
*/
|
||||
const VNetworkTemplatesTable = (props) => {
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } =
|
||||
useFetch(getVNetworkTemplates)
|
||||
const { INIT, PENDING } = STATUS
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { data = [], isFetching, refetch } = useGetVNTemplatesQuery()
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [])
|
||||
|
||||
if (vNetworkTemplates?.length === 0 && [INIT, PENDING].includes(status)) {
|
||||
return <SkeletonTable />
|
||||
}
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView(RESOURCE_NAMES.VN_TEMPLATE)?.filters,
|
||||
columns: VNetworkTemplateColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={vNetworkTemplates}
|
||||
isLoading={loading || reloading}
|
||||
data={data}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={VNetworkTemplateRow}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
VNetworkTemplatesTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
VNetworkTemplatesTable.displayName = 'VNetworkTemplatesTable'
|
||||
|
||||
export default VNetworkTemplatesTable
|
||||
|
@ -13,51 +13,60 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useVNetwork, useVNetworkApi } from 'client/features/One'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useGetVNetworksQuery } from 'client/features/OneApi/network'
|
||||
|
||||
import {
|
||||
SkeletonTable,
|
||||
EnhancedTable,
|
||||
EnhancedTableProps,
|
||||
} from 'client/components/Tables'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import VNetworkColumns from 'client/components/Tables/VNetworks/columns'
|
||||
import VNetworkRow from 'client/components/Tables/VNetworks/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'vnets'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Virtual networks table
|
||||
*/
|
||||
const VNetworksTable = (props) => {
|
||||
const columns = useMemo(() => VNetworkColumns, [])
|
||||
const {
|
||||
rootProps = {},
|
||||
searchProps = {},
|
||||
useQuery = useGetVNetworksQuery,
|
||||
...rest
|
||||
} = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const vNetworks = useVNetwork()
|
||||
const { getVNetworks } = useVNetworkApi()
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { data = [], isFetching, refetch } = useQuery()
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } =
|
||||
useFetch(getVNetworks)
|
||||
const { INIT, PENDING } = STATUS
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [])
|
||||
|
||||
if (vNetworks?.length === 0 && [INIT, PENDING].includes(status)) {
|
||||
return <SkeletonTable />
|
||||
}
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView(RESOURCE_NAMES.VNET)?.filters,
|
||||
columns: VNetworkColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={vNetworks}
|
||||
isLoading={loading || reloading}
|
||||
data={data}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={VNetworkRow}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
VNetworksTable.propTypes = EnhancedTableProps
|
||||
VNetworksTable.displayName = 'VNetworksTable'
|
||||
VNetworksTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
VNetworksTable.displayName = 'VirtualNetworksTable'
|
||||
|
||||
export default VNetworksTable
|
||||
|
@ -13,73 +13,22 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import api from 'client/features/OneApi/network'
|
||||
import { NetworkCard } from 'client/components/Cards'
|
||||
|
||||
import { User, Group, Lock, Server, Cloud } from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
const Row = memo(
|
||||
({ original, ...props }) => {
|
||||
const state = api.endpoints.getVNetworks.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((network) => +network.ID === +original.ID),
|
||||
})
|
||||
|
||||
import { LinearProgressWithLabel } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
|
||||
import * as VirtualNetworkModel from 'client/models/VirtualNetwork'
|
||||
|
||||
const Row = ({ original, value, ...props }) => {
|
||||
const classes = rowStyles()
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
UNAME,
|
||||
GNAME,
|
||||
LOCK,
|
||||
CLUSTERS,
|
||||
USED_LEASES,
|
||||
TOTAL_LEASES,
|
||||
PROVISION_ID,
|
||||
} = value
|
||||
|
||||
const { percentOfUsed, percentLabel } =
|
||||
VirtualNetworkModel.getLeasesInfo(original)
|
||||
|
||||
return (
|
||||
<div {...props} data-cy={`network-${ID}`}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span">{NAME}</Typography>
|
||||
<span className={classes.labels}>{LOCK && <Lock />}</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${ID}`}</span>
|
||||
<span title={`Owner: ${UNAME}`}>
|
||||
<User />
|
||||
<span>{` ${UNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Group: ${GNAME}`}>
|
||||
<Group />
|
||||
<span>{` ${GNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Total Clusters: ${CLUSTERS}`}>
|
||||
<Server />
|
||||
<span>{` ${CLUSTERS}`}</span>
|
||||
</span>
|
||||
{PROVISION_ID && (
|
||||
<span title={`Provision ID: #${PROVISION_ID}`}>
|
||||
<Cloud />
|
||||
<span>{` ${PROVISION_ID}`}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.secondary}>
|
||||
<LinearProgressWithLabel
|
||||
title={`Used / Total Leases: ${USED_LEASES} / ${TOTAL_LEASES}`}
|
||||
value={percentOfUsed}
|
||||
label={percentLabel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <NetworkCard network={state ?? original} rootProps={props} />
|
||||
},
|
||||
(prev, next) => prev.className === next.className
|
||||
)
|
||||
|
||||
Row.propTypes = {
|
||||
original: PropTypes.object,
|
||||
@ -88,4 +37,6 @@ Row.propTypes = {
|
||||
handleClick: PropTypes.func,
|
||||
}
|
||||
|
||||
Row.displayName = 'VirtualNetworkRow'
|
||||
|
||||
export default Row
|
||||
|
@ -13,43 +13,55 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useVRouter, useVRouterApi } from 'client/features/One'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useGetVRoutersQuery } from 'client/features/OneApi/vrouter'
|
||||
|
||||
import { SkeletonTable, EnhancedTable } from 'client/components/Tables'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import VRouterColumns from 'client/components/Tables/VRouters/columns'
|
||||
import VRouterRow from 'client/components/Tables/VRouters/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const VRoutersTable = () => {
|
||||
const columns = useMemo(() => VRouterColumns, [])
|
||||
const DEFAULT_DATA_CY = 'vrouters'
|
||||
|
||||
const vRouters = useVRouter()
|
||||
const { getVRouters } = useVRouterApi()
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Virtual Routers table
|
||||
*/
|
||||
const VRoutersTable = (props) => {
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } =
|
||||
useFetch(getVRouters)
|
||||
const { INIT, PENDING } = STATUS
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { data = [], isFetching, refetch } = useGetVRoutersQuery()
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [])
|
||||
|
||||
if (vRouters?.length === 0 && [INIT, PENDING].includes(status)) {
|
||||
return <SkeletonTable />
|
||||
}
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView(RESOURCE_NAMES.V_ROUTER)?.filters,
|
||||
columns: VRouterColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={vRouters}
|
||||
isLoading={loading || reloading}
|
||||
data={data}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={VRouterRow}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
VRoutersTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
VRoutersTable.displayName = 'VRoutersTable'
|
||||
|
||||
export default VRoutersTable
|
||||
|
@ -17,7 +17,6 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import {
|
||||
RefreshDouble,
|
||||
AddSquare,
|
||||
Import,
|
||||
Trash,
|
||||
@ -28,7 +27,12 @@ import {
|
||||
} from 'iconoir-react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useVmTemplateApi } from 'client/features/One'
|
||||
import {
|
||||
useLockTemplateMutation,
|
||||
useUnlockTemplateMutation,
|
||||
useCloneTemplateMutation,
|
||||
useRemoveTemplateMutation,
|
||||
} from 'client/features/OneApi/vmTemplate'
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
|
||||
import { CloneForm } from 'client/components/Forms/VmTemplate'
|
||||
@ -63,22 +67,16 @@ MessageToConfirmAction.displayName = 'MessageToConfirmAction'
|
||||
const Actions = () => {
|
||||
const history = useHistory()
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { getVmTemplate, getVmTemplates, lock, unlock, clone, remove } =
|
||||
useVmTemplateApi()
|
||||
const [lock] = useLockTemplateMutation()
|
||||
const [unlock] = useUnlockTemplateMutation()
|
||||
const [clone] = useCloneTemplateMutation()
|
||||
const [remove] = useRemoveTemplateMutation()
|
||||
|
||||
const vmTemplateActions = useMemo(
|
||||
() =>
|
||||
createActions({
|
||||
filters: getResourceView('VM-TEMPLATE')?.actions,
|
||||
filters: getResourceView(RESOURCE_NAMES.VM_TEMPLATE)?.actions,
|
||||
actions: [
|
||||
{
|
||||
accessor: VM_TEMPLATE_ACTIONS.REFRESH,
|
||||
tooltip: T.Refresh,
|
||||
icon: RefreshDouble,
|
||||
action: async () => {
|
||||
await getVmTemplates()
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: VM_TEMPLATE_ACTIONS.CREATE_DIALOG,
|
||||
tooltip: T.Create,
|
||||
@ -169,26 +167,18 @@ const Actions = () => {
|
||||
return CloneForm(stepProps, initialValues)
|
||||
},
|
||||
onSubmit: async (formData, rows) => {
|
||||
try {
|
||||
const { prefix } = formData
|
||||
const { prefix, ...restOfData } = formData
|
||||
|
||||
const vmTemplates = rows?.map?.(
|
||||
({ original: { ID, NAME } = {} }) => {
|
||||
// overwrite all names with prefix+NAME
|
||||
const formatData = prefix
|
||||
? { name: `${prefix} ${NAME}` }
|
||||
: {}
|
||||
const vmTemplates = rows?.map?.(
|
||||
({ original: { ID, NAME } = {} }) => {
|
||||
// overwrite all names with prefix+NAME
|
||||
const name = prefix ? `${prefix} ${NAME}` : NAME
|
||||
|
||||
return { id: ID, data: { ...formData, ...formatData } }
|
||||
}
|
||||
)
|
||||
return { id: ID, ...restOfData, name }
|
||||
}
|
||||
)
|
||||
|
||||
await Promise.all(
|
||||
vmTemplates.map(({ id, data }) => clone(id, data))
|
||||
)
|
||||
} finally {
|
||||
await getVmTemplates()
|
||||
}
|
||||
await Promise.all(vmTemplates.map(clone))
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -245,8 +235,7 @@ const Actions = () => {
|
||||
},
|
||||
onSubmit: async (_, rows) => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => lock(id)))
|
||||
await Promise.all(ids.map((id) => getVmTemplate(id)))
|
||||
await Promise.all(ids.map((id) => lock({ id })))
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -260,7 +249,6 @@ const Actions = () => {
|
||||
onSubmit: async (_, rows) => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => unlock(id)))
|
||||
await Promise.all(ids.map((id) => getVmTemplate(id)))
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -280,8 +268,7 @@ const Actions = () => {
|
||||
},
|
||||
onSubmit: async (_, rows) => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => remove(id)))
|
||||
await getVmTemplates()
|
||||
await Promise.all(ids.map((id) => remove({ id })))
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -13,62 +13,55 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useVmTemplate, useVmTemplateApi } from 'client/features/One'
|
||||
import { useGetTemplatesQuery } from 'client/features/OneApi/vmTemplate'
|
||||
|
||||
import {
|
||||
SkeletonTable,
|
||||
EnhancedTable,
|
||||
EnhancedTableProps,
|
||||
} from 'client/components/Tables'
|
||||
import { createColumns } from 'client/components/Tables/Enhanced/Utils'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import VmTemplateColumns from 'client/components/Tables/VmTemplates/columns'
|
||||
import VmTemplateRow from 'client/components/Tables/VmTemplates/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'vm-templates'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} VM Templates table
|
||||
*/
|
||||
const VmTemplatesTable = (props) => {
|
||||
const { view, getResourceView, filterPool } = useAuth()
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { data = [], isFetching, refetch } = useGetTemplatesQuery()
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView('VM-TEMPLATE')?.filters,
|
||||
filters: getResourceView(RESOURCE_NAMES.VM_TEMPLATE)?.filters,
|
||||
columns: VmTemplateColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
const vmTemplates = useVmTemplate()
|
||||
const { getVmTemplates } = useVmTemplateApi()
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } =
|
||||
useFetch(getVmTemplates)
|
||||
const { INIT, PENDING } = STATUS
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequest()
|
||||
}, [filterPool])
|
||||
|
||||
if (vmTemplates?.length === 0 && [INIT, PENDING].includes(status)) {
|
||||
return <SkeletonTable />
|
||||
}
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={vmTemplates}
|
||||
isLoading={loading || reloading}
|
||||
data={data}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={VmTemplateRow}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
VmTemplatesTable.propTypes = EnhancedTableProps
|
||||
VmTemplatesTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
VmTemplatesTable.displayName = 'VmTemplatesTable'
|
||||
|
||||
export default VmTemplatesTable
|
||||
|
@ -13,76 +13,34 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo } from 'react'
|
||||
import { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import vmTemplateApi from 'client/features/OneApi/vmTemplate'
|
||||
import { VmTemplateCard } from 'client/components/Cards'
|
||||
|
||||
import { User, Group, Lock } from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
const Row = memo(
|
||||
({ original, ...props }) => {
|
||||
const state = vmTemplateApi.endpoints.getTemplates.useQueryState(
|
||||
undefined,
|
||||
{
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((template) => +template.ID === +original.ID),
|
||||
}
|
||||
)
|
||||
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import Image from 'client/components/Image'
|
||||
|
||||
import * as Helper from 'client/models/Helper'
|
||||
import { isExternalURL } from 'client/utils'
|
||||
import { LOGO_IMAGES_URL } from 'client/constants'
|
||||
|
||||
const Row = ({ original, value, ...props }) => {
|
||||
const classes = rowStyles()
|
||||
const { ID, NAME, UNAME, GNAME, REGTIME, LOCK, VROUTER, LOGO = '' } = value
|
||||
|
||||
const [logoSource] = useMemo(() => {
|
||||
const external = isExternalURL(LOGO)
|
||||
const cleanLogoAttribute = String(LOGO).split('/').at(-1)
|
||||
const src = external ? LOGO : `${LOGO_IMAGES_URL}/${cleanLogoAttribute}`
|
||||
|
||||
return [src, external]
|
||||
}, [LOGO])
|
||||
|
||||
const time = Helper.timeFromMilliseconds(+REGTIME)
|
||||
const timeAgo = `registered ${time.toRelative()}`
|
||||
|
||||
return (
|
||||
<div {...props} data-cy={`template-${ID}`}>
|
||||
<div className={classes.figure}>
|
||||
<Image
|
||||
alt="logo"
|
||||
src={logoSource}
|
||||
imgProps={{ className: classes.image }}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography component="span">{NAME}</Typography>
|
||||
<span className={classes.labels}>
|
||||
{LOCK && <Lock />}
|
||||
{VROUTER && <StatusChip text={VROUTER} />}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span title={time.toFormat('ff')} className="full-width">
|
||||
{`#${ID} ${timeAgo}`}
|
||||
</span>
|
||||
<span title={`Owner: ${UNAME}`}>
|
||||
<User />
|
||||
<span>{` ${UNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Group: ${GNAME}`}>
|
||||
<Group />
|
||||
<span>{` ${GNAME}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <VmTemplateCard template={state ?? original} rootProps={props} />
|
||||
},
|
||||
(prev, next) => prev.className === next.className
|
||||
)
|
||||
|
||||
Row.propTypes = {
|
||||
original: PropTypes.object,
|
||||
value: PropTypes.object,
|
||||
isSelected: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
handleClick: PropTypes.func,
|
||||
}
|
||||
|
||||
Row.displayName = 'VmTemplateRow'
|
||||
|
||||
export default Row
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user