@@ -72,6 +112,9 @@ const Row = ({ original, value, ...props }) => {
))}
+
+
+
{`#${ID}`}
@@ -126,6 +169,7 @@ Row.propTypes = {
value: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func,
+ onClickLabel: PropTypes.func,
}
export default Row
diff --git a/src/fireedge/src/client/components/Tables/SecurityGroups/columns.js b/src/fireedge/src/client/components/Tables/SecurityGroups/columns.js
index 3aa2d4a940..9b4b47a6f6 100644
--- a/src/fireedge/src/client/components/Tables/SecurityGroups/columns.js
+++ b/src/fireedge/src/client/components/Tables/SecurityGroups/columns.js
@@ -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
diff --git a/src/fireedge/src/client/components/Tables/SecurityGroups/row.js b/src/fireedge/src/client/components/Tables/SecurityGroups/row.js
index 73f56da947..a833f82633 100644
--- a/src/fireedge/src/client/components/Tables/SecurityGroups/row.js
+++ b/src/fireedge/src/client/components/Tables/SecurityGroups/row.js
@@ -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 (
-
+
)
},
(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'
diff --git a/src/fireedge/src/client/components/Tables/VNetworks/columns.js b/src/fireedge/src/client/components/Tables/VNetworks/columns.js
index cc9c58783b..045e50cc17 100644
--- a/src/fireedge/src/client/components/Tables/VNetworks/columns.js
+++ b/src/fireedge/src/client/components/Tables/VNetworks/columns.js
@@ -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',
diff --git a/src/fireedge/src/client/components/Tables/VNetworks/row.js b/src/fireedge/src/client/components/Tables/VNetworks/row.js
index 79dde44980..cb988aa875 100644
--- a/src/fireedge/src/client/components/Tables/VNetworks/row.js
+++ b/src/fireedge/src/client/components/Tables/VNetworks/row.js
@@ -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 (
)
},
diff --git a/src/fireedge/src/client/components/Tables/VmTemplates/columns.js b/src/fireedge/src/client/components/Tables/VmTemplates/columns.js
index 4675e9e766..8d4975889d 100644
--- a/src/fireedge/src/client/components/Tables/VmTemplates/columns.js
+++ b/src/fireedge/src/client/components/Tables/VmTemplates/columns.js
@@ -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' },
diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js
index d0e8461d20..77329f6f45 100644
--- a/src/fireedge/src/client/constants/translates.js
+++ b/src/fireedge/src/client/constants/translates.js
@@ -30,7 +30,7 @@ module.exports = {
ApplyLabels: 'Apply labels',
Apply: 'Apply',
Label: 'Label',
- NoLabels: 'NoLabels',
+ NoLabels: 'No labels',
All: 'All',
On: 'On',
ToggleAllSelectedCardsCurrentPage:
diff --git a/src/fireedge/src/client/containers/Backups/index.js b/src/fireedge/src/client/containers/Backups/index.js
index 64af077c27..7a78177285 100644
--- a/src/fireedge/src/client/containers/Backups/index.js
+++ b/src/fireedge/src/client/containers/Backups/index.js
@@ -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() {
{hasSelectedRows && (
diff --git a/src/fireedge/src/client/containers/Images/index.js b/src/fireedge/src/client/containers/Images/index.js
index f523560d6a..4701f69d0f 100644
--- a/src/fireedge/src/client/containers/Images/index.js
+++ b/src/fireedge/src/client/containers/Images/index.js
@@ -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() {
{hasSelectedRows && (
diff --git a/src/fireedge/src/client/containers/SecurityGroups/index.js b/src/fireedge/src/client/containers/SecurityGroups/index.js
index ef9a2435ec..cccbe17e74 100644
--- a/src/fireedge/src/client/containers/SecurityGroups/index.js
+++ b/src/fireedge/src/client/containers/SecurityGroups/index.js
@@ -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() {
{hasSelectedRows && (
diff --git a/src/fireedge/src/client/features/OneApi/datastore.js b/src/fireedge/src/client/features/OneApi/datastore.js
index c0252001cb..40ff703b03 100644
--- a/src/fireedge/src/client/features/OneApi/datastore.js
+++ b/src/fireedge/src/client/features/OneApi/datastore.js
@@ -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(
diff --git a/src/fireedge/src/client/features/OneApi/image.js b/src/fireedge/src/client/features/OneApi/image.js
index 17b5bf6afa..0ecede26ee 100644
--- a/src/fireedge/src/client/features/OneApi/image.js
+++ b/src/fireedge/src/client/features/OneApi/image.js
@@ -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
diff --git a/src/fireedge/src/client/features/OneApi/network.js b/src/fireedge/src/client/features/OneApi/network.js
index 293a583782..17376ad55e 100644
--- a/src/fireedge/src/client/features/OneApi/network.js
+++ b/src/fireedge/src/client/features/OneApi/network.js
@@ -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(
diff --git a/src/fireedge/src/client/features/OneApi/securityGroup.js b/src/fireedge/src/client/features/OneApi/securityGroup.js
index 30ff2bde52..f34a97d58b 100644
--- a/src/fireedge/src/client/features/OneApi/securityGroup.js
+++ b/src/fireedge/src/client/features/OneApi/securityGroup.js
@@ -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({
/**
diff --git a/src/fireedge/src/client/models/Helper.js b/src/fireedge/src/client/models/Helper.js
index 17b3d77027..4732505483 100644
--- a/src/fireedge/src/client/models/Helper.js
+++ b/src/fireedge/src/client/models/Helper.js
@@ -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