diff --git a/packages/forklift-console-plugin/src/modules/Providers/hooks/useProvidersInventoryList.ts b/packages/forklift-console-plugin/src/modules/Providers/hooks/useProvidersInventoryList.ts index 3ad506af0..1507a47f0 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/hooks/useProvidersInventoryList.ts +++ b/packages/forklift-console-plugin/src/modules/Providers/hooks/useProvidersInventoryList.ts @@ -1,7 +1,16 @@ import { useEffect, useRef, useState } from 'react'; -import { ProviderInventory, ProvidersInventoryList } from '@kubev2v/types'; -import { consoleFetchJSON } from '@openshift-console/dynamic-plugin-sdk'; +import { + ProviderInventory, + ProviderModelGroupVersionKind, + ProvidersInventoryList, + V1beta1Provider, +} from '@kubev2v/types'; +import { + consoleFetchJSON, + useFlag, + useK8sWatchResource, +} from '@openshift-console/dynamic-plugin-sdk'; import { getInventoryApiUrl, hasObjectChangedInGivenFields } from '../utils/helpers'; @@ -12,9 +21,11 @@ const INVENTORY_TYPES: string[] = ['openshift', 'openstack', 'ovirt', 'vsphere', /** * Configuration parameters for useProvidersInventoryList hook. * @interface + * @property {string} namespace - namespace for fetching inventory's providers data for. Used only for users with limited namespaces privileges. * @property {number} interval - Polling interval in milliseconds. */ interface UseInventoryParams { + namespace?: string; interval?: number; // Polling interval in milliseconds } @@ -32,15 +43,19 @@ interface UseInventoryResult { } /** - * A React hook to fetch and maintain an up-to-date list of providers' inventory data. + * A React hook to fetch and maintain an up-to-date list of providers' inventory data, belongs to a given namespace or to all namespaces + * (based on the namespace parameter). + * For users with limited namespaces privileges, only the given namespace's providers inventory data are fetched. * It fetches data on mount and then at the specified interval. * * @param {UseInventoryParams} params - Configuration parameters for the hook. + * @param {string} namespace - namespace to fetch providers' inventory data for. if set to null, then fetch for all namespaces. * @param {number} [params.interval=10000] - Interval (in milliseconds) to fetch new data at. * * @returns {UseInventoryResult} result - Contains the inventory data, the loading state, and the error state. */ export const useProvidersInventoryList = ({ + namespace = null, interval = 20000, }: UseInventoryParams): UseInventoryResult => { const [inventory, setInventory] = useState(null); @@ -48,15 +63,23 @@ export const useProvidersInventoryList = ({ const [error, setError] = useState(null); const oldDataRef = useRef(null); const oldErrorRef = useRef(null); + const canList: boolean = useFlag('CAN_LIST_NS'); + const k8Providers: V1beta1Provider[] = !canList ? GetK8sProvidersForNamespace(namespace) : null; useEffect(() => { const fetchData = async () => { try { - const newInventory: ProvidersInventoryList = await consoleFetchJSON( - getInventoryApiUrl(`providers?detail=1`), - ); - - updateInventoryIfChanged(newInventory, DEFAULT_FIELDS_TO_COMPARE); + // Fetch all providers + if (canList) { + const newInventory: ProvidersInventoryList = await consoleFetchJSON( + getInventoryApiUrl(`providers?detail=1`), + ); + updateInventoryIfChanged(newInventory, DEFAULT_FIELDS_TO_COMPARE); + } + // Fetch single namespace's providers + else { + await getAndUpdateInventoryProviders(k8Providers); + } handleError(null); } catch (e) { handleError(e); @@ -67,7 +90,74 @@ export const useProvidersInventoryList = ({ const intervalId = setInterval(fetchData, interval); return () => clearInterval(intervalId); - }, [interval]); + }, [interval, namespace, k8Providers]); + + /** + * For users with limited namespaces privileges, Fetch only the given namespace's k8s providers + * and handle errors if exist. + * + * @param {string} namespace namespace to fetch providers' inventory data for. + * @returns {V1beta1Provider[]} list of providers' k8s data, belongs to the given namespace, or null in case of a failure. + */ + function GetK8sProvidersForNamespace(namespace: string): V1beta1Provider[] { + const [k8sProviders, providersLoaded, providersLoadError] = useK8sWatchResource< + V1beta1Provider[] + >({ + groupVersionKind: ProviderModelGroupVersionKind, + namespaced: true, + isList: true, + namespace, + }); + + if (namespace == null) + // Fetching all namespaces is not accessible for non privileged users + handleError(new Error('namespaceNotAccessible')); + else if (!providersLoaded && providersLoadError) + // In case of any other error, return a message that K8s is not reachable + handleError(new Error('providersK8sNotReachable')); + + return k8sProviders; + } + + /** + * For users with limited namespaces privileges, Fetch only the given namespace's inventory providers + * , handle errors if exist and update the UI if changed. + * + * @param {V1beta1Provider[]} k8Providers providers to fetch inventory data for. + * @returns {void} + */ + const getAndUpdateInventoryProviders = async (k8Providers: V1beta1Provider[]) => { + const newInventory: ProvidersInventoryList = { + openshift: [], + openstack: [], + ovirt: [], + vsphere: [], + ova: [], + }; + + const allPromises = Promise.all( + k8Providers + ?.filter((provider) => provider.status.phase === 'Ready') + .map(async (provider) => { + return await consoleFetchJSON( + getInventoryApiUrl(`providers/${provider.spec.type}/${provider.metadata.uid}`), + ); + }), + ); + + allPromises + .then((newInventoryProviders) => { + newInventoryProviders.map((newInventoryProvider) => + newInventory[newInventoryProvider.type].push(newInventoryProvider), + ); + }) + .catch((error) => { + handleError(error); + }) + .finally(() => { + updateInventoryIfChanged(newInventory, DEFAULT_FIELDS_TO_COMPARE); + }); + }; /** * Handles any errors thrown when trying to fetch the inventory. diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/list/ProvidersListPage.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/list/ProvidersListPage.tsx index af4b8036d..5b32ce842 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/list/ProvidersListPage.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/list/ProvidersListPage.tsx @@ -23,7 +23,12 @@ import modernizeMigration from '../../images/modernizeMigration.svg'; import { findInventoryByID } from '../../utils'; import { InventoryNotReachable } from './components/InventoryNotReachable'; -import { ProvidersAddButton, ProvidersEmptyState } from './components'; +import { + NamespaceNotAccessible, + ProvidersAddButton, + ProvidersEmptyState, + ProvidersK8sNotReachable, +} from './components'; import ProviderRow from './ProviderRow'; import './ProvidersListPage.style.css'; @@ -183,7 +188,7 @@ const ProvidersListPage: React.FC<{ inventory, loading: inventoryLoading, error: inventoryError, - } = useProvidersInventoryList({}); + } = useProvidersInventoryList({ namespace }); const permissions = useGetDeleteAndEditAccessReview({ model: ProviderModel, @@ -220,9 +225,9 @@ const ProvidersListPage: React.FC<{ title={t('Providers')} userSettings={userSettings} alerts={ - !inventoryLoading && inventoryError - ? [] - : undefined + providersLoadError + ? handleK8sError(providersLoaded, providersLoadError) + : handleInventoryErrorTypes(inventoryLoading, inventoryError) } customNoResultsFound={EmptyState} page={1} @@ -239,6 +244,27 @@ const ModernizeMigration = () => ( ); +const handleK8sError = (providersLoaded: boolean, providersLoadError: Error) => { + if (!providersLoaded && providersLoadError) + return []; +}; + +const handleInventoryErrorTypes = (inventoryLoading: boolean, inventoryError: Error) => { + if (!inventoryLoading && inventoryError) { + switch (inventoryError?.message) { + case 'namespaceNotAccessible': + return []; + case 'providersK8sNotReachable': + return []; + case 'inventoryNotReachable': + default: + return []; + } + } else { + return undefined; + } +}; + const EmptyState_: React.FC = ({ AddButton, namespace }) => { const { t } = useForkliftTranslation(); diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/list/components/NamespaceNotAccessible.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/list/components/NamespaceNotAccessible.tsx new file mode 100644 index 000000000..f8a3e929f --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Providers/views/list/components/NamespaceNotAccessible.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Alert, Text, TextContent, TextVariants } from '@patternfly/react-core'; + +export const NamespaceNotAccessible: React.FC = () => { + const { t } = useTranslation(); + return ( + + + + {t( + 'Namespace is not accessible. To troubleshoot, check the Forklift controller pod logs.', + )} + + + + ); +}; + +export default NamespaceNotAccessible; diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/list/components/ProvidersK8sNotReachable.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/list/components/ProvidersK8sNotReachable.tsx new file mode 100644 index 000000000..5d0f0ba56 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Providers/views/list/components/ProvidersK8sNotReachable.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Alert, Text, TextContent, TextVariants } from '@patternfly/react-core'; + +export const ProvidersK8sNotReachable: React.FC = () => { + const { t } = useTranslation(); + return ( + + + + {t( + 'Providers are not accessible. To troubleshoot, check the Forklift controller pod logs.', + )} + + + + ); +}; + +export default ProvidersK8sNotReachable; diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/list/components/index.ts b/packages/forklift-console-plugin/src/modules/Providers/views/list/components/index.ts index 7b4a43165..6b9b28041 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/list/components/index.ts +++ b/packages/forklift-console-plugin/src/modules/Providers/views/list/components/index.ts @@ -3,11 +3,13 @@ export * from './CellProps'; export * from './InventoryCellFactory'; export * from './InventoryNotReachable'; export * from './NamespaceCell'; +export * from './NamespaceNotAccessible'; export * from './OpenshiftNetworkCell'; export * from './ProviderCriticalCondition'; export * from './ProviderLinkCell'; export * from './ProvidersAddButton'; export * from './ProvidersEmptyState'; +export * from './ProvidersK8sNotReachable'; export * from './StatusCell'; export * from './TypeCell'; export * from './URLCell';