mirror of
https://github.com/OpenNebula/one.git
synced 2025-08-14 05:49:26 +03:00
Signed-off-by: Ángel Cívico Martos <acivico@opennnebula.io> Co-authored-by: Tino Vázquez <cvazquez@opennebula.io>
This commit is contained in:
@ -39,6 +39,8 @@ info-tabs:
|
||||
enabled: true
|
||||
actions:
|
||||
edit_admins: true
|
||||
add_users: true
|
||||
remove_users: true
|
||||
|
||||
quota:
|
||||
enabled: true
|
||||
|
@ -42,6 +42,9 @@ info-tabs:
|
||||
enabled: true
|
||||
actions:
|
||||
chgrp: true
|
||||
add_to_group: true
|
||||
remove_from_group: true
|
||||
change_primary_group: true
|
||||
|
||||
quota:
|
||||
enabled: true
|
||||
|
@ -38,6 +38,7 @@ info-tabs:
|
||||
enabled: true
|
||||
actions:
|
||||
edit_admins: true
|
||||
add_users: true
|
||||
|
||||
quota:
|
||||
enabled: true
|
||||
|
@ -15,8 +15,8 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { createForm } from '@UtilsModule'
|
||||
import {
|
||||
SCHEMA,
|
||||
FIELDS,
|
||||
SCHEMA,
|
||||
} from '@modules/components/Forms/Group/EditAdminsForm/schema'
|
||||
|
||||
const EditAdminsForm = createForm(SCHEMA, FIELDS, {
|
||||
|
@ -0,0 +1,29 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2025, 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 { createForm } from '@UtilsModule'
|
||||
import {
|
||||
FIELDS,
|
||||
SCHEMA,
|
||||
} from '@modules/components/Forms/Group/EditUsersForm/schema'
|
||||
|
||||
const EditUsersForm = createForm(SCHEMA, FIELDS, {
|
||||
transformInitialValue: (users, schema) => ({
|
||||
...schema.cast({ users }, { stripUnknown: false }),
|
||||
}),
|
||||
transformBeforeSubmit: (formData, initialValues) => {},
|
||||
})
|
||||
|
||||
export default EditUsersForm
|
@ -0,0 +1,52 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2025, 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 { array, object, string } from 'yup'
|
||||
|
||||
import { INPUT_TYPES, T } from '@ConstantsModule'
|
||||
import { UsersTable } from '@modules/components/Tables'
|
||||
import { getValidationFromFields } from '@UtilsModule'
|
||||
|
||||
const USERS = (props) => ({
|
||||
name: 'users',
|
||||
label: T['groups.actions.edit.users.form'],
|
||||
type: INPUT_TYPES.TABLE,
|
||||
Table: () => UsersTable.Table,
|
||||
fieldProps: {
|
||||
filterData: props.filterData,
|
||||
preserveState: true,
|
||||
},
|
||||
singleSelect: false,
|
||||
validation: array(string())
|
||||
.required()
|
||||
.default(() => undefined),
|
||||
grid: { md: 12 },
|
||||
})
|
||||
|
||||
/**
|
||||
* Fields of the form.
|
||||
*
|
||||
* @param {object} props - Object to get filterData function
|
||||
* @returns {object} Fields
|
||||
*/
|
||||
export const FIELDS = (props) => [USERS(props)]
|
||||
|
||||
/**
|
||||
* Schema of the form.
|
||||
*
|
||||
* @param {object} props - Object to get filterData function
|
||||
* @returns {object} Schema
|
||||
*/
|
||||
export const SCHEMA = (props) => object(getValidationFromFields(FIELDS(props)))
|
@ -38,4 +38,11 @@ const UpdateForm = (configProps) =>
|
||||
const EditAdminsForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'Group/EditAdminsForm' }, configProps)
|
||||
|
||||
export { CreateForm, UpdateForm, EditAdminsForm }
|
||||
/**
|
||||
* @param {ConfigurationProps} configProps - Configuration
|
||||
* @returns {ReactElement|CreateStepsCallback} Asynchronous loaded form
|
||||
*/
|
||||
const EditUsersForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'Group/EditUsersForm' }, configProps)
|
||||
|
||||
export { CreateForm, UpdateForm, EditAdminsForm, EditUsersForm }
|
||||
|
@ -0,0 +1,29 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2025, 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 { createForm } from '@UtilsModule'
|
||||
import {
|
||||
FIELDS,
|
||||
SCHEMA,
|
||||
} from '@modules/components/Forms/User/EditGroupForm/schema'
|
||||
|
||||
const EditGroupForm = createForm(SCHEMA, FIELDS, {
|
||||
transformInitialValue: (groups, schema) => ({
|
||||
...schema.cast({ groups }, { stripUnknown: false }),
|
||||
}),
|
||||
transformBeforeSubmit: (formData, initialValues) => {},
|
||||
})
|
||||
|
||||
export default EditGroupForm
|
@ -0,0 +1,52 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2025, 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 { array, object, string } from 'yup'
|
||||
|
||||
import { INPUT_TYPES, T } from '@ConstantsModule'
|
||||
import { GroupsTable } from '@modules/components/Tables'
|
||||
import { getValidationFromFields } from '@UtilsModule'
|
||||
|
||||
const GROUPS = (props) => ({
|
||||
name: 'groups',
|
||||
label: T['user.actions.edit.group.form'],
|
||||
type: INPUT_TYPES.TABLE,
|
||||
Table: () => GroupsTable.Table,
|
||||
fieldProps: {
|
||||
filterData: props.filterData,
|
||||
preserveState: true,
|
||||
},
|
||||
singleSelect: false,
|
||||
validation: array(string())
|
||||
.required()
|
||||
.default(() => undefined),
|
||||
grid: { md: 12 },
|
||||
})
|
||||
|
||||
/**
|
||||
* Fields of the form.
|
||||
*
|
||||
* @param {object} props - Object to get filterData function
|
||||
* @returns {object} Fields
|
||||
*/
|
||||
export const FIELDS = (props) => [GROUPS(props)]
|
||||
|
||||
/**
|
||||
* Schema of the form.
|
||||
*
|
||||
* @param {object} props - Object to get filterData function
|
||||
* @returns {object} Schema
|
||||
*/
|
||||
export const SCHEMA = (props) => object(getValidationFromFields(FIELDS(props)))
|
@ -13,9 +13,9 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import { AsyncLoadForm, ConfigurationProps } from '@modules/components/HOC'
|
||||
import { CreateStepsCallback } from '@UtilsModule'
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
/**
|
||||
* @param {ConfigurationProps} configProps - Configuration
|
||||
@ -24,4 +24,11 @@ import { CreateStepsCallback } from '@UtilsModule'
|
||||
const CreateForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'User/CreateForm' }, configProps)
|
||||
|
||||
export { CreateForm }
|
||||
/**
|
||||
* @param {ConfigurationProps} configProps - Configuration
|
||||
* @returns {ReactElement|CreateStepsCallback} Asynchronous loaded form
|
||||
*/
|
||||
const EditGroupForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'User/EditGroupForm' }, configProps)
|
||||
|
||||
export { CreateForm, EditGroupForm }
|
||||
|
@ -13,6 +13,9 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { RESOURCE_NAMES, T } from '@ConstantsModule'
|
||||
import { GroupAPI, useViews } from '@FeaturesModule'
|
||||
import { getGroupQuotaUsage } from '@ModelsModule'
|
||||
import { LinearProgressWithTooltip } from '@modules/components/Status'
|
||||
import EnhancedTable, {
|
||||
createColumns,
|
||||
@ -20,9 +23,6 @@ import EnhancedTable, {
|
||||
import WrapperRow from '@modules/components/Tables/Enhanced/WrapperRow'
|
||||
import GroupColumns from '@modules/components/Tables/Groups/columns'
|
||||
import GroupRow from '@modules/components/Tables/Groups/row'
|
||||
import { RESOURCE_NAMES, T } from '@ConstantsModule'
|
||||
import { useViews, GroupAPI } from '@FeaturesModule'
|
||||
import { getGroupQuotaUsage } from '@ModelsModule'
|
||||
import { Component, useMemo } from 'react'
|
||||
|
||||
const DEFAULT_DATA_CY = 'groups'
|
||||
@ -51,7 +51,16 @@ const GroupsTable = (props) => {
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useViews()
|
||||
const { data = [], isFetching, refetch } = GroupAPI.useGetGroupsQuery()
|
||||
const {
|
||||
data: groups = [],
|
||||
isFetching,
|
||||
refetch,
|
||||
} = GroupAPI.useGetGroupsQuery()
|
||||
|
||||
const data =
|
||||
props?.filterData && typeof props?.filterData === 'function'
|
||||
? props?.filterData(groups)
|
||||
: groups
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
@ -133,7 +142,7 @@ const GroupsTable = (props) => {
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
data={useMemo(() => data, [data])}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
|
@ -15,6 +15,9 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement, useMemo } from 'react'
|
||||
|
||||
import { RESOURCE_NAMES, T } from '@ConstantsModule'
|
||||
import { UserAPI, useViews } from '@FeaturesModule'
|
||||
import { getUserQuotaUsage } from '@ModelsModule'
|
||||
import { LinearProgressWithTooltip } from '@modules/components/Status'
|
||||
import EnhancedTable, {
|
||||
createColumns,
|
||||
@ -22,9 +25,6 @@ import EnhancedTable, {
|
||||
import WrapperRow from '@modules/components/Tables/Enhanced/WrapperRow'
|
||||
import UserColumns from '@modules/components/Tables/Users/columns'
|
||||
import UserRow from '@modules/components/Tables/Users/row'
|
||||
import { RESOURCE_NAMES, T } from '@ConstantsModule'
|
||||
import { useViews, UserAPI } from '@FeaturesModule'
|
||||
import { getUserQuotaUsage } from '@ModelsModule'
|
||||
|
||||
const DEFAULT_DATA_CY = 'users'
|
||||
|
||||
|
@ -13,12 +13,12 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { memo } from 'react'
|
||||
|
||||
import ButtonToTriggerForm from '@modules/components/Forms/ButtonToTriggerForm'
|
||||
|
||||
import { EditAdminsForm } from '@modules/components/Forms/Group'
|
||||
import { EditAdminsForm, EditUsersForm } from '@modules/components/Forms/Group'
|
||||
|
||||
import { T } from '@ConstantsModule'
|
||||
|
||||
@ -64,6 +64,84 @@ const EditAdminsActions = memo(({ admins, filterData, submit }) => {
|
||||
)
|
||||
})
|
||||
|
||||
const AddUsersAction = memo(({ users, filterData, submit }) => {
|
||||
// Handle submit form
|
||||
const handleEditAdmins = (formData) => {
|
||||
submit(formData.users)
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
color: 'secondary',
|
||||
'data-cy': 'add-user',
|
||||
label: T['groups.actions.add.user'],
|
||||
variant: 'outlined',
|
||||
sx: {
|
||||
m: '1em',
|
||||
},
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
cy: 'add-user',
|
||||
name: T['groups.actions.add.user'],
|
||||
dialogProps: {
|
||||
title: T['groups.actions.add.user'],
|
||||
dataCy: 'modal-add-user',
|
||||
},
|
||||
form: () =>
|
||||
EditUsersForm({
|
||||
initialValues: users,
|
||||
stepProps: {
|
||||
filterData,
|
||||
},
|
||||
}),
|
||||
onSubmit: handleEditAdmins,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const RemoveUsersAction = memo(({ users, filterData, submit }) => {
|
||||
// Handle submit form
|
||||
const handleEditAdmins = (formData) => {
|
||||
submit(formData.users)
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
color: 'secondary',
|
||||
'data-cy': 'remove-user',
|
||||
label: T['groups.actions.remove.user'],
|
||||
variant: 'outlined',
|
||||
sx: {
|
||||
m: '1em',
|
||||
},
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
cy: 'remove-user',
|
||||
name: T['groups.actions.remove.user'],
|
||||
dialogProps: {
|
||||
title: T['groups.actions.remove.user'],
|
||||
dataCy: 'modal-remove-user',
|
||||
},
|
||||
form: () =>
|
||||
EditUsersForm({
|
||||
initialValues: users,
|
||||
stepProps: {
|
||||
filterData,
|
||||
},
|
||||
}),
|
||||
onSubmit: handleEditAdmins,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
EditAdminsActions.propTypes = {
|
||||
admins: PropTypes.array,
|
||||
filterData: PropTypes.func,
|
||||
@ -71,4 +149,18 @@ EditAdminsActions.propTypes = {
|
||||
}
|
||||
EditAdminsActions.displayName = 'EditAdminsActions'
|
||||
|
||||
export { EditAdminsActions }
|
||||
AddUsersAction.propTypes = {
|
||||
users: PropTypes.array,
|
||||
filterData: PropTypes.func,
|
||||
submit: PropTypes.func,
|
||||
}
|
||||
AddUsersAction.displayName = 'AddUsersAction'
|
||||
|
||||
RemoveUsersAction.propTypes = {
|
||||
users: PropTypes.array,
|
||||
filterData: PropTypes.func,
|
||||
submit: PropTypes.func,
|
||||
}
|
||||
RemoveUsersAction.displayName = 'RemoveUsersAction'
|
||||
|
||||
export { AddUsersAction, EditAdminsActions, RemoveUsersAction }
|
||||
|
@ -13,16 +13,16 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Stack } from '@mui/material'
|
||||
import { UsersTable } from '@modules/components/Tables'
|
||||
import { Stack } from '@mui/material'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
import { GroupAPI, useGeneralApi } from '@FeaturesModule'
|
||||
import { getActionsAvailable } from '@ModelsModule'
|
||||
import { GROUP_ACTIONS, T } from '@ConstantsModule'
|
||||
import { GroupAPI, UserAPI, useGeneralApi } from '@FeaturesModule'
|
||||
import { getActionsAvailable } from '@ModelsModule'
|
||||
|
||||
import { EditAdminsActions } from './Actions'
|
||||
import { AddUsersAction, EditAdminsActions, RemoveUsersAction } from './Actions'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
@ -35,12 +35,15 @@ const _ = require('lodash')
|
||||
* @param {object} props.tabProps.actions - Actions to this tab
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const GroupUsersTab = ({ tabProps: { actions } = {}, id }) => {
|
||||
const GroupUsersTab = ({ tabProps: { actions } = {}, id: groupId }) => {
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
const [addAdmins] = GroupAPI.useAddAdminToGroupMutation()
|
||||
const [removeAdmins] = GroupAPI.useRemoveAdminFromGroupMutation()
|
||||
const [addUser] = UserAPI.useAddGroupMutation()
|
||||
const [removeUser] = UserAPI.useRemoveFromGroupMutation()
|
||||
const { data: users } = UserAPI.useGetUsersQuery()
|
||||
|
||||
const { data: group, refetch } = GroupAPI.useGetGroupQuery({ id })
|
||||
const { data: group, refetch } = GroupAPI.useGetGroupQuery({ id: groupId })
|
||||
const adminsGroup = Array.isArray(group.ADMINS?.ID)
|
||||
? group.ADMINS?.ID
|
||||
: [group.ADMINS?.ID]
|
||||
@ -48,14 +51,15 @@ const GroupUsersTab = ({ tabProps: { actions } = {}, id }) => {
|
||||
const actionsAvailable = getActionsAvailable(actions)
|
||||
|
||||
// Filter function to get only group users and add if the user is admin group
|
||||
const filterData = (data) => {
|
||||
const filterDataByAdmin = (data) => {
|
||||
// Returns all users of this group
|
||||
const filterUsers = data.filter((user) => {
|
||||
// filter users by group id
|
||||
const groupsUser = Array.isArray(user.GROUPS.ID)
|
||||
? user.GROUPS.ID
|
||||
: [user.GROUPS.ID]
|
||||
|
||||
return groupsUser.some((groupUser) => groupUser === id)
|
||||
return groupsUser.some((groupUser) => groupUser === groupId)
|
||||
})
|
||||
|
||||
const admins = Array.isArray(group.ADMINS?.ID)
|
||||
@ -70,30 +74,103 @@ const GroupUsersTab = ({ tabProps: { actions } = {}, id }) => {
|
||||
})
|
||||
}
|
||||
|
||||
// Filter users and show the ones that are not in a group
|
||||
const filterDataNotInGroup = (data) =>
|
||||
data.filter((user) => {
|
||||
// filter users by group id
|
||||
const groupsUser = Array.isArray(user.GROUPS.ID)
|
||||
? user.GROUPS.ID
|
||||
: [user.GROUPS.ID]
|
||||
|
||||
return !groupsUser.some((groupUser) => groupUser === groupId)
|
||||
})
|
||||
|
||||
// Filter users and show the ones that are in the current group and are not his primary group
|
||||
const filterDataInGroup = (data) =>
|
||||
data.filter((user) => {
|
||||
const USER_GROUPS = [].concat(user.GROUPS.ID ?? [])
|
||||
const primaryGroupId = user?.GID ?? USER_GROUPS?.[0]
|
||||
|
||||
// filter users by group id
|
||||
const groupsUser = Array.isArray(user.GROUPS.ID)
|
||||
? user.GROUPS.ID
|
||||
: [user.GROUPS.ID]
|
||||
|
||||
return (
|
||||
groupsUser.some((groupUser) => groupUser === groupId) &&
|
||||
primaryGroupId !== groupId
|
||||
)
|
||||
})
|
||||
|
||||
// Add and remove administrators
|
||||
const submitAdmins = async (adminsToAdd, adminsToRemove) => {
|
||||
// Add admins
|
||||
await Promise.all(adminsToAdd.map((user) => addAdmins({ id, user })))
|
||||
await Promise.all(
|
||||
adminsToAdd.map((user) => addAdmins({ id: groupId, user }))
|
||||
)
|
||||
|
||||
// Remove admins
|
||||
await Promise.all(adminsToRemove.map((user) => removeAdmins({ id, user })))
|
||||
await Promise.all(
|
||||
adminsToRemove.map((user) => removeAdmins({ id: groupId, user }))
|
||||
)
|
||||
|
||||
// Refresh info
|
||||
refetch({ id })
|
||||
refetch({ id: groupId })
|
||||
|
||||
// Success message
|
||||
enqueueSuccess(T['groups.actions.edit.admins.success'])
|
||||
}
|
||||
|
||||
const submitNewUsers = async (usersToAdd) => {
|
||||
await Promise.all(
|
||||
usersToAdd.map((user) => addUser({ id: user, group: groupId }))
|
||||
)
|
||||
|
||||
// Refresh info
|
||||
refetch({ id: groupId })
|
||||
|
||||
// Success message
|
||||
enqueueSuccess(T['groups.actions.add.user.success'])
|
||||
}
|
||||
|
||||
const submitDeleteUsers = async (usersToAdd) => {
|
||||
await Promise.all(
|
||||
usersToAdd.map((user) => removeUser({ id: user, group: groupId }))
|
||||
)
|
||||
|
||||
// Refresh info
|
||||
refetch({ id: groupId })
|
||||
|
||||
// Success message
|
||||
enqueueSuccess(T['groups.actions.add.user.success'])
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{actionsAvailable?.includes?.(GROUP_ACTIONS.EDIT_ADMINS) && (
|
||||
<EditAdminsActions
|
||||
admins={adminsGroup}
|
||||
filterData={filterData}
|
||||
filterData={filterDataByAdmin}
|
||||
submit={submitAdmins}
|
||||
/>
|
||||
)}
|
||||
|
||||
{actionsAvailable?.includes?.(GROUP_ACTIONS.ADD_USERS) && (
|
||||
<AddUsersAction
|
||||
users={users}
|
||||
filterData={filterDataNotInGroup}
|
||||
submit={submitNewUsers}
|
||||
/>
|
||||
)}
|
||||
|
||||
{actionsAvailable?.includes?.(GROUP_ACTIONS.REMOVE_USERS) && (
|
||||
<RemoveUsersAction
|
||||
users={users}
|
||||
filterData={filterDataInGroup}
|
||||
submit={submitDeleteUsers}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Stack
|
||||
display="grid"
|
||||
gap="1em"
|
||||
@ -103,8 +180,8 @@ const GroupUsersTab = ({ tabProps: { actions } = {}, id }) => {
|
||||
<UsersTable.Table
|
||||
disableRowSelect
|
||||
disableGlobalSort
|
||||
groupId={id}
|
||||
filterData={filterData}
|
||||
groupId={groupId}
|
||||
filterData={filterDataByAdmin}
|
||||
/>
|
||||
</Stack>
|
||||
</div>
|
||||
|
@ -23,7 +23,7 @@ import { getAvailableInfoTabs } from '@ModelsModule'
|
||||
|
||||
import { BaseTab as Tabs } from '@modules/components/Tabs'
|
||||
import Info from '@modules/components/Tabs/Group/Info'
|
||||
import Users from '@modules/components/Tabs/Group/Users'
|
||||
import GroupUsersTab from '@modules/components/Tabs/Group/Users'
|
||||
import generateQuotasInfoTab from '@modules/components/Tabs/Quota'
|
||||
import generateAccountingInfoTab from '@modules/components/Tabs/Accounting'
|
||||
import generateShowbackInfoTab from '@modules/components/Tabs/Showback'
|
||||
@ -31,7 +31,7 @@ import generateShowbackInfoTab from '@modules/components/Tabs/Showback'
|
||||
const getTabComponent = (tabName) =>
|
||||
({
|
||||
info: Info,
|
||||
user: Users,
|
||||
user: GroupUsersTab,
|
||||
quota: generateQuotasInfoTab({ groups: true }),
|
||||
accounting: generateAccountingInfoTab({ groups: true }),
|
||||
showback: generateShowbackInfoTab({ groups: true }),
|
||||
|
159
src/fireedge/src/modules/components/Tabs/User/Group/Action.js
Normal file
159
src/fireedge/src/modules/components/Tabs/User/Group/Action.js
Normal file
@ -0,0 +1,159 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2025, 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 PropTypes from 'prop-types'
|
||||
import { memo } from 'react'
|
||||
|
||||
import { T } from '@ConstantsModule'
|
||||
import ButtonToTriggerForm from '@modules/components/Forms/ButtonToTriggerForm'
|
||||
import { EditGroupForm } from '@modules/components/Forms/User'
|
||||
|
||||
const AddToGroup = memo(({ groups, filterData, submit }) => {
|
||||
// Handle submit form
|
||||
const habdleSubmit = (formData) => {
|
||||
submit(formData.groups)
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
color: 'secondary',
|
||||
'data-cy': 'add-to-group',
|
||||
label: T['users.actions.add.to.group'],
|
||||
variant: 'outlined',
|
||||
sx: {
|
||||
m: '1em',
|
||||
},
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
cy: 'add-to-group',
|
||||
name: T['users.actions.add.to.group'],
|
||||
dialogProps: {
|
||||
title: T['users.actions.add.to.group'],
|
||||
dataCy: 'modal-add-to-group',
|
||||
},
|
||||
form: () =>
|
||||
EditGroupForm({
|
||||
initialValues: groups,
|
||||
stepProps: {
|
||||
filterData,
|
||||
},
|
||||
}),
|
||||
onSubmit: habdleSubmit,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const RemoveFromGroup = memo(({ groups, filterData, submit }) => {
|
||||
const handleSubmit = (formData) => {
|
||||
submit(formData.groups)
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
color: 'secondary',
|
||||
'data-cy': 'remove-from-group',
|
||||
label: T['users.actions.remove.from.group'],
|
||||
variant: 'outlined',
|
||||
sx: {
|
||||
m: '1em',
|
||||
},
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
cy: 'remove-from-group',
|
||||
name: T['users.actions.remove.from.group'],
|
||||
dialogProps: {
|
||||
title: T['users.actions.remove.from.group'],
|
||||
dataCy: 'modal-remove-from-group',
|
||||
},
|
||||
form: () =>
|
||||
EditGroupForm({
|
||||
initialValues: groups,
|
||||
stepProps: {
|
||||
filterData,
|
||||
},
|
||||
}),
|
||||
onSubmit: handleSubmit,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const ChangePrimaryGroup = memo(({ groups, filterData, submit }) => {
|
||||
const handleSubmit = (formData) => {
|
||||
submit(formData.groups)
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
color: 'secondary',
|
||||
'data-cy': 'change-primary-group',
|
||||
label: T['users.actions.change.primary.group'],
|
||||
variant: 'outlined',
|
||||
sx: {
|
||||
m: '1em',
|
||||
},
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
cy: 'change-primary-group',
|
||||
name: T['users.actions.change.primary.group'],
|
||||
dialogProps: {
|
||||
title: T['users.actions.change.primary.group'],
|
||||
dataCy: 'modal-change-primary-group',
|
||||
},
|
||||
form: () =>
|
||||
EditGroupForm({
|
||||
initialValues: groups,
|
||||
stepProps: {
|
||||
filterData,
|
||||
},
|
||||
}),
|
||||
onSubmit: handleSubmit,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
AddToGroup.propTypes = {
|
||||
groups: PropTypes.array,
|
||||
filterData: PropTypes.func,
|
||||
submit: PropTypes.func,
|
||||
}
|
||||
AddToGroup.displayName = 'AddToGroupAction'
|
||||
|
||||
RemoveFromGroup.propTypes = {
|
||||
groups: PropTypes.array,
|
||||
filterData: PropTypes.func,
|
||||
submit: PropTypes.func,
|
||||
}
|
||||
RemoveFromGroup.displayName = 'RemoveFromGroupAction'
|
||||
|
||||
ChangePrimaryGroup.propTypes = {
|
||||
groups: PropTypes.array,
|
||||
filterData: PropTypes.func,
|
||||
submit: PropTypes.func,
|
||||
}
|
||||
ChangePrimaryGroup.displayName = 'ChangePrimaryGroupAction'
|
||||
|
||||
export { AddToGroup, ChangePrimaryGroup, RemoveFromGroup }
|
@ -13,35 +13,46 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useHistory, generatePath } from 'react-router-dom'
|
||||
import { ReactElement, useMemo } from 'react'
|
||||
import { generatePath, useHistory } from 'react-router-dom'
|
||||
|
||||
import { PATH } from '@modules/components/path'
|
||||
|
||||
import { UserAPI, GroupAPI } from '@FeaturesModule'
|
||||
import { GroupAPI, UserAPI, useGeneralApi } from '@FeaturesModule'
|
||||
import { getActionsAvailable } from '@ModelsModule'
|
||||
|
||||
import { Box, Divider } from '@mui/material'
|
||||
|
||||
import { T } from '@ConstantsModule'
|
||||
import { Tr } from '@modules/components/HOC'
|
||||
import { AddToGroup, ChangePrimaryGroup, RemoveFromGroup } from './Action'
|
||||
|
||||
import { T, USER_ACTIONS } from '@ConstantsModule'
|
||||
import { GroupCard } from '@modules/components/Cards'
|
||||
import { Tr } from '@modules/components/HOC'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Datastore id
|
||||
* @param props.tabProps
|
||||
* @param props.tabProps.actions
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const GroupsInfoTab = ({ id }) => {
|
||||
const GroupsInfoTab = ({ tabProps: { actions } = {}, id: userId }) => {
|
||||
const path = PATH.SYSTEM.GROUPS.DETAIL
|
||||
const history = useHistory()
|
||||
const { data = [] } = GroupAPI.useGetGroupsQuery()
|
||||
const { data: user } = UserAPI.useGetUserQuery({ id })
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
const { data: groups = [], refetch } = GroupAPI.useGetGroupsQuery()
|
||||
const { data: user } = UserAPI.useGetUserQuery({ id: userId })
|
||||
const [addUser] = UserAPI.useAddGroupMutation()
|
||||
const [removeUser] = UserAPI.useRemoveFromGroupMutation()
|
||||
const [changeGroup] = UserAPI.useChangeGroupMutation()
|
||||
|
||||
const USER_GROUPS = [].concat(user.GROUPS.ID ?? [])
|
||||
|
||||
const actionsAvailable = getActionsAvailable(actions)
|
||||
|
||||
const handleRowClick = (rowId) => {
|
||||
history.push(generatePath(path, { id: String(rowId) }))
|
||||
}
|
||||
@ -50,25 +61,98 @@ const GroupsInfoTab = ({ id }) => {
|
||||
|
||||
const primaryGroup = useMemo(
|
||||
() =>
|
||||
data.find(
|
||||
groups.find(
|
||||
(group) =>
|
||||
group.ID === primaryGroupId ||
|
||||
String(group.ID) === String(primaryGroupId)
|
||||
),
|
||||
[data]
|
||||
[groups]
|
||||
)
|
||||
|
||||
const secondaryGroups = useMemo(
|
||||
() =>
|
||||
data.filter(
|
||||
groups.filter(
|
||||
(group) =>
|
||||
group?.ID !== primaryGroupId && USER_GROUPS?.includes(group?.ID)
|
||||
),
|
||||
[data]
|
||||
[groups]
|
||||
)
|
||||
|
||||
/* Filter groups showing only the ones the user is not linked into */
|
||||
const filterGroupsNotLinked = (data) =>
|
||||
data.filter(
|
||||
(group) => !USER_GROUPS.some((userGroup) => userGroup === group.ID)
|
||||
)
|
||||
|
||||
/* Filter groups showing only the ones the user is linked into and it's not its primary group */
|
||||
const filterGroupsLinked = (data) =>
|
||||
data.filter(
|
||||
(group) =>
|
||||
USER_GROUPS.some((userGroup) => userGroup === group.ID) &&
|
||||
group.ID !== primaryGroupId
|
||||
)
|
||||
|
||||
/* Filter groups showing only the ones the user has not as primary group */
|
||||
const filterByNotPrimaryGroup = (data) =>
|
||||
data.filter((group) => group.ID !== primaryGroupId)
|
||||
|
||||
const submitAddToGroup = async (groupsToAdd) => {
|
||||
await Promise.all(
|
||||
groupsToAdd.map((groupId) => addUser({ id: userId, group: groupId }))
|
||||
)
|
||||
|
||||
refetch()
|
||||
|
||||
// Success message
|
||||
enqueueSuccess(T['user.actions.edit.group.success'])
|
||||
}
|
||||
|
||||
const submitRemoveFromGroup = async (groupsToAdd) => {
|
||||
await Promise.all(
|
||||
groupsToAdd.map((groupId) => removeUser({ id: userId, group: groupId }))
|
||||
)
|
||||
|
||||
refetch()
|
||||
|
||||
// Success message
|
||||
enqueueSuccess(T['user.actions.edit.group.success'])
|
||||
}
|
||||
|
||||
const changePrimaryGroup = (group) => {
|
||||
changeGroup({ id: userId, group: group })
|
||||
|
||||
refetch()
|
||||
|
||||
// Success message
|
||||
enqueueSuccess(T['user.actions.edit.group.success'])
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{actionsAvailable?.includes?.(USER_ACTIONS.ADD_TO_GROUP) && (
|
||||
<AddToGroup
|
||||
groups={groups}
|
||||
filterData={filterGroupsNotLinked}
|
||||
submit={submitAddToGroup}
|
||||
/>
|
||||
)}
|
||||
|
||||
{actionsAvailable?.includes?.(USER_ACTIONS.REMOVE_FROM_GROUP) && (
|
||||
<RemoveFromGroup
|
||||
groups={groups}
|
||||
filterData={filterGroupsLinked}
|
||||
submit={submitRemoveFromGroup}
|
||||
/>
|
||||
)}
|
||||
|
||||
{actionsAvailable?.includes?.(USER_ACTIONS.CHANGE_PRIMARY_GROUP) && (
|
||||
<ChangePrimaryGroup
|
||||
groups={groups}
|
||||
filterData={filterByNotPrimaryGroup}
|
||||
submit={changePrimaryGroup}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
@ -41,4 +41,6 @@ export const GROUP_ACTIONS = {
|
||||
QUOTAS_DIALOG: 'quotas_dialog',
|
||||
DELETE: 'delete',
|
||||
EDIT_ADMINS: 'edit_admins',
|
||||
ADD_USERS: 'add_users',
|
||||
REMOVE_USERS: 'remove_users',
|
||||
}
|
||||
|
@ -2203,8 +2203,11 @@ module.exports = {
|
||||
'See OpenNebula documentation to get more details about views on Fireedge Sunstone.',
|
||||
'groups.actions.edit.admins': 'Edit administrators',
|
||||
'groups.actions.edit.admins.form': 'Select the administrators',
|
||||
'groups.actions.edit.users.form': 'Select the users',
|
||||
'groups.actions.edit.admins.success': 'Administrators updated',
|
||||
|
||||
'groups.actions.add.user.success': 'Users updated',
|
||||
'groups.actions.add.user': 'Add users',
|
||||
'groups.actions.remove.user': 'Remove users',
|
||||
'groups.view.admin.name': 'Admin view',
|
||||
'groups.view.admin.description': 'View used by admin users',
|
||||
'groups.view.user.name': 'User view',
|
||||
@ -2215,6 +2218,12 @@ module.exports = {
|
||||
'groups.view.groupadmin.description':
|
||||
'View used by the admin users of the group',
|
||||
|
||||
/* User */
|
||||
'users.actions.add.to.group': 'Add to group',
|
||||
'users.actions.remove.from.group': 'Remove from group',
|
||||
'user.actions.edit.group.success': 'Group updated',
|
||||
'users.actions.change.primary.group': 'Change primary group',
|
||||
|
||||
/* Showback */
|
||||
'showback.title': 'Showback',
|
||||
'showback.button.getShowback': 'Get showback',
|
||||
|
@ -14,10 +14,15 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
// eslint-disable-next-line prettier/prettier, no-unused-vars
|
||||
import { VmQuota, NetworkQuota, DatastoreQuota, ImageQuota } from '@modules/constants/quota'
|
||||
import * as ACTIONS from '@modules/constants/actions'
|
||||
import * as STATES from '@modules/constants/states'
|
||||
import { COLOR } from '@modules/constants/color'
|
||||
import {
|
||||
DatastoreQuota,
|
||||
ImageQuota,
|
||||
NetworkQuota,
|
||||
VmQuota,
|
||||
} from '@modules/constants/quota'
|
||||
import * as STATES from '@modules/constants/states'
|
||||
|
||||
/**
|
||||
* @typedef LoginToken
|
||||
@ -77,6 +82,9 @@ export const USER_ACTIONS = {
|
||||
CHANGE_AUTH: 'change_authentication',
|
||||
ENABLE: 'enable',
|
||||
DISABLE: 'disable',
|
||||
ADD_TO_GROUP: 'add_to_group',
|
||||
REMOVE_FROM_GROUP: 'remove_from_group',
|
||||
CHANGE_PRIMARY_GROUP: 'change_primary_group',
|
||||
}
|
||||
|
||||
export const AUTH_DRIVER = {
|
||||
|
Reference in New Issue
Block a user