mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-12 08:58:17 +03:00
B OpenNebula/one#6660: Fix services/templates (#3159)
* Fixes user inputs validation to allow '_' in names now * Fetches VM templates in role config properly * Color scheme for Roles tab action buttons updated * Confirmation dialogs updated to reflect targeted resources Signed-off-by: Victor Hansson <vhansson@opennebula.io>
This commit is contained in:
parent
360605628c
commit
253f140bc1
@ -90,13 +90,13 @@ const NAME = {
|
||||
validation: string()
|
||||
.trim()
|
||||
.lowercase()
|
||||
.matches(/^[a-z0-9]*$/, {
|
||||
.matches(/^[a-z0-9_]*$/, {
|
||||
message:
|
||||
'Name must only contain lowercase alphanumeric characters and no spaces',
|
||||
excludeEmptyString: true,
|
||||
})
|
||||
.required()
|
||||
.default(() => undefined),
|
||||
.default(() => ''),
|
||||
grid: { sm: 2.5, md: 2.5 },
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ RoleSummary.propTypes = {
|
||||
PropTypes.object,
|
||||
]),
|
||||
selectedRoleIndex: PropTypes.number,
|
||||
onRemoveAffinity: PropTypes.func.isRequired,
|
||||
onRemoveAffinity: PropTypes.func,
|
||||
}
|
||||
|
||||
export default RoleSummary
|
||||
|
@ -34,7 +34,11 @@ const CARDINALITY_FIELD = {
|
||||
label: T.NumberOfVms,
|
||||
|
||||
validation: number()
|
||||
.positive('Number of VMs must be positive')
|
||||
.test(
|
||||
'Is positive?',
|
||||
'Number of VMs cannot be negative!',
|
||||
(value) => value >= 0
|
||||
)
|
||||
.default(() => 0),
|
||||
}
|
||||
|
||||
@ -81,8 +85,8 @@ export const SCHEMA = array()
|
||||
role.NAME.trim().length <= 128
|
||||
)
|
||||
)
|
||||
.test('non-negative', 'Number of VMs must be a positive number', (roles) =>
|
||||
roles.every((role) => role?.CARDINALITY >= 1)
|
||||
.test('non-negative', 'Number of VMs must be non-negative', (roles) =>
|
||||
roles.every((role) => role?.CARDINALITY >= 0)
|
||||
)
|
||||
.test(
|
||||
'valid-characters',
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
Paper,
|
||||
useTheme,
|
||||
} from '@mui/material'
|
||||
import { useGetTemplatesQuery } from 'client/features/OneApi/vmTemplate'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { DateTime } from 'luxon'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
@ -57,6 +58,7 @@ const VmTemplatesPanel = ({
|
||||
const theme = useTheme()
|
||||
const { enqueueError } = useGeneralApi()
|
||||
const templateID = roles?.[selectedRoleIndex]?.SELECTED_VM_TEMPLATE_ID ?? []
|
||||
const templates = vmTemplates || (useGetTemplatesQuery()?.data ?? [])
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
@ -105,7 +107,7 @@ const VmTemplatesPanel = ({
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{vmTemplates
|
||||
{templates
|
||||
?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
|
||||
?.map((vmTemplate) => (
|
||||
<TableRow
|
||||
@ -152,7 +154,7 @@ const VmTemplatesPanel = ({
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[10, 25, 50]}
|
||||
component="div"
|
||||
count={vmTemplates?.length ?? 0}
|
||||
count={templates?.length ?? 0}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
onPageChange={handleChangePage}
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useMemo } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { Typography } from '@mui/material'
|
||||
import { Box, Typography } from '@mui/material'
|
||||
import { AddCircledOutline, Trash, PlayOutline, Group } from 'iconoir-react'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
@ -55,11 +55,11 @@ const ListServiceTemplateNames = ({ rows = [] }) =>
|
||||
const SubHeader = (rows) => <ListServiceTemplateNames rows={rows} />
|
||||
|
||||
const MessageToConfirmAction = (rows, description) => (
|
||||
<>
|
||||
<Box sx={{ minWidth: '25vw' }}>
|
||||
<ListServiceTemplateNames rows={rows} />
|
||||
{description && <Translate word={description} />}
|
||||
<Translate word={T.DoYouWantProceed} />
|
||||
</>
|
||||
</Box>
|
||||
)
|
||||
|
||||
MessageToConfirmAction.displayName = 'MessageToConfirmAction'
|
||||
|
@ -15,8 +15,8 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useMemo } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { Typography } from '@mui/material'
|
||||
import { AddCircledOutline, Trash, Group, RefreshCircular } from 'iconoir-react'
|
||||
import { Box, Typography } from '@mui/material'
|
||||
import { PlayOutline, Trash, Group, RefreshCircular } from 'iconoir-react'
|
||||
import { makeStyles } from '@mui/styles'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
@ -33,7 +33,7 @@ import {
|
||||
} from 'client/components/Tables/Enhanced/Utils'
|
||||
|
||||
import ServiceTemplatesTable from 'client/components/Tables/ServiceTemplates'
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { T, SERVICE_TEMPLATE_ACTIONS, RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
@ -60,11 +60,11 @@ const ListVmTemplateNames = ({ rows = [] }) =>
|
||||
const SubHeader = (rows) => <ListVmTemplateNames rows={rows} />
|
||||
|
||||
const MessageToConfirmAction = (rows, description) => (
|
||||
<>
|
||||
<Box sx={{ minWidth: '25vw' }}>
|
||||
<ListVmTemplateNames rows={rows} />
|
||||
{description && <Translate word={description} />}
|
||||
<Translate word={T.DoYouWantProceed} />
|
||||
</>
|
||||
</Box>
|
||||
)
|
||||
|
||||
MessageToConfirmAction.displayName = 'MessageToConfirmAction'
|
||||
@ -89,8 +89,8 @@ const Actions = () => {
|
||||
actions: [
|
||||
{
|
||||
accessor: SERVICE_TEMPLATE_ACTIONS.INSTANTIATE_DIALOG,
|
||||
tooltip: T.Create,
|
||||
icon: AddCircledOutline,
|
||||
tooltip: T.Instantiate,
|
||||
icon: PlayOutline,
|
||||
options: [
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
@ -171,19 +171,8 @@ const Actions = () => {
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
dataCy: `modal-${SERVICE_TEMPLATE_ACTIONS.RECOVER}`,
|
||||
title: (rows) => {
|
||||
const isMultiple = rows?.length > 1
|
||||
const { ID, NAME } = rows?.[0]?.original ?? {}
|
||||
|
||||
return [
|
||||
Tr(
|
||||
isMultiple ? T.RecoverSeveralServices : T.RecoverService
|
||||
),
|
||||
!isMultiple && `#${ID} ${NAME}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' - ')
|
||||
},
|
||||
title: T.RecoverService,
|
||||
children: MessageToConfirmAction,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
@ -196,19 +185,8 @@ const Actions = () => {
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
dataCy: `modal-${SERVICE_TEMPLATE_ACTIONS.RECOVER}`,
|
||||
title: (rows) => {
|
||||
const isMultiple = rows?.length > 1
|
||||
const { ID, NAME } = rows?.[0]?.original ?? {}
|
||||
|
||||
return [
|
||||
Tr(
|
||||
isMultiple ? T.RecoverSeveralServices : T.RecoverService
|
||||
),
|
||||
!isMultiple && `#${ID} ${NAME}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' - ')
|
||||
},
|
||||
title: T.RecoverDelete,
|
||||
children: MessageToConfirmAction,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
@ -230,19 +208,8 @@ const Actions = () => {
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
dataCy: `modal-${SERVICE_TEMPLATE_ACTIONS.DELETE}`,
|
||||
title: (rows) => {
|
||||
const isMultiple = rows?.length > 1
|
||||
const { ID, NAME } = rows?.[0]?.original ?? {}
|
||||
|
||||
return [
|
||||
Tr(
|
||||
isMultiple ? T.DeleteSeveralTemplates : T.DeleteTemplate
|
||||
),
|
||||
!isMultiple && `#${ID} ${NAME}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' - ')
|
||||
},
|
||||
title: T.Delete,
|
||||
children: MessageToConfirmAction,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
|
@ -50,7 +50,7 @@ export const ButtonGenerator = ({ items, options = {} }) => {
|
||||
aria-controls="customized-menu"
|
||||
aria-haspopup="true"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
color="secondary"
|
||||
onClick={handleClick}
|
||||
{...options?.button}
|
||||
sx={{
|
||||
@ -64,7 +64,7 @@ export const ButtonGenerator = ({ items, options = {} }) => {
|
||||
aria-controls="customized-menu"
|
||||
aria-haspopup="true"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
color="secondary"
|
||||
onClick={handleClick}
|
||||
endIcon={items.length > 1 ? <NavArrowDown /> : null}
|
||||
{...options?.button}
|
||||
@ -100,9 +100,7 @@ export const ButtonGenerator = ({ items, options = {} }) => {
|
||||
<IconButton
|
||||
aria-controls="customized-menu"
|
||||
aria-haspopup="true"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleClick}
|
||||
onClick={(event) => handleClick(event, items.onClick)}
|
||||
{...options?.button}
|
||||
sx={{
|
||||
...options?.singleButton?.sx,
|
||||
@ -113,7 +111,7 @@ export const ButtonGenerator = ({ items, options = {} }) => {
|
||||
) : (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
color="secondary"
|
||||
onClick={(event) => handleClick(event, items.onClick)}
|
||||
startIcon={items.icon || null}
|
||||
{...options?.singleButton}
|
||||
|
@ -247,12 +247,20 @@ const RolesTab = ({ id }) => {
|
||||
options={{
|
||||
singleButton: {
|
||||
disabled: !selectedRoles?.length > 0,
|
||||
startIcon: <PlayOutline />,
|
||||
sx: {
|
||||
fontSize: 20,
|
||||
padding: '0px 8px',
|
||||
},
|
||||
icon: <PlayOutline />,
|
||||
sx: (theme) => ({
|
||||
color: theme.palette.text.primary,
|
||||
padding: '0',
|
||||
borderRadius: '50%',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
'&.selected': {
|
||||
border: `2px solid ${theme.palette.secondary.main}`,
|
||||
},
|
||||
}),
|
||||
title: null,
|
||||
type: 'icon',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@ -261,11 +269,7 @@ const RolesTab = ({ id }) => {
|
||||
items={[
|
||||
{
|
||||
name: T.Suspend,
|
||||
onClick: () =>
|
||||
handleAddRoleAction({
|
||||
perform: 'suspend',
|
||||
role: roles?.[selectedRoles?.[0]]?.name,
|
||||
}),
|
||||
onClick: () => handleAddRoleAction('suspend'),
|
||||
},
|
||||
{
|
||||
name: T.Poweroff,
|
||||
@ -284,6 +288,7 @@ const RolesTab = ({ id }) => {
|
||||
sx: {
|
||||
fontSize: 20,
|
||||
padding: '8px 16px',
|
||||
border: '0',
|
||||
},
|
||||
title: null,
|
||||
},
|
||||
@ -361,10 +366,12 @@ const RolesTab = ({ id }) => {
|
||||
disabled: !selectedRoles?.length > 0,
|
||||
startIcon: <Trash />,
|
||||
endIcon: <NavArrowDown />,
|
||||
color: 'error',
|
||||
sx: {
|
||||
fontSize: 20,
|
||||
padding: '8px 16px',
|
||||
marginLeft: '2em',
|
||||
color: 'primary',
|
||||
},
|
||||
title: null,
|
||||
},
|
||||
@ -390,8 +397,8 @@ const RolesTab = ({ id }) => {
|
||||
bgcolor: 'background.paper',
|
||||
border: `2px solid ${
|
||||
isSelected(idx)
|
||||
? theme.palette.grey[600]
|
||||
: theme.palette.grey[400]
|
||||
? theme.palette.secondary.main
|
||||
: theme.palette.divider
|
||||
}`,
|
||||
})}
|
||||
onClick={(event) => handleRoleClick(idx, role, event)}
|
||||
|
@ -248,7 +248,7 @@ module.exports = {
|
||||
/* questions */
|
||||
Yes: 'Yes',
|
||||
No: 'No',
|
||||
DoYouWantProceed: 'Do you want proceed?',
|
||||
DoYouWantProceed: 'Do you want to proceed?',
|
||||
|
||||
/* Scheduling */
|
||||
Action: 'Action',
|
||||
|
Loading…
x
Reference in New Issue
Block a user