diff --git a/locales/en/plugin__forklift-console-plugin.json b/locales/en/plugin__forklift-console-plugin.json index 90c3f0cc1..12bbf0334 100644 --- a/locales/en/plugin__forklift-console-plugin.json +++ b/locales/en/plugin__forklift-console-plugin.json @@ -2,7 +2,12 @@ "AddProvider": "Add Provider", "Any": "Any", "Cancel": "Cancel", + "CannotDeleteProvider": "Cannot remove provider", + "ClearAllFilters": "Clear all filters", "Clusters": "Clusters", + "Delete": "Delete", + "DeleteProvider": "Delete Provider", + "EditProvider": "Edit Provider", "False": "False", "FilterByName": "Filter by name", "FilterByNamespace": "Filter by namespace", @@ -10,14 +15,20 @@ "FilterByType": "Filter by type", "FilterByUrl": "Filter by endpoint", "Hosts": "Hosts", + "Loading": "Loading...", "ManageColumns": "Manage columns", "Mappings for VM Import": "Mappings for VM Import", "Name": "Name", "Namespace": "Namespace", - "Networks": "Newtworks", + "Networks": "Networks", + "NoResultsFound": "No results found", + "NoResultsMatchFilter": "No results match the filter criteria. Clear all filters and try again.", "Openshift": "Openshift", "Ovirt": "oVirt", + "PermanentlyDeleteProvider": "Permanently delete provider?", "Plans for VM Import": "Plans for VM Import", + "ProviderNoLongerSelectableAsSource": "{{type}} provider {{name}} will no longer be selectable as a migration source.", + "ProviderNoLongerSelectableAsTarget": "{{type}} provider {{name}} will no longer be selectable as a migration target.", "Providers": "Providers", "Providers for VM Import": "Providers for VM Import", "Ready": "Ready", @@ -25,12 +36,14 @@ "RestoreDefaultColums": "Restore default colums", "Save": "Save", "SelectFilter": "Select Filter", + "SelectMigrationNetwork": "Select migration network", "Status": "Status", "Storage": "Storage", "Success": "Success", "TableColumnManagement": "Table column management", "Type": "Type", "True": "True", + "UnableToRetrieve": "Unable to retrieve data", "Unknown": "Unknown", "Url": "Endpoint", "Virtualization": "Virtualization", diff --git a/src/Providers/ProviderRow.tsx b/src/Providers/ProviderRow.tsx index f3a031f59..62380dbee 100644 --- a/src/Providers/ProviderRow.tsx +++ b/src/Providers/ProviderRow.tsx @@ -1,11 +1,20 @@ -import React from 'react'; +import React, { useState } from 'react'; import { RowProps } from 'src/components/TableView'; import { useTranslation } from 'src/internal/i18n'; import { NAME, NAMESPACE, READY, TYPE, URL } from 'src/utils/constants'; +import { ConfirmModal } from '@app/common/components/ConfirmModal'; +import { ProviderType } from '@app/common/constants'; +import { useDeleteProviderMutation } from '@app/queries'; import { StatusIcon } from '@migtools/lib-ui'; import { ResourceLink } from '@openshift-console/dynamic-plugin-sdk'; -import { Button, Popover } from '@patternfly/react-core'; +import { + Button, + Dropdown, + DropdownItem, + KebabToggle, + Popover, +} from '@patternfly/react-core'; import { Td, Tr } from '@patternfly/react-table'; import { MergedProvider } from './data'; @@ -13,7 +22,6 @@ import { MergedProvider } from './data'; interface CellProps { value: string; entity: MergedProvider; - kind: string; } const StatusCell = ({ value, entity: { conditions } }: CellProps) => { const { t } = useTranslation(); @@ -48,8 +56,8 @@ const StatusCell = ({ value, entity: { conditions } }: CellProps) => { const TextCell = ({ value }: { value: string }) => <>{value}; -const ProviderLink = ({ value, entity, kind }: CellProps) => ( - +const ProviderLink = ({ value, entity }: CellProps) => ( + ); const cellCreator = { @@ -62,21 +70,79 @@ const cellCreator = { ), }; -const ProviderRow = (kind: string) => - function ProviderRow({ columns, entity }: RowProps) { - return ( - - {columns.map(({ id }) => ( - - {cellCreator?.[id]?.({ - kind, - value: entity[id], - entity, - }) ?? } - - ))} - - ); - }; +const ProviderRow = ({ columns, entity }: RowProps) => { + const { t } = useTranslation(); + const [isActionMenuOpen, setIsActionMenuOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + console.warn('Modal open?', isDeleteModalOpen, entity); + const toggleDeleteModal = () => setIsDeleteModalOpen(!isDeleteModalOpen); + const deleteProviderMutation = useDeleteProviderMutation( + entity.type as ProviderType, + toggleDeleteModal, + ); + const editProvider = () => ''; + const selectNetwork = () => ''; + const isTarget = (type: ProviderType) => type !== 'openshift'; + return ( + + {columns.map(({ id }) => ( + + {cellCreator?.[id]?.({ + value: entity[id], + entity, + }) ?? } + + ))} + + setIsActionMenuOpen(!isActionMenuOpen)} + toggle={} + isOpen={isActionMenuOpen} + isPlain + dropdownItems={[ + + {t('EditProvider')} + , + + {t('DeleteProvider')} + , + + {t('SelectMigrationNetwork')} + , + ]} + /> + + deleteProviderMutation.mutate({ + metadata: { + name: entity.name, + namespace: entity.namespace, + }, + spec: { type: entity.type as ProviderType }, + kind: '', + apiVersion: '', + }) + } + mutateResult={deleteProviderMutation} + title={t('PermanentlyDeleteProvider')} + body={t( + isTarget(entity.type as ProviderType) + ? 'ProviderNoLongerSelectableAsTarget' + : 'ProviderNoLongerSelectableAsSource', + { type: entity.type, name: entity.name }, + )} + confirmButtonText={t('Delete')} + errorText={t('CannotDeleteProvider')} + cancelButtonText={t('Cancel')} + /> + + + ); +}; export default ProviderRow; diff --git a/src/Providers/ProvidersPage.tsx b/src/Providers/ProvidersPage.tsx index 5b9102e8b..603ebb3da 100644 --- a/src/Providers/ProvidersPage.tsx +++ b/src/Providers/ProvidersPage.tsx @@ -22,17 +22,24 @@ import { VM_COUNT, } from 'src/utils/constants'; +import { RedExclamationCircleIcon } from '@openshift-console/dynamic-plugin-sdk'; import { Button, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + EmptyStatePrimary, Level, LevelItem, PageSection, + Spinner, Title, Toolbar, ToolbarContent, ToolbarToggleGroup, } from '@patternfly/react-core'; import { FilterIcon } from '@patternfly/react-icons'; +import { SearchIcon } from '@patternfly/react-icons'; import { MergedProvider, useProvidersWithInventory } from './data'; import ProviderRow from './ProviderRow'; @@ -155,10 +162,15 @@ export const ProvidersPage = ({ namespace, kind }: ProvidersPageProps) => { namespace, }); const [selectedFilters, setSelectedFilters] = useState({}); + const clearAllFilters = () => setSelectedFilters({}); const [fields, setFields] = useFields(namespace, fieldsMetadata); console.error('Providers', providers, fields, namespace, kind); + const filteredProviders = providers.filter( + createMetaMatcher(selectedFilters, fields), + ); + return ( <> @@ -174,7 +186,10 @@ export const ProvidersPage = ({ namespace, kind }: ProvidersPageProps) => { - setSelectedFilters({})}> + } breakpoint="xl"> { - - {loaded && error && } - {!loaded && } - {loaded && !error && ( - - entities={providers.filter( - createMetaMatcher(selectedFilters, fields), - )} - allColumns={fields} - visibleColumns={fields.filter(({ isVisible }) => isVisible)} - aria-label={t('Providers')} - Row={ProviderRow(kind)} - /> - )} + + entities={filteredProviders} + allColumns={fields} + visibleColumns={fields.filter(({ isVisible }) => isVisible)} + aria-label={t('Providers')} + Row={ProviderRow} + > + {[ + !loaded && , + loaded && error && , + loaded && !error && providers.length == 0 && , + loaded && + !error && + filteredProviders.length === 0 && + providers.length > 0 && ( + + ), + ].filter(Boolean)} + ); }; -const Errors = () => <> Erorrs!; +const ErrorState = () => { + const { t } = useTranslation(); + return ( + + + + {t('UnableToRetrieve')} + + + ); +}; -const Loading = () => <> Loading!; +const Loading = () => { + const { t } = useTranslation(); + return ( + + + + {t('Loading')} + + + ); +}; + +const NoResultsFound = () => { + const { t } = useTranslation(); + return ( + + + + {t('NoResultsFound')} + + + ); +}; + +const NoResultsMatchFilter = ({ + clearAllFilters, +}: { + clearAllFilters: () => void; +}) => { + const { t } = useTranslation(); + return ( + + + + {t('NoResultsFound')} + + {t('NoResultsMatchFilter')} + + + + + ); +}; type ProvidersPageProps = { kind: string; diff --git a/src/Providers/data.ts b/src/Providers/data.ts index d94872e86..5a514c5eb 100644 --- a/src/Providers/data.ts +++ b/src/Providers/data.ts @@ -3,6 +3,7 @@ import { ProviderResource } from 'src/internal/k8s'; import { CONNECTED, INVENTORY, + KIND, NAME, NAMESPACE, READY, @@ -36,6 +37,7 @@ interface SupportedConditions { } interface FlattenedProvider { + [KIND]: string; [NAME]: string; [NAMESPACE]: string; [URL]: string; @@ -75,6 +77,7 @@ const mergeData = ( metadata: { name, namespace, uid } = {}, status: { conditions = [] } = {}, spec: { url, type } = {}, + kind, }): [ FlattenedProvider, IVMwareProvider & IRHVProvider & IOpenShiftProvider, @@ -86,6 +89,7 @@ const mergeData = ( url, type, uid, + kind, }, inventory?.[type].find(({ uid: otherUid }) => otherUid === uid) ?? {}, Object.fromEntries( diff --git a/src/components/TableView/TableView.tsx b/src/components/TableView/TableView.tsx index 427d7991d..f694b9445 100644 --- a/src/components/TableView/TableView.tsx +++ b/src/components/TableView/TableView.tsx @@ -1,8 +1,16 @@ -import React from 'react'; +import React, { ReactNode } from 'react'; import { useTranslation } from 'src/internal/i18n'; import { UID } from 'src/utils/constants'; -import { TableComposable, Tbody, Th, Thead, Tr } from '@patternfly/react-table'; +import { Bullseye } from '@patternfly/react-core'; +import { + TableComposable, + Tbody, + Td, + Th, + Thead, + Tr, +} from '@patternfly/react-table'; import { Field } from '../types'; @@ -16,6 +24,7 @@ export function TableView({ entities, 'aria-label': ariaLabel, Row, + children, }: TableViewProps) { const { t } = useTranslation(); @@ -46,13 +55,21 @@ export function TableView({ - {entities.map((entity, index) => ( - - ))} + {children.length > 0 && ( + + + {children} + + + )} + {children.length === 0 && + entities.map((entity, index) => ( + + ))} ); @@ -65,4 +82,5 @@ interface TableViewProps { 'aria-label': string; uidFieldId?: string; Row(props: RowProps): JSX.Element; + children?: ReactNode[]; } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index e4ffe5e46..badd8041e 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -6,6 +6,7 @@ export const VALIDATED = 'validated'; export const TYPE = 'type'; export const URL = 'url'; export const NAMESPACE = 'namespace'; +export const KIND = 'kind'; export const UID = 'uid'; export const CLUSTER_COUNT = 'clusterCount'; export const HOST_COUNT = 'hostCount';