mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
F OpenNebula/one#6362: Add missing label selectors (#2844)
This commit is contained in:
parent
ec64ac4457
commit
25e3529f53
@ -20,13 +20,18 @@ import { Typography } from '@mui/material'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { StatusCircle } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import {
|
||||
getColorFromString,
|
||||
getUniqueLabels,
|
||||
timeFromMilliseconds,
|
||||
} from 'client/models/Helper'
|
||||
|
||||
import Timer from 'client/components/Timer'
|
||||
import { T } from 'client/constants'
|
||||
import { Group, HighPriority, Lock, User } from 'iconoir-react'
|
||||
|
||||
import COLOR from 'client/constants/color'
|
||||
import { timeFromMilliseconds } from 'client/models/Helper'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const haveValues = function (object) {
|
||||
@ -56,6 +61,7 @@ const BackupJobCard = memo(
|
||||
PRIORITY,
|
||||
LAST_BACKUP_TIME,
|
||||
LOCK,
|
||||
TEMPLATE: { LABELS } = {},
|
||||
} = template
|
||||
|
||||
const time = useMemo(() => {
|
||||
@ -110,6 +116,18 @@ const BackupJobCard = memo(
|
||||
}
|
||||
}, [OUTDATED_VMS, BACKING_UP_VMS, ERROR_VMS, LAST_BACKUP_TIME])
|
||||
|
||||
const labels = useMemo(
|
||||
() =>
|
||||
getUniqueLabels(LABELS).map((label) => ({
|
||||
text: label,
|
||||
dataCy: `label-${label}`,
|
||||
stateColor: getColorFromString(label),
|
||||
onClick: onClickLabel,
|
||||
onDelete: onDeleteLabel,
|
||||
})),
|
||||
[LABELS, onClickLabel, onDeleteLabel]
|
||||
)
|
||||
|
||||
return (
|
||||
<div {...rootProps} data-cy={`backupjob-${ID}`}>
|
||||
<div className={classes.main}>
|
||||
@ -118,6 +136,7 @@ const BackupJobCard = memo(
|
||||
<Typography component="span">{NAME}</Typography>
|
||||
<span className={classes.labels}>
|
||||
{LOCK && <Lock data-cy="lock" />}
|
||||
<MultipleTags tags={labels} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
|
@ -14,7 +14,8 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import PropTypes from 'prop-types'
|
||||
import { memo, ReactElement } from 'react'
|
||||
import { memo, ReactElement, useMemo } from 'react'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
|
||||
import { Typography } from '@mui/material'
|
||||
import { ModernTv, Server } from 'iconoir-react'
|
||||
@ -27,6 +28,8 @@ import {
|
||||
} from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
|
||||
import { getColorFromString, getUniqueLabels } from 'client/models/Helper'
|
||||
|
||||
import { Host, HOST_THRESHOLD, T } from 'client/constants'
|
||||
import { getAllocatedInfo, getState } from 'client/models/Host'
|
||||
|
||||
@ -36,11 +39,33 @@ const HostCard = memo(
|
||||
* @param {Host} props.host - Host resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @param {ReactElement} props.actions - Actions
|
||||
* @param {function(string):Promise} [props.onClickLabel] - Callback to click label
|
||||
* @param {function(string):Promise} [props.onDeleteLabel] - Callback to delete label
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ host, rootProps, actions }) => {
|
||||
({ host, rootProps, actions, onClickLabel, onDeleteLabel }) => {
|
||||
const classes = rowStyles()
|
||||
const { ID, NAME, IM_MAD, VM_MAD, HOST_SHARE, CLUSTER } = host
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
IM_MAD,
|
||||
VM_MAD,
|
||||
HOST_SHARE,
|
||||
CLUSTER,
|
||||
TEMPLATE: { LABELS } = {},
|
||||
} = host
|
||||
|
||||
const labels = useMemo(
|
||||
() =>
|
||||
getUniqueLabels(LABELS).map((label) => ({
|
||||
text: label,
|
||||
dataCy: `label-${label}`,
|
||||
stateColor: getColorFromString(label),
|
||||
onClick: onClickLabel,
|
||||
onDelete: onDeleteLabel,
|
||||
})),
|
||||
[LABELS, onClickLabel, onDeleteLabel]
|
||||
)
|
||||
|
||||
const {
|
||||
percentCpuUsed,
|
||||
@ -55,7 +80,7 @@ const HostCard = memo(
|
||||
const totalVms = [host?.VMS?.ID ?? []].flat().length || 0
|
||||
const { color: stateColor, name: stateName } = getState(host)
|
||||
|
||||
const labels = [...new Set([IM_MAD, VM_MAD])]
|
||||
const statusLabels = [...new Set([IM_MAD, VM_MAD])]
|
||||
|
||||
return (
|
||||
<div {...rootProps} data-cy={`host-${ID}`}>
|
||||
@ -65,11 +90,14 @@ const HostCard = memo(
|
||||
<Typography noWrap component="span">
|
||||
{NAME}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
{labels.map((label) => (
|
||||
<span className={classes.statusLabels}>
|
||||
{statusLabels.map((label) => (
|
||||
<StatusChip key={label} text={label} />
|
||||
))}
|
||||
</span>
|
||||
<span className={classes.labels}>
|
||||
<MultipleTags tags={labels} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${ID}`}</span>
|
||||
@ -113,6 +141,8 @@ HostCard.propTypes = {
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
actions: PropTypes.any,
|
||||
onClickLabel: PropTypes.func,
|
||||
onDeleteLabel: PropTypes.func,
|
||||
}
|
||||
|
||||
HostCard.displayName = 'HostCard'
|
||||
|
@ -60,9 +60,10 @@ const NetworkCard = memo(
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @param {function(string):Promise} [props.onDeleteLabel] - Callback to delete label
|
||||
* @param {ReactElement} [props.actions] - Actions
|
||||
* @param {function(string):Promise} [props.onClickLabel] - Callback to click label
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ network, rootProps, actions, onDeleteLabel }) => {
|
||||
({ network, rootProps, actions, onClickLabel, onDeleteLabel }) => {
|
||||
const classes = rowStyles()
|
||||
const { [RESOURCE_NAMES.VM]: vmView } = useViews()
|
||||
|
||||
@ -93,6 +94,7 @@ const NetworkCard = memo(
|
||||
getUniqueLabels(LABELS).map((label) => ({
|
||||
text: label,
|
||||
stateColor: getColorFromString(label),
|
||||
onClick: onClickLabel,
|
||||
onDelete: enableEditLabels && onDeleteLabel,
|
||||
})),
|
||||
[LABELS, enableEditLabels, onDeleteLabel]
|
||||
@ -169,6 +171,7 @@ NetworkCard.propTypes = {
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
onClickLabel: PropTypes.func,
|
||||
onDeleteLabel: PropTypes.func,
|
||||
actions: PropTypes.any,
|
||||
}
|
||||
|
@ -19,8 +19,10 @@ import PropTypes from 'prop-types'
|
||||
import { User, Group, PcCheck, PcNoEntry, PcWarning } from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { SecurityGroup } from 'client/constants'
|
||||
import { getColorFromString, getUniqueLabels } from 'client/models/Helper'
|
||||
|
||||
const getTotalOfResources = (resources) =>
|
||||
[resources?.ID ?? []].flat().length || 0
|
||||
@ -31,13 +33,23 @@ const SecurityGroupCard = memo(
|
||||
* @param {SecurityGroup} props.securityGroup - Security Group resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @param {ReactElement} [props.actions] - Actions
|
||||
* @param {function(string):Promise} [props.onDeleteLabel] - Callback to delete label
|
||||
* @param {function(string):Promise} [props.onClickLabel] - Callback to click label
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ securityGroup, rootProps, actions }) => {
|
||||
({ securityGroup, rootProps, actions, onClickLabel, onDeleteLabel }) => {
|
||||
const classes = rowStyles()
|
||||
|
||||
const { ID, NAME, UNAME, GNAME, UPDATED_VMS, OUTDATED_VMS, ERROR_VMS } =
|
||||
securityGroup
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
UNAME,
|
||||
GNAME,
|
||||
UPDATED_VMS,
|
||||
OUTDATED_VMS,
|
||||
ERROR_VMS,
|
||||
TEMPLATE: { LABELS } = {},
|
||||
} = securityGroup
|
||||
|
||||
const [totalUpdatedVms, totalOutdatedVms, totalErrorVms] = useMemo(
|
||||
() => [
|
||||
@ -48,6 +60,17 @@ const SecurityGroupCard = memo(
|
||||
[UPDATED_VMS?.ID, OUTDATED_VMS?.ID, ERROR_VMS?.ID]
|
||||
)
|
||||
|
||||
const labels = useMemo(
|
||||
() =>
|
||||
getUniqueLabels(LABELS).map((label) => ({
|
||||
text: label,
|
||||
stateColor: getColorFromString(label),
|
||||
onClick: onClickLabel,
|
||||
onDelete: onDeleteLabel,
|
||||
})),
|
||||
[LABELS, onDeleteLabel]
|
||||
)
|
||||
|
||||
return (
|
||||
<div {...rootProps} data-cy={`secgroup-${ID}`}>
|
||||
<div className={classes.main}>
|
||||
@ -55,6 +78,8 @@ const SecurityGroupCard = memo(
|
||||
<Typography noWrap component="span">
|
||||
{NAME}
|
||||
</Typography>
|
||||
|
||||
<MultipleTags tags={labels} />
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${ID}`}</span>
|
||||
@ -91,6 +116,8 @@ SecurityGroupCard.propTypes = {
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
onClickLabel: PropTypes.func,
|
||||
onDeleteLabel: PropTypes.func,
|
||||
actions: PropTypes.any,
|
||||
}
|
||||
|
||||
|
@ -119,8 +119,6 @@ export const ChartRenderer = ({
|
||||
() => (coordinateType === 'POLAR' ? FormatPolarDataset(datasets) : null),
|
||||
[coordinateType, datasets]
|
||||
)
|
||||
console.log('polarDataset: ', polarDataset)
|
||||
|
||||
const chartConfig = useMemo(
|
||||
() =>
|
||||
GetChartConfig(
|
||||
|
@ -127,7 +127,5 @@ export const FIELDS = [
|
||||
INCREMENT_MODE,
|
||||
]
|
||||
|
||||
console.log({ FIELDS })
|
||||
|
||||
/** @type {ObjectSchema} Graphics schema */
|
||||
export const BACKUP_SCHEMA = getObjectSchemaFromFields(FIELDS)
|
||||
|
@ -22,8 +22,14 @@ const COLUMNS = [
|
||||
{ Header: T.ID, id: 'id', accessor: 'ID', sortType: 'number' },
|
||||
{ Header: T.Name, id: 'name', accessor: 'NAME' },
|
||||
{ Header: T.Locked, id: 'locked', accessor: 'LOCK' },
|
||||
{
|
||||
Header: T.Label,
|
||||
id: 'label',
|
||||
accessor: 'TEMPLATE.LABELS',
|
||||
filter: 'includesSome',
|
||||
},
|
||||
]
|
||||
|
||||
COLUMNS.noFilterIds = ['id', 'name']
|
||||
COLUMNS.noFilterIds = ['id', 'name', 'label']
|
||||
|
||||
export default COLUMNS
|
||||
|
@ -16,13 +16,14 @@
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { CategoryFilter } from 'client/components/Tables/Enhanced/Utils'
|
||||
import * as ImageModel from 'client/models/Image'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const getTotalOfResources = (resources) =>
|
||||
[resources?.ID ?? []].flat().length || 0
|
||||
|
||||
export default [
|
||||
{ Header: 'ID', accessor: 'ID', sortType: 'number' },
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
const COLUMNS = [
|
||||
{ Header: 'ID', accessor: 'ID', id: 'id', sortType: 'number' },
|
||||
{ Header: 'Name', id: 'name', accessor: 'NAME' },
|
||||
{ Header: 'Owner', accessor: 'UNAME' },
|
||||
{ Header: 'Group', accessor: 'GNAME' },
|
||||
{ Header: 'Locked', id: 'locked', accessor: 'LOCK' },
|
||||
@ -49,6 +50,12 @@ export default [
|
||||
id: 'DISK_TYPE',
|
||||
accessor: (row) => ImageModel.getDiskType(row),
|
||||
},
|
||||
{
|
||||
Header: T.Label,
|
||||
id: 'label',
|
||||
accessor: 'TEMPLATE.LABELS',
|
||||
filter: 'includesSome',
|
||||
},
|
||||
{ Header: 'Registration Time', accessor: 'REGTIME' },
|
||||
{ Header: 'Datastore', accessor: 'DATASTORE' },
|
||||
{ Header: 'Persistent', accessor: 'PERSISTENT' },
|
||||
@ -64,3 +71,7 @@ export default [
|
||||
sortType: 'number',
|
||||
},
|
||||
]
|
||||
|
||||
COLUMNS.noFilterIds = ['id', 'name', 'label']
|
||||
|
||||
export default COLUMNS
|
||||
|
@ -15,6 +15,8 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import PropTypes from 'prop-types'
|
||||
import { useMemo, useCallback } from 'react'
|
||||
import imageApi, { useUpdateImageMutation } from 'client/features/OneApi/image'
|
||||
|
||||
import {
|
||||
Lock,
|
||||
@ -26,6 +28,12 @@ import {
|
||||
Archive as DiskTypeIcon,
|
||||
} from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import {
|
||||
jsonToXml,
|
||||
getUniqueLabels,
|
||||
getColorFromString,
|
||||
} from 'client/models/Helper'
|
||||
|
||||
import Timer from 'client/components/Timer'
|
||||
import { StatusCircle, StatusChip } from 'client/components/Status'
|
||||
@ -35,10 +43,31 @@ import { T } from 'client/constants'
|
||||
import * as ImageModel from 'client/models/Image'
|
||||
import * as Helper from 'client/models/Helper'
|
||||
|
||||
const Row = ({ original, value, ...props }) => {
|
||||
const Row = ({ original, value, onClickLabel, ...props }) => {
|
||||
const [update] = useUpdateImageMutation()
|
||||
|
||||
const state = imageApi.endpoints.getImages.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((image) => +image.ID === +original.ID),
|
||||
})
|
||||
|
||||
const memoImage = useMemo(() => state ?? original, [state, original])
|
||||
|
||||
const handleDeleteLabel = useCallback(
|
||||
(label) => {
|
||||
const currentLabels = memoImage.TEMPLATE?.LABELS?.split(',')
|
||||
const newLabels = currentLabels.filter((l) => l !== label).join(',')
|
||||
const newImageTemplate = { ...memoImage.TEMPLATE, LABELS: newLabels }
|
||||
const templateXml = jsonToXml(newImageTemplate)
|
||||
|
||||
update({ id: original.ID, template: templateXml, replace: 0 })
|
||||
},
|
||||
[memoImage.TEMPLATE?.LABELS, update]
|
||||
)
|
||||
|
||||
const classes = rowStyles()
|
||||
const {
|
||||
ID,
|
||||
id: ID,
|
||||
NAME,
|
||||
UNAME,
|
||||
GNAME,
|
||||
@ -49,6 +78,7 @@ const Row = ({ original, value, ...props }) => {
|
||||
DATASTORE,
|
||||
TOTAL_VMS,
|
||||
RUNNING_VMS,
|
||||
label: LABELS = [],
|
||||
} = value
|
||||
|
||||
const {
|
||||
@ -62,6 +92,17 @@ const Row = ({ original, value, ...props }) => {
|
||||
|
||||
const time = Helper.timeFromMilliseconds(+REGTIME)
|
||||
|
||||
const multiTagLabels = useMemo(
|
||||
() =>
|
||||
getUniqueLabels(LABELS).map((label) => ({
|
||||
text: label,
|
||||
stateColor: getColorFromString(label),
|
||||
onClick: onClickLabel,
|
||||
onDelete: handleDeleteLabel,
|
||||
})),
|
||||
[LABELS, handleDeleteLabel, onClickLabel]
|
||||
)
|
||||
|
||||
return (
|
||||
<div {...props} data-cy={`image-${ID}`}>
|
||||
<div className={classes.main}>
|
||||
@ -76,6 +117,9 @@ const Row = ({ original, value, ...props }) => {
|
||||
<StatusChip key={label} text={label} />
|
||||
))}
|
||||
</span>
|
||||
<span className={classes.labels}>
|
||||
<MultipleTags tags={multiTagLabels} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${ID}`}</span>
|
||||
@ -130,6 +174,7 @@ Row.propTypes = {
|
||||
value: PropTypes.object,
|
||||
isSelected: PropTypes.bool,
|
||||
handleClick: PropTypes.func,
|
||||
onClickLabel: PropTypes.func,
|
||||
}
|
||||
|
||||
export default Row
|
||||
|
@ -138,7 +138,7 @@ const DatastoresTable = (props) => {
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={useMemo(() => data, [data])}
|
||||
data={useMemo(() => data, [data, data?.TEMPLATE?.LABELS])}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
|
@ -16,7 +16,7 @@
|
||||
import { memo, useCallback, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import api, {
|
||||
import datastoreApi, {
|
||||
useUpdateDatastoreMutation,
|
||||
} from 'client/features/OneApi/datastore'
|
||||
import { DatastoreCard } from 'client/components/Cards'
|
||||
@ -26,12 +26,21 @@ const Row = memo(
|
||||
({ original, onClickLabel, ...props }) => {
|
||||
const [update] = useUpdateDatastoreMutation()
|
||||
|
||||
const state = api.endpoints.getDatastores.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((datastore) => +datastore.ID === +original.ID),
|
||||
})
|
||||
const {
|
||||
data: datastores,
|
||||
error,
|
||||
isLoading,
|
||||
} = datastoreApi.endpoints.getDatastores.useQuery(undefined)
|
||||
|
||||
const memoDs = useMemo(() => state ?? original, [state, original])
|
||||
const datastore = useMemo(
|
||||
() => datastores?.find((ds) => +ds.ID === +original.ID) ?? original,
|
||||
[datastores, original]
|
||||
)
|
||||
|
||||
const memoDs = useMemo(
|
||||
() => datastore ?? original,
|
||||
[datastore, original, update, isLoading, error, datastores]
|
||||
)
|
||||
|
||||
const handleDeleteLabel = useCallback(
|
||||
(label) => {
|
||||
@ -47,7 +56,7 @@ const Row = memo(
|
||||
|
||||
return (
|
||||
<DatastoreCard
|
||||
datastore={state ?? original}
|
||||
datastore={memoDs}
|
||||
onClickLabel={onClickLabel}
|
||||
onDeleteLabel={handleDeleteLabel}
|
||||
rootProps={props}
|
||||
|
@ -20,9 +20,9 @@ import * as ImageModel from 'client/models/Image'
|
||||
const getTotalOfResources = (resources) =>
|
||||
[resources?.ID ?? []].flat().length || 0
|
||||
|
||||
export default [
|
||||
{ Header: 'ID', accessor: 'ID', sortType: 'number' },
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
const COLUMNS = [
|
||||
{ Header: 'ID', id: 'id', accessor: 'ID', sortType: 'number' },
|
||||
{ Header: 'Name', id: 'name', accessor: 'NAME' },
|
||||
{ Header: 'Owner', accessor: 'UNAME' },
|
||||
{ Header: 'Group', accessor: 'GNAME' },
|
||||
{ Header: 'Locked', id: 'locked', accessor: 'LOCK' },
|
||||
@ -64,3 +64,5 @@ export default [
|
||||
sortType: 'number',
|
||||
},
|
||||
]
|
||||
|
||||
export default COLUMNS
|
||||
|
@ -17,7 +17,7 @@
|
||||
const getTotalOfResources = (resources) =>
|
||||
[resources?.ID ?? []].flat().length || 0
|
||||
|
||||
export default [
|
||||
const COLUMNS = [
|
||||
{ Header: 'ID', accessor: 'ID', sortType: 'number' },
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
{
|
||||
@ -31,3 +31,5 @@ export default [
|
||||
{ Header: 'Network quota', accessor: 'NETWORK_QUOTA' },
|
||||
{ Header: 'Image quota', accessor: 'IMAGE_QUOTA' },
|
||||
]
|
||||
|
||||
export default COLUMNS
|
||||
|
@ -16,12 +16,13 @@
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { CategoryFilter } from 'client/components/Tables/Enhanced/Utils'
|
||||
import * as HostModel from 'client/models/Host'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const getTotalOfResources = (resources) =>
|
||||
[resources?.ID ?? []].flat().length || 0
|
||||
|
||||
export default [
|
||||
{ Header: 'ID', accessor: 'ID', sortType: 'number' },
|
||||
const COLUMNS = [
|
||||
{ Header: 'ID', accessor: 'ID', id: 'ID', sortType: 'number' },
|
||||
{
|
||||
Header: 'Name',
|
||||
id: 'NAME',
|
||||
@ -41,9 +42,15 @@ export default [
|
||||
filter: 'includesValue',
|
||||
},
|
||||
{ Header: 'Cluster', accessor: 'CLUSTER' },
|
||||
{
|
||||
Header: T.Label,
|
||||
id: 'label',
|
||||
accessor: 'TEMPLATE.LABELS',
|
||||
filter: 'includesSome',
|
||||
},
|
||||
{
|
||||
Header: 'IM MAD',
|
||||
id: 'IM_MAD',
|
||||
id: 'im_mad',
|
||||
accessor: 'IM_MAD',
|
||||
disableFilters: false,
|
||||
Filter: ({ column }) =>
|
||||
@ -85,3 +92,7 @@ export default [
|
||||
disableSortBy: true,
|
||||
},
|
||||
]
|
||||
|
||||
COLUMNS.noFilterIds = ['id', 'name', 'label']
|
||||
|
||||
export default COLUMNS
|
||||
|
@ -13,21 +13,52 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useMemo } from 'react'
|
||||
import { memo, useMemo, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import hostApi from 'client/features/OneApi/host'
|
||||
import hostApi, { useUpdateHostMutation } from 'client/features/OneApi/host'
|
||||
import { HostCard } from 'client/components/Cards'
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
|
||||
const Row = memo(
|
||||
({ original, value, ...props }) => {
|
||||
const state = hostApi.endpoints.getHosts.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((host) => +host?.ID === +original.ID),
|
||||
})
|
||||
({ original, value, onClickLabel, ...props }) => {
|
||||
const [update] = useUpdateHostMutation()
|
||||
|
||||
const memoHost = useMemo(() => state ?? original, [state, original])
|
||||
const {
|
||||
data: hosts,
|
||||
error,
|
||||
isLoading,
|
||||
} = hostApi.endpoints.getHosts.useQuery(undefined)
|
||||
|
||||
return <HostCard host={memoHost} rootProps={props} />
|
||||
const host = useMemo(
|
||||
() => hosts?.find((h) => +h.ID === +original.ID) ?? original,
|
||||
[hosts, original]
|
||||
)
|
||||
|
||||
const memoHost = useMemo(
|
||||
() => host ?? original,
|
||||
[host, original, update, isLoading, error, hosts]
|
||||
)
|
||||
|
||||
const handleDeleteLabel = useCallback(
|
||||
(label) => {
|
||||
const currentLabels = memoHost?.TEMPLATE?.LABELS.split(',')
|
||||
const newLabels = currentLabels.filter((l) => l !== label).join(',')
|
||||
const newHostTemplate = { ...memoHost.TEMPLATE, LABELS: newLabels }
|
||||
const templateXml = jsonToXml(newHostTemplate)
|
||||
|
||||
update({ id: original.ID, template: templateXml, replace: 0 })
|
||||
},
|
||||
[memoHost.TEMPLATE?.LABELS, update]
|
||||
)
|
||||
|
||||
return (
|
||||
<HostCard
|
||||
host={memoHost}
|
||||
rootProps={props}
|
||||
onClickLabel={onClickLabel}
|
||||
onDeleteLabel={handleDeleteLabel}
|
||||
/>
|
||||
)
|
||||
},
|
||||
(prev, next) => prev.className === next.className
|
||||
)
|
||||
@ -38,6 +69,7 @@ Row.propTypes = {
|
||||
isSelected: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
handleClick: PropTypes.func,
|
||||
onClickLabel: PropTypes.func,
|
||||
}
|
||||
|
||||
Row.displayName = 'HostRow'
|
||||
|
@ -16,13 +16,14 @@
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { CategoryFilter } from 'client/components/Tables/Enhanced/Utils'
|
||||
import * as ImageModel from 'client/models/Image'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const getTotalOfResources = (resources) =>
|
||||
[resources?.ID ?? []].flat().length || 0
|
||||
|
||||
export default [
|
||||
{ Header: 'ID', accessor: 'ID', sortType: 'number' },
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
const COLUMNS = [
|
||||
{ Header: 'ID', id: 'id', accessor: 'ID', sortType: 'number' },
|
||||
{ Header: 'Name', id: 'name', accessor: 'NAME' },
|
||||
{ Header: 'Owner', accessor: 'UNAME' },
|
||||
{ Header: 'Group', accessor: 'GNAME' },
|
||||
{ Header: 'Locked', id: 'locked', accessor: 'LOCK' },
|
||||
@ -39,6 +40,12 @@ export default [
|
||||
}),
|
||||
filter: 'includesValue',
|
||||
},
|
||||
{
|
||||
Header: T.Label,
|
||||
id: 'label',
|
||||
accessor: 'TEMPLATE.LABELS',
|
||||
filter: 'includesSome',
|
||||
},
|
||||
{
|
||||
Header: 'Type',
|
||||
id: 'TYPE',
|
||||
@ -64,3 +71,6 @@ export default [
|
||||
sortType: 'number',
|
||||
},
|
||||
]
|
||||
|
||||
COLUMNS.noFilterIds = ['id', 'name', 'type', 'label']
|
||||
export default COLUMNS
|
||||
|
@ -15,6 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import PropTypes from 'prop-types'
|
||||
import { useMemo, useCallback } from 'react'
|
||||
|
||||
import {
|
||||
Lock,
|
||||
@ -26,19 +27,27 @@ import {
|
||||
Archive as DiskTypeIcon,
|
||||
} from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import imageApi, { useUpdateImageMutation } from 'client/features/OneApi/image'
|
||||
|
||||
import Timer from 'client/components/Timer'
|
||||
import { StatusCircle, StatusChip } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { T } from 'client/constants'
|
||||
import {
|
||||
jsonToXml,
|
||||
getUniqueLabels,
|
||||
getColorFromString,
|
||||
} from 'client/models/Helper'
|
||||
|
||||
import * as ImageModel from 'client/models/Image'
|
||||
import * as Helper from 'client/models/Helper'
|
||||
|
||||
const Row = ({ original, value, ...props }) => {
|
||||
const Row = ({ original, value, onClickLabel, ...props }) => {
|
||||
const [update] = useUpdateImageMutation()
|
||||
const classes = rowStyles()
|
||||
const {
|
||||
ID,
|
||||
id: ID,
|
||||
NAME,
|
||||
UNAME,
|
||||
GNAME,
|
||||
@ -50,14 +59,45 @@ const Row = ({ original, value, ...props }) => {
|
||||
DATASTORE,
|
||||
TOTAL_VMS,
|
||||
RUNNING_VMS,
|
||||
label: LABELS = [],
|
||||
} = value
|
||||
|
||||
const state = imageApi.endpoints.getImages.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((image) => +image.ID === +original.ID),
|
||||
})
|
||||
|
||||
const memoImage = useMemo(() => state ?? original, [state, original])
|
||||
|
||||
const handleDeleteLabel = useCallback(
|
||||
(label) => {
|
||||
const currentLabels = memoImage.TEMPLATE?.LABELS?.split(',')
|
||||
const newLabels = currentLabels.filter((l) => l !== label).join(',')
|
||||
const newImageTemplate = { ...memoImage.TEMPLATE, LABELS: newLabels }
|
||||
const templateXml = jsonToXml(newImageTemplate)
|
||||
|
||||
update({ id: original.ID, template: templateXml, replace: 0 })
|
||||
},
|
||||
[memoImage.TEMPLATE?.LABELS, update]
|
||||
)
|
||||
|
||||
const labels = [...new Set([TYPE])].filter(Boolean)
|
||||
|
||||
const { color: stateColor, name: stateName } = ImageModel.getState(original)
|
||||
|
||||
const time = Helper.timeFromMilliseconds(+REGTIME)
|
||||
|
||||
const multiTagLabels = useMemo(
|
||||
() =>
|
||||
getUniqueLabels(LABELS).map((label) => ({
|
||||
text: label,
|
||||
stateColor: getColorFromString(label),
|
||||
onClick: onClickLabel,
|
||||
onDelete: handleDeleteLabel,
|
||||
})),
|
||||
[LABELS, handleDeleteLabel, onClickLabel]
|
||||
)
|
||||
|
||||
return (
|
||||
<div {...props} data-cy={`image-${ID}`}>
|
||||
<div className={classes.main}>
|
||||
@ -72,6 +112,9 @@ const Row = ({ original, value, ...props }) => {
|
||||
<StatusChip key={label} text={label} />
|
||||
))}
|
||||
</span>
|
||||
<span className={classes.labels}>
|
||||
<MultipleTags tags={multiTagLabels} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${ID}`}</span>
|
||||
@ -126,6 +169,7 @@ Row.propTypes = {
|
||||
value: PropTypes.object,
|
||||
isSelected: PropTypes.bool,
|
||||
handleClick: PropTypes.func,
|
||||
onClickLabel: PropTypes.func,
|
||||
}
|
||||
|
||||
export default Row
|
||||
|
@ -14,12 +14,13 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { T } from 'client/constants'
|
||||
const getTotalOfResources = (resources) =>
|
||||
[resources?.ID ?? []].flat().length || 0
|
||||
|
||||
export default [
|
||||
{ Header: 'ID', accessor: 'ID', sortType: 'number' },
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
const COLUMNS = [
|
||||
{ Header: 'ID', accessor: 'ID', id: 'id', sortType: 'number' },
|
||||
{ Header: 'Name', id: 'name', accessor: 'NAME' },
|
||||
{ Header: 'Owner', accessor: 'UNAME' },
|
||||
{ Header: 'Group', accessor: 'GNAME' },
|
||||
{
|
||||
@ -28,6 +29,12 @@ export default [
|
||||
accessor: (row) => getTotalOfResources(row?.UPDATED_VMS),
|
||||
sortType: 'number',
|
||||
},
|
||||
{
|
||||
Header: T.Label,
|
||||
id: 'label',
|
||||
accessor: 'TEMPLATE.LABELS',
|
||||
filter: 'includesSome',
|
||||
},
|
||||
{
|
||||
Header: 'Outdated VMs',
|
||||
id: 'OUTDATED_VMS',
|
||||
@ -47,3 +54,6 @@ export default [
|
||||
sortType: 'number',
|
||||
},
|
||||
]
|
||||
|
||||
COLUMNS.noFilterIds = ['id', 'name', 'label']
|
||||
export default COLUMNS
|
||||
|
@ -13,20 +13,56 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import { memo, useMemo, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import secGroupApi from 'client/features/OneApi/securityGroup'
|
||||
import secGroupApi, {
|
||||
useUpdateSecGroupMutation,
|
||||
} from 'client/features/OneApi/securityGroup'
|
||||
import { SecurityGroupCard } from 'client/components/Cards'
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
|
||||
const Row = memo(
|
||||
({ original, value, ...props }) => {
|
||||
const state = secGroupApi.endpoints.getSecGroups.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((secgroup) => +secgroup.ID === +original.ID),
|
||||
})
|
||||
({ original, value, onClickLabel, ...props }) => {
|
||||
const [update] = useUpdateSecGroupMutation()
|
||||
|
||||
const {
|
||||
data: secgroups,
|
||||
error,
|
||||
isLoading,
|
||||
} = secGroupApi.endpoints.getSecGroups.useQuery(undefined)
|
||||
|
||||
const secGroup = useMemo(
|
||||
() => secgroups?.find((sg) => +sg.ID === +original.ID) ?? original,
|
||||
[secgroups, original]
|
||||
)
|
||||
|
||||
const memoSecGroup = useMemo(
|
||||
() => secGroup ?? original,
|
||||
[secGroup, original, update, isLoading, error, secgroups]
|
||||
)
|
||||
|
||||
const handleDeleteLabel = useCallback(
|
||||
(label) => {
|
||||
const currentLabels = memoSecGroup.TEMPLATE?.LABELS?.split(',')
|
||||
const newLabels = currentLabels.filter((l) => l !== label).join(',')
|
||||
const newSecGroupTemplate = {
|
||||
...memoSecGroup.TEMPLATE,
|
||||
LABELS: newLabels,
|
||||
}
|
||||
const templateXml = jsonToXml(newSecGroupTemplate)
|
||||
|
||||
update({ id: original.ID, template: templateXml, replace: 0 })
|
||||
},
|
||||
[memoSecGroup.TEMPLATE?.LABELS, update]
|
||||
)
|
||||
|
||||
return (
|
||||
<SecurityGroupCard securityGroup={state ?? original} rootProps={props} />
|
||||
<SecurityGroupCard
|
||||
securityGroup={memoSecGroup}
|
||||
rootProps={props}
|
||||
onClickLabel={onClickLabel}
|
||||
onDeleteLabel={handleDeleteLabel}
|
||||
/>
|
||||
)
|
||||
},
|
||||
(prev, next) => prev.className === next.className
|
||||
@ -37,6 +73,7 @@ Row.propTypes = {
|
||||
value: PropTypes.object,
|
||||
isSelected: PropTypes.bool,
|
||||
handleClick: PropTypes.func,
|
||||
onClickLabel: PropTypes.func,
|
||||
}
|
||||
|
||||
Row.displayName = 'SecurityGroupRow'
|
||||
|
@ -27,6 +27,12 @@ const COLUMNS = [
|
||||
{ Header: T.Group, id: 'group', accessor: 'GNAME' },
|
||||
{ Header: T.Locked, id: 'locked', accessor: 'LOCK' },
|
||||
{ Header: T.Driver, id: 'vn_mad', accessor: getVNManager },
|
||||
{
|
||||
Header: T.Label,
|
||||
id: 'label',
|
||||
accessor: 'TEMPLATE.LABELS',
|
||||
filter: 'includesSome',
|
||||
},
|
||||
{
|
||||
Header: T.UsedLeases,
|
||||
id: 'used_leases',
|
||||
|
@ -13,26 +13,49 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useMemo } from 'react'
|
||||
import { memo, useMemo, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
|
||||
import api from 'client/features/OneApi/network'
|
||||
import api, { useUpdateVNetMutation } from 'client/features/OneApi/network'
|
||||
import { NetworkCard } from 'client/components/Cards'
|
||||
|
||||
const Row = memo(
|
||||
({ original, value, onClickLabel, ...props }) => {
|
||||
const state = api.endpoints.getVNetworks.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((network) => +network.ID === +original.ID),
|
||||
})
|
||||
const [update] = useUpdateVNetMutation()
|
||||
const {
|
||||
data: vnetworks,
|
||||
error,
|
||||
isLoading,
|
||||
} = api.endpoints.getVNetworks.useQuery(undefined)
|
||||
|
||||
const memoNetwork = useMemo(() => state ?? original, [state, original])
|
||||
const vnetwork = useMemo(
|
||||
() => vnetworks?.find((vnet) => +vnet.ID === +original.ID) ?? original,
|
||||
[vnetworks, original]
|
||||
)
|
||||
|
||||
const memoNetwork = useMemo(
|
||||
() => vnetwork ?? original,
|
||||
[vnetwork, original, update, isLoading, error, vnetworks]
|
||||
)
|
||||
const handleDeleteLabel = useCallback(
|
||||
(label) => {
|
||||
const currentLabels = memoNetwork.TEMPLATE?.LABELS?.split(',')
|
||||
const newLabels = currentLabels.filter((l) => l !== label).join(',')
|
||||
const newVnetTemplate = { ...memoNetwork.TEMPLATE, LABELS: newLabels }
|
||||
const templateXml = jsonToXml(newVnetTemplate)
|
||||
|
||||
update({ id: original.ID, template: templateXml, replace: 0 })
|
||||
},
|
||||
[memoNetwork.TEMPLATE?.LABELS, update]
|
||||
)
|
||||
|
||||
return (
|
||||
<NetworkCard
|
||||
network={memoNetwork}
|
||||
rootProps={props}
|
||||
onClickLabel={onClickLabel}
|
||||
onDeleteLabel={handleDeleteLabel}
|
||||
/>
|
||||
)
|
||||
},
|
||||
|
@ -24,6 +24,12 @@ const COLUMNS = [
|
||||
{ Header: T.Owner, id: 'owner', accessor: 'UNAME' },
|
||||
{ Header: T.Group, id: 'group', accessor: 'GNAME' },
|
||||
{ Header: T.RegistrationTime, id: 'time', accessor: 'REGTIME' },
|
||||
{
|
||||
Header: T.Label,
|
||||
id: 'label',
|
||||
accessor: 'TEMPLATE.LABELS',
|
||||
filter: 'includesSome',
|
||||
},
|
||||
{ Header: T.Locked, id: 'locked', accessor: 'LOCK' },
|
||||
{ Header: T.Logo, id: 'logo', accessor: 'TEMPLATE.LOGO' },
|
||||
{ Header: T.VirtualRouter, id: 'vrouter', accessor: 'TEMPLATE.VROUTER' },
|
||||
|
@ -30,7 +30,7 @@ module.exports = {
|
||||
ApplyLabels: 'Apply labels',
|
||||
Apply: 'Apply',
|
||||
Label: 'Label',
|
||||
NoLabels: 'NoLabels',
|
||||
NoLabels: 'No labels',
|
||||
All: 'All',
|
||||
On: 'On',
|
||||
ToggleAllSelectedCardsCurrentPage:
|
||||
|
@ -29,7 +29,10 @@ import { BackupsTable } from 'client/components/Tables'
|
||||
import BackupActions from 'client/components/Tables/Backups/actions'
|
||||
import BackupTabs from 'client/components/Tabs/Backup'
|
||||
import { Image, T } from 'client/constants'
|
||||
import { useLazyGetImageQuery } from 'client/features/OneApi/image'
|
||||
import {
|
||||
useLazyGetImageQuery,
|
||||
useUpdateImageMutation,
|
||||
} from 'client/features/OneApi/image'
|
||||
|
||||
/**
|
||||
* Displays a list of Backups with a split pane between the list and selected row(s).
|
||||
@ -50,6 +53,7 @@ function Backups() {
|
||||
<BackupsTable
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
globalActions={actions}
|
||||
useUpdateMutation={useUpdateImageMutation}
|
||||
/>
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -29,7 +29,10 @@ import { ImagesTable } from 'client/components/Tables'
|
||||
import ImageActions from 'client/components/Tables/Images/actions'
|
||||
import ImageTabs from 'client/components/Tabs/Image'
|
||||
import { Image, T } from 'client/constants'
|
||||
import { useLazyGetImageQuery } from 'client/features/OneApi/image'
|
||||
import {
|
||||
useUpdateImageMutation,
|
||||
useLazyGetImageQuery,
|
||||
} from 'client/features/OneApi/image'
|
||||
|
||||
/**
|
||||
* Displays a list of Images with a split pane between the list and selected row(s).
|
||||
@ -50,6 +53,7 @@ function Images() {
|
||||
<ImagesTable
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
globalActions={actions}
|
||||
useUpdateMutation={useUpdateImageMutation}
|
||||
/>
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -21,7 +21,10 @@ import Cancel from 'iconoir-react/dist/Cancel'
|
||||
import { Typography, Box, Stack, Chip } from '@mui/material'
|
||||
import { Row } from 'react-table'
|
||||
|
||||
import { useLazyGetSecGroupQuery } from 'client/features/OneApi/securityGroup'
|
||||
import {
|
||||
useLazyGetSecGroupQuery,
|
||||
useUpdateSecGroupMutation,
|
||||
} from 'client/features/OneApi/securityGroup'
|
||||
import { SecurityGroupsTable } from 'client/components/Tables'
|
||||
import SecurityGroupsActions from 'client/components/Tables/SecurityGroups/actions'
|
||||
import SecurityGroupsTabs from 'client/components/Tabs/SecurityGroup'
|
||||
@ -50,6 +53,7 @@ function SecurityGroups() {
|
||||
<SecurityGroupsTable
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
globalActions={actions}
|
||||
useUpdateMutation={useUpdateSecGroupMutation}
|
||||
/>
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -158,7 +158,10 @@ const datastoreApi = oneApi.injectEndpoints({
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: DATASTORE, id }],
|
||||
invalidatesTags: (_, __, { id }) => [
|
||||
{ type: DATASTORE, id },
|
||||
{ type: DATASTORE_POOL, id },
|
||||
],
|
||||
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const patchDatastore = dispatch(
|
||||
|
@ -31,6 +31,11 @@ import {
|
||||
IMAGE_TYPES_FOR_BACKUPS,
|
||||
} from 'client/constants'
|
||||
import { getType } from 'client/models/Image'
|
||||
import {
|
||||
updateResourceOnPool,
|
||||
removeResourceOnPool,
|
||||
updateTemplateOnResource,
|
||||
} from 'client/features/OneApi/common'
|
||||
|
||||
const { IMAGE } = ONE_RESOURCES
|
||||
const { IMAGE_POOL } = ONE_RESOURCES_POOL
|
||||
@ -214,6 +219,28 @@ const imageApi = oneApi.injectEndpoints({
|
||||
imageApi.util.updateQueryData('getImages', undefined, updateFn),
|
||||
resource: 'IMAGE',
|
||||
}),
|
||||
async onQueryStarted({ id }, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: resourceFromQuery } = await queryFulfilled
|
||||
|
||||
dispatch(
|
||||
imageApi.util.updateQueryData(
|
||||
'getImages',
|
||||
undefined,
|
||||
updateResourceOnPool({ id, resourceFromQuery })
|
||||
)
|
||||
)
|
||||
} catch {
|
||||
// if the query fails, we want to remove the resource from the pool
|
||||
dispatch(
|
||||
imageApi.util.updateQueryData(
|
||||
'getImages',
|
||||
undefined,
|
||||
removeResourceOnPool({ id })
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
}),
|
||||
allocateImage: builder.mutation({
|
||||
/**
|
||||
@ -383,7 +410,34 @@ const imageApi = oneApi.injectEndpoints({
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: IMAGE, id }],
|
||||
invalidatesTags: (_, __, { id }) => [
|
||||
{ type: IMAGE, id },
|
||||
{ type: IMAGE_POOL, id },
|
||||
],
|
||||
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const patchImage = dispatch(
|
||||
imageApi.util.updateQueryData(
|
||||
'getImage',
|
||||
{ id: params.id },
|
||||
updateTemplateOnResource(params)
|
||||
)
|
||||
)
|
||||
|
||||
const patchImages = dispatch(
|
||||
imageApi.util.updateQueryData(
|
||||
'getImages',
|
||||
undefined,
|
||||
updateTemplateOnResource(params)
|
||||
)
|
||||
)
|
||||
|
||||
queryFulfilled.catch(() => {
|
||||
patchImage.undo()
|
||||
patchImages.undo()
|
||||
})
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
changeImagePermissions: builder.mutation({
|
||||
/**
|
||||
@ -598,3 +652,5 @@ export const {
|
||||
useUploadImageMutation,
|
||||
useRestoreBackupMutation,
|
||||
} = imageApi
|
||||
|
||||
export default imageApi
|
||||
|
@ -304,7 +304,10 @@ const vNetworkApi = oneApi.injectEndpoints({
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VNET, id }],
|
||||
invalidatesTags: (_, __, { id }) => [
|
||||
{ type: VNET, id },
|
||||
{ type: VNET_POOL, id },
|
||||
],
|
||||
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const patchVNet = dispatch(
|
||||
|
@ -19,6 +19,10 @@ import {
|
||||
ONE_RESOURCES,
|
||||
ONE_RESOURCES_POOL,
|
||||
} from 'client/features/OneApi'
|
||||
import {
|
||||
removeResourceOnPool,
|
||||
updateResourceOnPool,
|
||||
} from 'client/features/OneApi/common'
|
||||
import { FilterFlag, Permission } from 'client/constants'
|
||||
|
||||
const { SECURITYGROUP } = ONE_RESOURCES
|
||||
@ -74,6 +78,26 @@ const securityGroupApi = oneApi.injectEndpoints({
|
||||
},
|
||||
transformResponse: (data) => data?.SECURITY_GROUP ?? {},
|
||||
providesTags: (_, __, { id }) => [{ type: SECURITYGROUP, id }],
|
||||
async onQueryStarted({ id }, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: resourceFromQuery } = await queryFulfilled
|
||||
dispatch(
|
||||
securityGroupApi.util.updateQueryData(
|
||||
'getSecGroups',
|
||||
undefined,
|
||||
updateResourceOnPool({ id, resourceFromQuery })
|
||||
)
|
||||
)
|
||||
} catch {
|
||||
dispatch(
|
||||
securityGroupApi.util.updateQueryData(
|
||||
'getSecGroups',
|
||||
undefined,
|
||||
removeResourceOnPool({ id })
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
}),
|
||||
renameSecGroup: builder.mutation({
|
||||
/**
|
||||
@ -218,7 +242,10 @@ const securityGroupApi = oneApi.injectEndpoints({
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: SECURITYGROUP, id }],
|
||||
invalidatesTags: (_, __, { id }) => [
|
||||
{ type: SECURITYGROUP, id },
|
||||
{ type: SECURITYGROUP_POOL, id },
|
||||
],
|
||||
}),
|
||||
commitSegGroup: builder.mutation({
|
||||
/**
|
||||
|
@ -532,16 +532,27 @@ export const userInputsToObject = (userInputs) =>
|
||||
* @param {string} labels - List of labels separated by comma
|
||||
* @returns {string[]} List of unique labels
|
||||
*/
|
||||
export const getUniqueLabels = (labels) =>
|
||||
labels
|
||||
?.split(',')
|
||||
?.filter(
|
||||
(label, _, list) =>
|
||||
label !== '' && !list.some((element) => element.startsWith(`${label}/`))
|
||||
)
|
||||
?.sort((a, b) =>
|
||||
a.localeCompare(b, undefined, { numeric: true, ignorePunctuation: true })
|
||||
) ?? []
|
||||
export const getUniqueLabels = (labels) => {
|
||||
if (labels?.length < 1) {
|
||||
return []
|
||||
}
|
||||
|
||||
return (
|
||||
labels
|
||||
?.split(',')
|
||||
?.filter(
|
||||
(label, _, list) =>
|
||||
label !== '' &&
|
||||
!list.some((element) => element.startsWith(`${label}/`))
|
||||
)
|
||||
?.sort((a, b) =>
|
||||
a.localeCompare(b, undefined, {
|
||||
numeric: true,
|
||||
ignorePunctuation: true,
|
||||
})
|
||||
) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
// The number 16,777,215 is the total possible combinations
|
||||
// of RGB(255,255,255) which is 32 bit color
|
||||
|
Loading…
x
Reference in New Issue
Block a user