1
0
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:
vichansson 2024-07-22 14:21:48 +03:00 committed by GitHub
parent 360605628c
commit 253f140bc1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 54 additions and 76 deletions

View File

@ -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 },
}

View File

@ -182,7 +182,7 @@ RoleSummary.propTypes = {
PropTypes.object,
]),
selectedRoleIndex: PropTypes.number,
onRemoveAffinity: PropTypes.func.isRequired,
onRemoveAffinity: PropTypes.func,
}
export default RoleSummary

View File

@ -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',

View File

@ -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}

View File

@ -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'

View File

@ -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)

View File

@ -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}

View File

@ -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)}

View File

@ -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',