diff --git a/cypress/.eslintrc.json b/cypress/.eslintrc.json index e1b561239..9c799a718 100644 --- a/cypress/.eslintrc.json +++ b/cypress/.eslintrc.json @@ -10,7 +10,8 @@ ], "plugins": ["cypress", "chai-friendly"], "rules": { - "cypress/no-force": "error", - "cypress/assertion-before-screenshot": "error" + "chai-friendly/no-unused-expressions": "off", + "cypress/assertion-before-screenshot": "error", + "cypress/no-force": "error" } } diff --git a/cypress/cypress.json b/cypress/cypress.json index 216086fef..45b526567 100644 --- a/cypress/cypress.json +++ b/cypress/cypress.json @@ -1,21 +1,21 @@ { - "viewportWidth": 1920, - "viewportHeight": 1080, + "defaultCommandTimeout": 40000, + "fixturesFolder": false, "integrationFolder": "tests", - "screenshotsFolder": "./gui-test-screenshots/screenshots/", - "videosFolder": "./gui-test-screenshots/videos/", - "video": true, + "pluginsFile": "plugin.js", "reporter": "../node_modules/cypress-multi-reporters", "reporterOptions": { "configFile": "reporter-config.json" }, + "retries": { + "openMode": 0, + "runMode": 1 + }, "screenshotOnRunFailure": true, + "screenshotsFolder": "./gui-test-screenshots/screenshots/", "supportFile": "support.ts", - "pluginsFile": "plugin.js", - "fixturesFolder": false, - "defaultCommandTimeout": 30000, - "retries": { - "runMode": 1, - "openMode": 0 - } + "video": true, + "videosFolder": "./gui-test-screenshots/videos/", + "viewportHeight": 1080, + "viewportWidth": 1920 } diff --git a/locales/en/plugin__kubevirt-plugin.json b/locales/en/plugin__kubevirt-plugin.json index 28e8e37f4..e654ba176 100644 --- a/locales/en/plugin__kubevirt-plugin.json +++ b/locales/en/plugin__kubevirt-plugin.json @@ -104,6 +104,8 @@ "Add tolerations to allow a VirtualMachine to schedule onto Nodes with matching taints.": "Add tolerations to allow a VirtualMachine to schedule onto Nodes with matching taints.", "Add volume": "Add volume", "Adding a bridged network interface to a running VirtualMachine is a Technology Preview feature that allows you to choose between live-migrating the VirtualMachine or restarting it to apply the changes. Enabling this feature requires cluster admin permissions.": "Adding a bridged network interface to a running VirtualMachine is a Technology Preview feature that allows you to choose between live-migrating the VirtualMachine or restarting it to apply the changes. Enabling this feature requires cluster admin permissions.", + "Additional column list": "Additional column list", + "Additional columns": "Additional columns", "Additional disks types and interfaces are available when the VirtualMachine is stopped.": "Additional disks types and interfaces are available when the VirtualMachine is stopped.", "Additional resources": "Additional resources", "Additional statuses": "Additional statuses", @@ -248,6 +250,7 @@ "Cluster administrator permissions are required to enable this feature.": "Cluster administrator permissions are required to enable this feature.", "Cluster scope migrations": "Cluster scope migrations", "Collapse": "Collapse", + "Column management": "Column management", "Completion timeout": "Completion timeout", "CompletionTimeoutPerGiB is the maximum number of seconds per GiB a migration is allowed to take. If a live-migration takes longer to migrate than this value multiplied by the size of the VMI, the migration will be cancelled, unless AllowPostCopy is true. Defaults to 800. ": "CompletionTimeoutPerGiB is the maximum number of seconds per GiB a migration is allowed to take. If a live-migration takes longer to migrate than this value multiplied by the size of the VMI, the migration will be cancelled, unless AllowPostCopy is true. Defaults to 800. ", "Compute-intensive applications": "Compute-intensive applications", @@ -337,6 +340,8 @@ "Decrement": "Decrement", "Dedicated resources": "Dedicated resources", "Default": "Default", + "Default {{resourceKind}} columns": "Default {{resourceKind}} columns", + "Default column list": "Default column list", "Default InstanceType": "Default InstanceType", "Default templates": "Default templates", "Default value for this parameter": "Default value for this parameter", @@ -605,6 +610,7 @@ "Make persistent disk": "Make persistent disk", "Make persistent?": "Make persistent?", "Make sure to have clone permissions in the destination namespace. <2>Learn more <1>": "Make sure to have clone permissions in the destination namespace. <2>Learn more <1>", + "Manage columns": "Manage columns", "Manage source": "Manage source", "Manage source for {{dataSource}}": "Manage source for {{dataSource}}", "Manage SSH keys": "Manage SSH keys", @@ -879,6 +885,7 @@ "Restart": "Restart", "Restart the VirtualMachine to apply changes.": "Restart the VirtualMachine to apply changes.", "Restore": "Restore", + "Restore default columns": "Restore default columns", "Restore is enabled only for offline VirtualMachine.": "Restore is enabled only for offline VirtualMachine.", "Restore snapshot": "Restore snapshot", "Restore template settings": "Restore template settings", @@ -934,6 +941,7 @@ "Select StorageClass": "Select StorageClass", "Select volume to boot from": "Select volume to boot from", "Select workloads that must have all the following expressions.": "Select workloads that must have all the following expressions.", + "Selected columns will appear in the table.": "Selected columns will appear in the table.", "Selected StorageClass is different from StorageClass of the source": "Selected StorageClass is different from StorageClass of the source", "Selected sysprep": "Selected sysprep", "Selector": "Selector", @@ -1060,6 +1068,7 @@ "The following information regarding how the disks are partitioned is provided by the guest agent.": "The following information regarding how the disks are partitioned is provided by the guest agent.", "The following resources will be deleted along with this VirtualMachine. Unchecked items will not be deleted.": "The following resources will be deleted along with this VirtualMachine. Unchecked items will not be deleted.", "The machine type defines the virtual hardware configuration while the operating system name and version refer to the hypervisor.": "The machine type defines the virtual hardware configuration while the operating system name and version refer to the hypervisor.", + "The namespace column is only shown when in \"All projects\"": "The namespace column is only shown when in \"All projects\"", "The preferred VirtualMachine attribute values required to run a given workload.": "The preferred VirtualMachine attribute values required to run a given workload.", "The template has": "The template has", "The virtctl client is a supplemental command-line utility for managing virtualization resources from the command line.": "The virtctl client is a supplemental command-line utility for managing virtualization resources from the command line.", @@ -1258,6 +1267,7 @@ "You can edit the boot order in the <1>{t('Disks tab')}": "You can edit the boot order in the <1>{t('Disks tab')}", "You can host and manage virtualized workloads on the same platform as container-based workloads.": "You can host and manage virtualized workloads on the same platform as container-based workloads.", "You can select the boot source in the <2>Disks tab.": "You can select the boot source in the <2>Disks tab.", + "You can select up to {{MAX_VIEW_COLS}} columns": "You can select up to {{MAX_VIEW_COLS}} columns", "You can use cloud-init to initialize the operating system with a specific configuration when the VirtualMachine is started.": "You can use cloud-init to initialize the operating system with a specific configuration when the VirtualMachine is started.", "You must ensure that the configuration is correct before starting the VirtualMachine.": "You must ensure that the configuration is correct before starting the VirtualMachine.", "You're in view-only mode": "You're in view-only mode" diff --git a/src/utils/components/ColumnManagementModal/ColumnManagement.tsx b/src/utils/components/ColumnManagementModal/ColumnManagement.tsx new file mode 100644 index 000000000..573a03180 --- /dev/null +++ b/src/utils/components/ColumnManagementModal/ColumnManagement.tsx @@ -0,0 +1,47 @@ +import React, { FC } from 'react'; + +import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { isEmpty } from '@kubevirt-utils/utils/utils'; +import { ColumnLayout } from '@openshift-console/dynamic-plugin-sdk'; +import { Button, ToolbarGroup, ToolbarItem, Tooltip } from '@patternfly/react-core'; +import { ColumnsIcon } from '@patternfly/react-icons'; + +import { ColumnManagementModal } from '../ColumnManagementModal/ColumnManagementModal'; +import { useModal } from '../ModalProvider/ModalProvider'; + +type ColumnManagementProps = { + columnLayout: ColumnLayout; + hideColumnManagement?: boolean; +}; + +const ColumnManagement: FC = ({ columnLayout, hideColumnManagement }) => { + const { t } = useKubevirtTranslation(); + const { createModal } = useModal(); + + if (isEmpty(columnLayout) || !columnLayout?.id || hideColumnManagement) { + return null; + } + + return ( + + + + + + + + ); +}; + +export default ColumnManagement; diff --git a/src/utils/components/ColumnManagementModal/ColumnManagementModal.tsx b/src/utils/components/ColumnManagementModal/ColumnManagementModal.tsx new file mode 100644 index 000000000..f6795f772 --- /dev/null +++ b/src/utils/components/ColumnManagementModal/ColumnManagementModal.tsx @@ -0,0 +1,193 @@ +import React, { FC, MouseEventHandler, SyntheticEvent, useState } from 'react'; + +import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import useKubevirtUserSettingsTableColumns from '@kubevirt-utils/hooks/useKubevirtUserSettings/useKubevirtUserSettingsTableColumns'; +import { ColumnLayout } from '@openshift-console/dynamic-plugin-sdk'; +import { + ActionList, + ActionListItem, + Alert, + AlertVariant, + Button, + ButtonVariant, + DataList, + Form, + Modal, + ModalVariant, + Stack, + StackItem, +} from '@patternfly/react-core'; + +import { MAX_VIEW_COLS } from './constants'; +import DataListRow from './DataListRow'; +import { createInputId, getColumnId } from './utils'; + +import './column-management-modal.scss'; + +type ColumnManagementModalProps = { + columnLayout: ColumnLayout; + isOpen: boolean; + onClose: () => void; +}; + +export const ColumnManagementModal: FC = ({ + columnLayout, + isOpen, + onClose, +}) => { + const { t } = useKubevirtTranslation(); + const { columns, id, selectedColumns, showNamespaceOverride, type } = columnLayout; + + const defaultColumns = columns.filter((column) => column.id && !column.additional); + const additionalColumns = columns.filter((column) => column.additional); + + const [_, setActiveColumns, loaded, error] = useKubevirtUserSettingsTableColumns({ + columnManagementID: id, + columns, + }); + + const [checkedColumns, setCheckedColumns] = useState>( + selectedColumns && selectedColumns.size !== 0 + ? new Set(selectedColumns) + : new Set(defaultColumns.map((col) => col.id)), + ); + + const onColumnChange = (checked: boolean, event: SyntheticEvent): void => { + const updatedCheckedColumns = new Set(checkedColumns); + const selectedId = getColumnId(event?.currentTarget?.id); + updatedCheckedColumns.has(selectedId) + ? updatedCheckedColumns.delete(selectedId) + : updatedCheckedColumns.add(selectedId); + setCheckedColumns(updatedCheckedColumns); + }; + + const submit: MouseEventHandler = async (event) => { + event.preventDefault(); + const orderedCheckedColumns = new Set(); + checkedColumns.forEach((ids) => orderedCheckedColumns.add(ids)); + + await setActiveColumns([...orderedCheckedColumns]); + onClose(); + }; + + const areMaxColumnsDisplayed = checkedColumns.size >= MAX_VIEW_COLS; + + const resetColumns = (event: SyntheticEvent): void => { + event.preventDefault(); + const updatedCheckedColumns = new Set(checkedColumns); + defaultColumns.forEach((col) => col.id && updatedCheckedColumns.add(col.id)); + additionalColumns.forEach((col) => updatedCheckedColumns.delete(col.id)); + setCheckedColumns(updatedCheckedColumns); + }; + + return ( + + {error && ( + + + + {error.message} + + + + )} + + + + + + + + + + + + + + + + } + isOpen={isOpen} + onClose={onClose} + position="top" + title={t('Manage columns')} + variant={ModalVariant.medium} + > +
+
+

{t('Selected columns will appear in the table.')}

+
+
+ + {!showNamespaceOverride && + t('The namespace column is only shown when in "All projects"')} + +
+
+
+ + + + {defaultColumns.map((defaultColumn) => ( + + ))} + + + + + + {additionalColumns.map((additionalColumn) => ( + + ))} + + +
+
+
+
+ ); +}; diff --git a/src/utils/components/ColumnManagementModal/DataListRow.tsx b/src/utils/components/ColumnManagementModal/DataListRow.tsx new file mode 100644 index 000000000..eb2ee1485 --- /dev/null +++ b/src/utils/components/ColumnManagementModal/DataListRow.tsx @@ -0,0 +1,58 @@ +import React, { FC, SyntheticEvent } from 'react'; + +import { ManagedColumn } from '@openshift-console/dynamic-plugin-sdk'; +import { + DataListCell, + DataListCheck, + DataListItem, + DataListItemCells, + DataListItemRow, +} from '@patternfly/react-core'; + +import { NAME_COLUMN_ID } from './constants'; + +type DataListRowProps = { + checkedColumns: Set; + column: ManagedColumn; + disableUncheckedRow: boolean; + inputId: string; + onChange: (checked: boolean, event: SyntheticEvent) => void; +}; + +const DataListRow: FC = ({ + checkedColumns, + column, + disableUncheckedRow, + inputId, + onChange, +}) => ( + + + + + + , + ]} + /> + + +); + +export default DataListRow; diff --git a/src/utils/components/ColumnManagementModal/column-management-modal.scss b/src/utils/components/ColumnManagementModal/column-management-modal.scss new file mode 100644 index 000000000..29dd3feb4 --- /dev/null +++ b/src/utils/components/ColumnManagementModal/column-management-modal.scss @@ -0,0 +1,7 @@ +.column-management-modal__action-list { + justify-content: end; +} + +.data-list-row-item label { + cursor: pointer; +} diff --git a/src/utils/components/ColumnManagementModal/constants.ts b/src/utils/components/ColumnManagementModal/constants.ts new file mode 100644 index 000000000..fa7d02ee7 --- /dev/null +++ b/src/utils/components/ColumnManagementModal/constants.ts @@ -0,0 +1,5 @@ +export const MAX_VIEW_COLS = 9; + +export const NAME_COLUMN_ID = 'name'; + +export const DATA_LIST_PREFIX = 'data-list-'; diff --git a/src/utils/components/ColumnManagementModal/utils.ts b/src/utils/components/ColumnManagementModal/utils.ts new file mode 100644 index 000000000..29ce24f70 --- /dev/null +++ b/src/utils/components/ColumnManagementModal/utils.ts @@ -0,0 +1,5 @@ +import { DATA_LIST_PREFIX } from './constants'; + +export const createInputId = (columnId: string) => `${DATA_LIST_PREFIX}${columnId}`; + +export const getColumnId = (inputId: string) => inputId.replace(DATA_LIST_PREFIX, ''); diff --git a/src/utils/components/ListPageFilter/ListPageFilter.tsx b/src/utils/components/ListPageFilter/ListPageFilter.tsx index b8462deae..f7ad85181 100644 --- a/src/utils/components/ListPageFilter/ListPageFilter.tsx +++ b/src/utils/components/ListPageFilter/ListPageFilter.tsx @@ -3,6 +3,7 @@ import React, { FC, useMemo, useState } from 'react'; import useDeepCompareMemoize from '@kubevirt-utils/hooks/useDeepCompareMemoize/useDeepCompareMemoize'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; import { + ColumnLayout, FilterValue, K8sResourceCommon, OnFilterChange, @@ -20,6 +21,8 @@ import { } from '@patternfly/react-core'; import { FilterIcon } from '@patternfly/react-icons'; +import ColumnManagement from '../ColumnManagementModal/ColumnManagement'; + import useListPageFiltersMethods from './hooks/useListPageFiltersMethods'; import { useRowFiltersParameters } from './hooks/useRowFiltersParametersType'; import { useSearchFiltersParameters } from './hooks/useSearchFiltersParameters'; @@ -40,13 +43,22 @@ import { } from './utils'; type ListPageFilterProps = { + columnLayout?: ColumnLayout; data?: K8sResourceCommon[]; + hideColumnManagement?: boolean; loaded?: boolean; onFilterChange?: OnFilterChange; rowFilters?: RowFilter[]; }; -const ListPageFilter: FC = ({ data, loaded, onFilterChange, rowFilters }) => { +const ListPageFilter: FC = ({ + columnLayout, + data, + hideColumnManagement, + loaded, + onFilterChange, + rowFilters, +}) => { const { t } = useKubevirtTranslation(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); @@ -186,6 +198,7 @@ const ListPageFilter: FC = ({ data, loaded, onFilterChange, + ); diff --git a/src/utils/hooks/useKubevirtUserSettings/useKubevirtUserSettings.ts b/src/utils/hooks/useKubevirtUserSettings/useKubevirtUserSettings.ts index 09b0298b4..d4bf7aea4 100644 --- a/src/utils/hooks/useKubevirtUserSettings/useKubevirtUserSettings.ts +++ b/src/utils/hooks/useKubevirtUserSettings/useKubevirtUserSettings.ts @@ -14,12 +14,7 @@ import { } from '@kubevirt-ui/kubevirt-api/kubernetes'; import { DEFAULT_NAMESPACE } from '@kubevirt-utils/constants/constants'; import { isEmpty } from '@kubevirt-utils/utils/utils'; -import { - k8sCreate, - k8sGet, - k8sPatch, - useK8sWatchResource, -} from '@openshift-console/dynamic-plugin-sdk'; +import { k8sCreate, k8sGet, useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; import { KUBEVIRT_USER_SETTINGS_CONFIG_MAP_NAME, @@ -27,11 +22,11 @@ import { userSettingsRoleBinding, } from './utils/const'; import userSettingsInitialState, { UserSettingsState } from './utils/userSettingsInitialState'; -import { parseNestedJSON } from './utils/utils'; +import { parseNestedJSON, patchUserConfigMap } from './utils/utils'; type UseKubevirtUserSettings = ( key?: string, -) => [{ [key: string]: any }, (val: any) => void, boolean, Error]; +) => [{ [key: string]: any }, (val: any) => Promise<{ [key: string]: any }>, boolean, Error]; const useKubevirtUserSettings: UseKubevirtUserSettings = (key) => { const [error, setError] = useState(); @@ -108,29 +103,29 @@ const useKubevirtUserSettings: UseKubevirtUserSettings = (key) => { } }, [userConfigMap, userSettings, userName]); + const pushUserSettingsChanges = async (data, resolve, reject) => { + setLoading(true); + + try { + await patchUserConfigMap(userConfigMap, userName, data); + resolve(key ? data[key] : data); + } catch (apiError) { + setError(apiError); + reject(apiError); + } + + setLoading(false); + }; + const updateUserSetting = (val: any) => { - setUserSettings((prevUserSettings) => { - const updateResource = async (data: { [key: string]: any }) => { - try { - await k8sPatch({ - data: [ - { - op: 'replace', - path: `/data/${userName}`, - value: JSON.stringify(data), - }, - ], - model: ConfigMapModel, - resource: userConfigMap, - }); - } catch (e) { - setError(e); - } - }; + return new Promise((resolve, reject) => { + setUserSettings((prevUserSettings) => { + const data = key ? { ...prevUserSettings, [key]: val } : val; + + pushUserSettingsChanges(data, resolve, reject); - const data = key ? { ...prevUserSettings, [key]: val } : val; - updateResource(data); - return data; + return data; + }); }); }; diff --git a/src/utils/hooks/useKubevirtUserSettings/useKubevirtUserSettingsTableColumns.ts b/src/utils/hooks/useKubevirtUserSettings/useKubevirtUserSettingsTableColumns.ts index 560ac7ce9..c778fb5a4 100644 --- a/src/utils/hooks/useKubevirtUserSettings/useKubevirtUserSettingsTableColumns.ts +++ b/src/utils/hooks/useKubevirtUserSettings/useKubevirtUserSettingsTableColumns.ts @@ -1,15 +1,27 @@ -import { useEffect, useRef } from 'react'; +import { useCallback, useEffect } from 'react'; import { isEqualObject } from '@kubevirt-utils/components/NodeSelectorModal/utils/helpers'; -import { useActiveColumns } from '@openshift-console/dynamic-plugin-sdk'; +import { TableColumn, useActiveColumns } from '@openshift-console/dynamic-plugin-sdk'; import { useUserSettings } from '@openshift-console/dynamic-plugin-sdk-internal'; import useKubevirtUserSettings from './useKubevirtUserSettings'; -const useKubevirtUserSettingsTableColumns = ({ columnManagementID, columns }) => { - const updateOnceFromUserSetting = useRef(null); - const [userColumns, setUserColumns] = useKubevirtUserSettings('columns'); - const [localStorageSettings, setLocalSotrageSettings] = useUserSettings<{ +type UseKubevirtUserSettingsTableColumnsType = (input: { + columnManagementID; + columns; +}) => [ + activeColumns: TableColumn[], + setActiveColumns: (val: any) => void, + loaded: boolean, + error: Error, +]; + +const useKubevirtUserSettingsTableColumns: UseKubevirtUserSettingsTableColumnsType = ({ + columnManagementID, + columns, +}) => { + const [userColumns, setUserColumns, loaded, error] = useKubevirtUserSettings('columns'); + const [localStorageSettings, setLocalStorageSettings] = useUserSettings<{ [key: string]: string; }>('console.tableColumns'); @@ -20,20 +32,18 @@ const useKubevirtUserSettingsTableColumns = ({ columnManagementID, columns }) }); useEffect(() => { - if (userColumns?.[columnManagementID] && !updateOnceFromUserSetting.current) { - setLocalSotrageSettings({ + if (!loaded || error) return; + + if ( + !isEqualObject(userColumns?.[columnManagementID], localStorageSettings?.[columnManagementID]) + ) { + setLocalStorageSettings({ ...localStorageSettings, [columnManagementID]: userColumns?.[columnManagementID], }); - updateOnceFromUserSetting.current = true; } - if ( - !isEqualObject( - userColumns?.[columnManagementID], - activeColumns.map((col) => col?.id), - ) - ) { + if (userColumns?.[columnManagementID] === undefined && activeColumns?.length > 0) { setUserColumns?.({ ...userColumns, [columnManagementID]: activeColumns.map((col) => col?.id), @@ -43,12 +53,23 @@ const useKubevirtUserSettingsTableColumns = ({ columnManagementID, columns }) activeColumns, columnManagementID, localStorageSettings, - setLocalSotrageSettings, + setLocalStorageSettings, setUserColumns, userColumns, + loaded, + error, ]); - return [activeColumns]; + const setActiveColumns = useCallback( + (columnIds: string[]) => + setUserColumns?.({ + ...userColumns, + [columnManagementID]: columnIds, + }), + [columnManagementID, setUserColumns, userColumns], + ); + + return [activeColumns, setActiveColumns, loaded, error]; }; export default useKubevirtUserSettingsTableColumns; diff --git a/src/utils/hooks/useKubevirtUserSettings/utils/utils.ts b/src/utils/hooks/useKubevirtUserSettings/utils/utils.ts index f8499ce80..7a309180b 100644 --- a/src/utils/hooks/useKubevirtUserSettings/utils/utils.ts +++ b/src/utils/hooks/useKubevirtUserSettings/utils/utils.ts @@ -1,3 +1,7 @@ +import { ConfigMapModel } from '@kubevirt-ui/kubevirt-api/console'; +import { IoK8sApiCoreV1ConfigMap } from '@kubevirt-ui/kubevirt-api/kubernetes'; +import { k8sPatch } from '@openshift-console/dynamic-plugin-sdk'; + export const parseNestedJSON = (str: string): T => { try { return JSON.parse(str, (_, val) => { @@ -8,3 +12,20 @@ export const parseNestedJSON = (str: string): T => { return (str) as T; } }; + +export const patchUserConfigMap = async ( + userConfigMap: IoK8sApiCoreV1ConfigMap, + userName: string, + data: { [key: string]: any }, +) => + k8sPatch({ + data: [ + { + op: 'replace', + path: `/data/${userName}`, + value: JSON.stringify(data), + }, + ], + model: ConfigMapModel, + resource: userConfigMap, + }); diff --git a/src/views/virtualmachines/list/VirtualMachinesList.tsx b/src/views/virtualmachines/list/VirtualMachinesList.tsx index deb6d7f03..619f96c0c 100644 --- a/src/views/virtualmachines/list/VirtualMachinesList.tsx +++ b/src/views/virtualmachines/list/VirtualMachinesList.tsx @@ -4,6 +4,7 @@ import { VirtualMachineInstanceMigrationModelGroupVersionKind, VirtualMachineInstanceModelGroupVersionKind, VirtualMachineModelGroupVersionKind, + VirtualMachineModelRef, } from '@kubevirt-ui/kubevirt-api/console'; import { V1VirtualMachine, @@ -129,7 +130,7 @@ const VirtualMachinesList: FC = ({ kind, namespace }) })); }; - const [_, activeColumns] = useVirtualMachineColumns(namespace, pagination, data); + const [columns, activeColumns] = useVirtualMachineColumns(namespace, pagination, data); const loaded = vmLoaded && vmiLoaded && vmimsLoaded && isSingleNodeLoaded && !loadingFeatureProxy; @@ -142,6 +143,16 @@ const VirtualMachinesList: FC = ({ kind, namespace })
({ + additional, + id, + title, + })), + id: VirtualMachineModelRef, + selectedColumns: new Set(activeColumns?.map((col) => col?.id)), + type: t('VirtualMachine'), + }} onFilterChange={(...args) => { onFilterChange(...args); setPagination((prevPagination) => ({