diff --git a/locales/en/plugin__kubevirt-plugin.json b/locales/en/plugin__kubevirt-plugin.json index 240acd3bb8..461352f6e6 100644 --- a/locales/en/plugin__kubevirt-plugin.json +++ b/locales/en/plugin__kubevirt-plugin.json @@ -1287,6 +1287,7 @@ "No Upload URL found {{configError}}": "No Upload URL found {{configError}}", "No username set, see operating system documentation for the default username.": "No username set, see operating system documentation for the default username.", "No valid results found in ConfigMap": "No valid results found in ConfigMap", + "No virtualmachine networks found": "No virtualmachine networks found", "No VirtualMachineClusterInstanceType found": "No VirtualMachineClusterInstanceType found", "No VirtualMachineInstanceTypes found": "No VirtualMachineInstanceTypes found", "No VirtualMachines found": "No VirtualMachines found", @@ -2127,6 +2128,7 @@ "VirtualMachine name field is mandatory": "VirtualMachine name field is mandatory", "VirtualMachine name not valid": "VirtualMachine name not valid", "VirtualMachine name: {{vmName}}": "VirtualMachine name: {{vmName}}", + "VirtualMachine networks": "VirtualMachine networks", "VirtualMachine statuses": "VirtualMachine statuses", "VirtualMachine storage": "VirtualMachine storage", "VirtualMachine Templates": "VirtualMachine Templates", diff --git a/locales/es/plugin__kubevirt-plugin.json b/locales/es/plugin__kubevirt-plugin.json index 46f90a28d4..78df1193ec 100644 --- a/locales/es/plugin__kubevirt-plugin.json +++ b/locales/es/plugin__kubevirt-plugin.json @@ -1303,6 +1303,7 @@ "No Upload URL found {{configError}}": "No se encontró URL de carga {{configError}}", "No username set, see operating system documentation for the default username.": "Sin nombre de usuario establecido, consulte la documentación del sistema operativo para conocer el nombre de usuario predeterminado.", "No valid results found in ConfigMap": "No se encontraron resultados válidos en ConfigMap", + "No virtualmachine networks found": "No virtualmachine networks found", "No VirtualMachineClusterInstanceType found": "No se encontró ningún VirtualMachineClusterInstanceType", "No VirtualMachineInstanceTypes found": "No se encontraron VirtualMachineInstanceTypes", "No VirtualMachines found": "No se encontraron VirtualMachines", @@ -2148,6 +2149,7 @@ "VirtualMachine name field is mandatory": "El campo de nombre de VirtualMachine es obligatorio.", "VirtualMachine name not valid": "El nombre de VirtualMachine no es válido.", "VirtualMachine name: {{vmName}}": "Nombre de VirtualMachine: {{vmName}}", + "VirtualMachine networks": "VirtualMachine networks", "VirtualMachine statuses": "Estados de VirtualMachine", "VirtualMachine storage": "Almacenamiento de VirtualMachine", "VirtualMachine Templates": "Plantillas de VirtualMachine", diff --git a/locales/fr/plugin__kubevirt-plugin.json b/locales/fr/plugin__kubevirt-plugin.json index 9d10dd0a4c..bc432dc686 100644 --- a/locales/fr/plugin__kubevirt-plugin.json +++ b/locales/fr/plugin__kubevirt-plugin.json @@ -1303,6 +1303,7 @@ "No Upload URL found {{configError}}": "Aucune URL de téléchargement trouvée {{configError}}", "No username set, see operating system documentation for the default username.": "Aucun nom d'utilisateur défini, consultez la documentation du système d'exploitation pour le nom d'utilisateur par défaut.", "No valid results found in ConfigMap": "Aucun résultat valide trouvé dans ConfigMap", + "No virtualmachine networks found": "No virtualmachine networks found", "No VirtualMachineClusterInstanceType found": "Aucun VirtualMachineClusterInstanceType trouvé", "No VirtualMachineInstanceTypes found": "Aucun VirtualMachineInstanceTypes trouvé", "No VirtualMachines found": "Aucune machine virtuelle trouvée", @@ -2148,6 +2149,7 @@ "VirtualMachine name field is mandatory": "Le champ Nom de la machine virtuelle est obligatoire", "VirtualMachine name not valid": "Nom de la machine virtuelle non valide", "VirtualMachine name: {{vmName}}": "Nom de la machine virtuelle\u00a0: {{vmName}}", + "VirtualMachine networks": "VirtualMachine networks", "VirtualMachine statuses": "États des machines virtuelles", "VirtualMachine storage": "Stockage de machine virtuelle", "VirtualMachine Templates": "Modèles de machines virtuelles", diff --git a/locales/ja/plugin__kubevirt-plugin.json b/locales/ja/plugin__kubevirt-plugin.json index a6e72ce1fc..8d50c72021 100644 --- a/locales/ja/plugin__kubevirt-plugin.json +++ b/locales/ja/plugin__kubevirt-plugin.json @@ -1281,6 +1281,7 @@ "No Upload URL found {{configError}}": "アップロード URL が見つかりません {{configError}}", "No username set, see operating system documentation for the default username.": "ユーザー名が設定されていません。デフォルトのユーザー名については、オペレーティングシステムのドキュメントを参照してください。", "No valid results found in ConfigMap": "ConfigMap 内に有効な結果が見つかりません", + "No virtualmachine networks found": "No virtualmachine networks found", "No VirtualMachineClusterInstanceType found": "VirtualMachineClusterInstanceType が見つかりません", "No VirtualMachineInstanceTypes found": "VirtualMachineInstanceType が見つかりません", "No VirtualMachines found": "VirtualMachine が見つかりません", @@ -2119,6 +2120,7 @@ "VirtualMachine name field is mandatory": "VirtualMachine 名フィールドは必須です", "VirtualMachine name not valid": "VirtualMachine 名が無効です", "VirtualMachine name: {{vmName}}": "仮想マシン名: {{vmName}}", + "VirtualMachine networks": "VirtualMachine networks", "VirtualMachine statuses": "VirtualMachine のステータス", "VirtualMachine storage": "VirtualMachine ストレージ", "VirtualMachine Templates": "VirtualMachine テンプレート", diff --git a/locales/ko/plugin__kubevirt-plugin.json b/locales/ko/plugin__kubevirt-plugin.json index a3e0ac21c8..44b930478c 100644 --- a/locales/ko/plugin__kubevirt-plugin.json +++ b/locales/ko/plugin__kubevirt-plugin.json @@ -1281,6 +1281,7 @@ "No Upload URL found {{configError}}": "업로드 URL을 찾을 수 없음 {{configError}}", "No username set, see operating system documentation for the default username.": "사용자 이름이 설정되지 않았습니다. 기본 사용자 이름은 운영 체제 설명서를 참조하세요.", "No valid results found in ConfigMap": "ConfigMap에서 유효한 결과를 찾을 수 없음", + "No virtualmachine networks found": "No virtualmachine networks found", "No VirtualMachineClusterInstanceType found": "VirtualMachineClusterInstanceType을 찾을 수 없음", "No VirtualMachineInstanceTypes found": "VirtualMachineInstanceTypes를 찾을 수 없음", "No VirtualMachines found": "VirtualMachines를 찾을 수 없음", @@ -2119,6 +2120,7 @@ "VirtualMachine name field is mandatory": "VirtualMachine 이름 필드는 필수입니다", "VirtualMachine name not valid": "VirtualMachine 이름이 유효하지 않음", "VirtualMachine name: {{vmName}}": "VirtualMachine 이름: {{vmName}}", + "VirtualMachine networks": "VirtualMachine networks", "VirtualMachine statuses": "VirtualMachine 상태", "VirtualMachine storage": "VirtualMachine 스토리지", "VirtualMachine Templates": "VirtualMachine 템플릿", diff --git a/locales/zh/plugin__kubevirt-plugin.json b/locales/zh/plugin__kubevirt-plugin.json index 127cc43650..45bf6237dd 100644 --- a/locales/zh/plugin__kubevirt-plugin.json +++ b/locales/zh/plugin__kubevirt-plugin.json @@ -1281,6 +1281,7 @@ "No Upload URL found {{configError}}": "找到上传 URL {{configError}}", "No username set, see operating system documentation for the default username.": "没有用户名设置,请参阅操作系统文档了解默认用户名。", "No valid results found in ConfigMap": "ConfigMap 中没有找到有效结果", + "No virtualmachine networks found": "No virtualmachine networks found", "No VirtualMachineClusterInstanceType found": "没有找到 VirtualMachineClusterInstanceType", "No VirtualMachineInstanceTypes found": "没有找到 VirtualMachineInstanceTypes", "No VirtualMachines found": "没有找到虚拟机", @@ -2119,6 +2120,7 @@ "VirtualMachine name field is mandatory": "VirtualMachine 名称字段是必需的", "VirtualMachine name not valid": "VirtualMachine 名称无效", "VirtualMachine name: {{vmName}}": "VirtualMachine 名称: {{vmName}}", + "VirtualMachine networks": "VirtualMachine networks", "VirtualMachine statuses": "VirtualMachine 状态", "VirtualMachine storage": "VirtualMachine 存储", "VirtualMachine Templates": "VirtualMachine 模板", diff --git a/src/utils/components/ExpandableProjectList/ExpandableProjectList.tsx b/src/utils/components/ExpandableProjectList/ExpandableProjectList.tsx index b936b8172e..f1ca9b95aa 100644 --- a/src/utils/components/ExpandableProjectList/ExpandableProjectList.tsx +++ b/src/utils/components/ExpandableProjectList/ExpandableProjectList.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState } from 'react'; +import React, { FC, useEffect, useRef, useState } from 'react'; import { modelToGroupVersionKind, NamespaceModel } from '@kubevirt-ui-ext/kubevirt-api/console'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; @@ -23,27 +23,56 @@ const ExpandableProjectList: FC = ({ }) => { const { t } = useKubevirtTranslation(); const [expand, setExpand] = useState(false); + const isFirstRun = useRef(true); - if (!loaded) return ; - - if (isEmpty(projectNames)) return {emptyMessage ?? t('No projects matched')}; - - return ( - <> - {projectNames.slice(0, expand ? projectNames.length : maxItems).map((project) => ( - - ))} - {projectNames.length > maxItems && ( - + // Notify virtualized table to re-measure after expand/collapse so row overlap is avoided + useEffect(() => { + if (isFirstRun.current) { + isFirstRun.current = false; + return; + } + const id = requestAnimationFrame(() => { + requestAnimationFrame(() => { + window.dispatchEvent(new Event('resize')); + }); + }); + return () => cancelAnimationFrame(id); + }, [expand]); + + if (!loaded) { + return ( +
+ +
+ ); + } + + const cellContent = ( +
+ {isEmpty(projectNames) ? ( + {emptyMessage ?? t('No projects matched')} + ) : ( + <> + {projectNames.slice(0, expand ? projectNames.length : maxItems).map((project) => ( + + ))} + {projectNames.length > maxItems && ( + + )} + )} - +
); + + return cellContent; }; export default ExpandableProjectList; diff --git a/src/views/vmnetworks/list/VMNetworkList.tsx b/src/views/vmnetworks/list/VMNetworkList.tsx index 375cf8def2..e6777c927c 100644 --- a/src/views/vmnetworks/list/VMNetworkList.tsx +++ b/src/views/vmnetworks/list/VMNetworkList.tsx @@ -1,47 +1,56 @@ import React, { FC } from 'react'; +import KubevirtTable from '@kubevirt-utils/components/KubevirtTable/KubevirtTable'; import StateHandler from '@kubevirt-utils/components/StateHandler/StateHandler'; +import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { getName } from '@kubevirt-utils/resources/shared'; import { ClusterUserDefinedNetworkKind } from '@kubevirt-utils/resources/udn/types'; import { isEmpty } from '@kubevirt-utils/utils/utils'; import { ListPageBody, ListPageFilter, useListPageFilter, - VirtualizedTable, } from '@openshift-console/dynamic-plugin-sdk'; import useVMNetworks from '../hooks/useVMNetworks'; import LocalnetEmptyState from './components/LocalnetEmptyState/LocalnetEmptyState'; -import VMNetworkRow from './components/VMNetworkRow'; -import useVMNetworkColumns from './hooks/useVMNetworkColumns'; +import useVMNetworkTableColumns from './hooks/useVMNetworkTableColumns'; type VMNetworkListProps = { onCreate: () => void; }; const VMNetworkList: FC = ({ onCreate }) => { + const { t } = useKubevirtTranslation(); const [vmNetworks, loaded, error] = useVMNetworks(); const [data, filteredData, onFilterChange] = useListPageFilter(vmNetworks); - const columns = useVMNetworkColumns(); + const columns = useVMNetworkTableColumns(); + + let body: React.ReactNode = null; + if (isEmpty(data)) { + body = ; + } else { + body = ( + + + + ariaLabel={t('VirtualMachine networks')} + columns={columns} + data={filteredData} + getRowId={(row) => getName(row) ?? ''} + loaded={loaded} + loadError={error} + noFilteredDataEmptyMsg={t('No virtualmachine networks found')} + unfilteredData={data} + /> + + ); + } return ( - {isEmpty(data) ? ( - - ) : ( - - - - columns={columns} - data={filteredData} - loaded={loaded} - loadError={error} - Row={VMNetworkRow} - unfilteredData={data} - /> - - )} + {body} ); }; diff --git a/src/views/vmnetworks/list/hooks/useVMNetworkColumns.tsx b/src/views/vmnetworks/list/hooks/useVMNetworkColumns.tsx index 61c6b0b8a6..ac7ccbb2cb 100644 --- a/src/views/vmnetworks/list/hooks/useVMNetworkColumns.tsx +++ b/src/views/vmnetworks/list/hooks/useVMNetworkColumns.tsx @@ -14,7 +14,7 @@ const sortUDNByMTU = return direction === 'asc' ? result : -result; }; -const useVMNetworkColumns = (): { id: string; title: string }[] => { +const useVMNetworkColumns = (): [TableColumn[], boolean] => { const { t } = useKubevirtTranslation(); const columns: TableColumn[] = useMemo( @@ -56,13 +56,13 @@ const useVMNetworkColumns = (): { id: string; title: string }[] => { [t], ); - const [activeColumns] = useActiveColumns({ + const [activeColumns, userSettingsLoaded] = useActiveColumns({ columnManagementID: 'VMNetworkList', columns, showNamespaceOverride: false, }); - return activeColumns; + return [activeColumns, userSettingsLoaded]; }; export default useVMNetworkColumns; diff --git a/src/views/vmnetworks/list/hooks/useVMNetworkTableColumns.tsx b/src/views/vmnetworks/list/hooks/useVMNetworkTableColumns.tsx new file mode 100644 index 0000000000..c91eb672ef --- /dev/null +++ b/src/views/vmnetworks/list/hooks/useVMNetworkTableColumns.tsx @@ -0,0 +1,70 @@ +import React, { useMemo } from 'react'; +import { Link } from 'react-router-dom-v5-compat'; + +import { ColumnConfig } from '@kubevirt-utils/hooks/useDataViewTableSort/types'; +import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { getName } from '@kubevirt-utils/resources/shared'; +import { getLocalnet, getMTU, getVLANID } from '@kubevirt-utils/resources/udn/selectors'; +import { ClusterUserDefinedNetworkKind } from '@kubevirt-utils/resources/udn/types'; +import { NO_DATA_DASH } from '@kubevirt-utils/resources/vm/utils/constants'; + +import VMNetworkActions from '../../actions/VMNetworkActions'; +import { VM_NETWORKS_PATH } from '../../constants'; +import MatchedProjects from '../components/MatchedProjects'; + +const useVMNetworkTableColumns = (): ColumnConfig[] => { + const { t } = useKubevirtTranslation(); + + return useMemo( + (): ColumnConfig[] => [ + { + getValue: (row) => getName(row) ?? '', + key: 'name', + label: t('Name'), + renderCell: (row) => { + const name = getName(row); + return {name}; + }, + sortable: true, + }, + { + key: 'connected-projects', + label: t('Connected projects'), + renderCell: (row) => , + }, + { + getValue: (row) => getLocalnet(row)?.physicalNetworkName ?? '', + key: 'physicalNetworkName', + label: t('Physical network name'), + renderCell: (row) => getLocalnet(row)?.physicalNetworkName || NO_DATA_DASH, + sortable: true, + }, + { + getValue: (row) => String(getVLANID(row) ?? ''), + key: 'vlanID', + label: t('VLAN ID'), + renderCell: (row) => getVLANID(row) ?? NO_DATA_DASH, + sortable: true, + }, + { + getValue: (row) => getMTU(row) ?? 0, + key: 'mtu', + label: t('MTU'), + renderCell: (row) => { + const mtu = getMTU(row); + return mtu ??
{t('Not available')}
; + }, + sortable: true, + }, + { + key: 'actions', + label: '', + props: { className: 'pf-v6-c-table__action' }, + renderCell: (row) => , + }, + ], + [t], + ); +}; + +export default useVMNetworkTableColumns;