diff --git a/packages/core/package.json b/packages/core/package.json index fa7a244d394..35694a22b3e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,6 +13,7 @@ ], "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", + "@tanstack/react-table": "^8.17.3", "clean-deep": "^3.4.0", "embla-carousel-react": "^8.0.0-rc11", "file-saver": "^2.0.5", diff --git a/packages/core/src/components/GlobalStyles/index.tsx b/packages/core/src/components/GlobalStyles/index.tsx index 60db7bad302..b60a85a3c86 100644 --- a/packages/core/src/components/GlobalStyles/index.tsx +++ b/packages/core/src/components/GlobalStyles/index.tsx @@ -156,6 +156,30 @@ const GlobalStyles = createGlobalStyle` } } } + + .kube-table-wrapper { + padding: 0 12px 12px; + table.kube-table { + width: 100%; + } + table.kube-table--max-context { + width: max-content; + } + td.table-cell { + .field-content { + word-break: break-word; + } + .field-label { + display: -webkit-box; + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 3; + line-clamp: 3; + -webkit-box-orient: vertical; + white-space: unset; + } + } + } `; export default GlobalStyles; diff --git a/packages/core/src/containers/Extensions/Management/components/Extensions/Extensions.styles.ts b/packages/core/src/containers/Extensions/Management/components/Extensions/Extensions.styles.ts index 7379e7df519..bfd0a80e4a6 100644 --- a/packages/core/src/containers/Extensions/Management/components/Extensions/Extensions.styles.ts +++ b/packages/core/src/containers/Extensions/Management/components/Extensions/Extensions.styles.ts @@ -14,11 +14,14 @@ export const LoadingWrapper = styled.div` background-color: #fff; `; -export const TableWrapper = styled(Card)` - position: relative; - - & > div { - padding: 0; +export const StyledCard = styled(Card)` + .kube-table-wrapper { + td.table-cell { + .field-label { + -webkit-line-clamp: 1; + line-clamp: 1; + } + } } `; diff --git a/packages/core/src/containers/Extensions/Management/components/Extensions/Extensions.tsx b/packages/core/src/containers/Extensions/Management/components/Extensions/Extensions.tsx index a9e4ddeb412..22a58f62a0a 100644 --- a/packages/core/src/containers/Extensions/Management/components/Extensions/Extensions.tsx +++ b/packages/core/src/containers/Extensions/Management/components/Extensions/Extensions.tsx @@ -6,20 +6,11 @@ import React, { useState, useMemo } from 'react'; import { Link } from 'react-router-dom'; import { debounce, find } from 'lodash'; -import { useDeepCompareEffect } from 'react-use'; +import type { ColumnDef } from '@tanstack/react-table'; import { useDisclosure } from '@kubed/hooks'; -import { LoadingOverlay, Loading } from '@kubed/components'; +import { Loading, DataTable } from '@kubed/components'; -import type { Column } from '@ks-console/shared'; -import { - transformRequestParams, - useDataTable, - TableWithoutHook, - TableToolbar, - TableFooter, - TableSkeleton, - StatusIndicator, -} from '@ks-console/shared'; +import { tableState2Query, useUrlSearchParamsStatus, StatusIndicator } from '@ks-console/shared'; import { EXTENSIONS_PAGE_PATHS } from '../../../../../constants/extension'; import type { FetchKExtensionsParams, FormattedExtension } from '../../../../../stores/extension'; import { @@ -28,11 +19,9 @@ import { // useUpdateInstallPlanMutation, } from '../../../../../stores/extension'; import { useMarketplaceConfigQuery } from '../../../../../stores/marketplace'; -import { ExtensionsEmpty as ExtensionsWithFiltersEmpty } from '../../../components/ExtensionsEmpty'; +import { getExtensionsEmptyProps as getExtensionsEmptyPropsWithFilters } from '../../../components/ExtensionsEmpty'; import { DEBOUNCE_WAIT, - K_EXTENSIONS_QUERY_INITIAL_PARAMS, - EXTENSIONS_TABLE_INITIAL_STATE, InstallModalActionType, EXTENSIONS_EVALUATION_PAGE_LINK, } from '../../constants'; @@ -43,21 +32,14 @@ import { useLocalExtensionStatusItems } from '../../hooks/useLocalExtensionStatu import { ExtensionUninstallConfirmModal } from '../ExtensionUninstallConfirmModal'; import { ExtensionForceUninstallConfirmModal } from '../ExtensionForceUninstallConfirmModal'; import { MarketplaceUserEmpty } from './MarketplaceUserEmpty'; -import { ExtensionsEmpty } from './ExtensionsEmpty'; +import { getExtensionsEmptyProps } from './ExtensionsEmpty'; import { MarketplaceAccount } from './MarketplaceAccount'; import { ExtensionStatus } from './ExtensionStatus'; // import { ListActionButtons } from './ListActionButtons'; import { InstallModal } from '../InstallModal'; -import { LoadingWrapper, TableWrapper, ExtensionField, Icon } from './Extensions.styles'; - -function Extensions() { - const [kExtensionsQueryParams, setKExtensionsQueryParams] = useState< - Pick< - FetchKExtensionsParams, - 'limit' | 'page' | 'q' | 'status' | 'enabled' | 'sortBy' | 'ascending' - > - >(K_EXTENSIONS_QUERY_INITIAL_PARAMS); +import { LoadingWrapper, ExtensionField, Icon, StyledCard } from './Extensions.styles'; +export function Extensions() { const { isLoading: isMarketplaceConfigQueryLoading, isOnline, @@ -69,10 +51,8 @@ function Extensions() { const [currentExtensionName, setCurrentExtensionName] = useState(''); const resetCurrentExtensionName = () => setCurrentExtensionName(''); - const [isManualRefetch, setIsManualRefetch] = useState(false); - const { - localExtensionStatusItems, + // localExtensionStatusItems, setLocalExtensionStatusItems, getLocalExtensionStatusItem, setLocalExtensionStatusItem, @@ -85,18 +65,17 @@ function Extensions() { const extensionUninstallConfirmModal = useDisclosure(); const extensionForceUninstallConfirmModal = useDisclosure(); - const hasFilters = Boolean( - kExtensionsQueryParams.q ?? kExtensionsQueryParams.status ?? kExtensionsQueryParams.enabled, - ); + const { state, setState } = useUrlSearchParamsStatus(['']); + const queryParams: Pick = + useMemo(() => tableState2Query(state), [state]); + const hasFilters = Boolean(queryParams.q ?? queryParams.status ?? queryParams.enabled); const { - isLoading: isExtensionsQueryLoading, - isRefetching, + isFetching: isExtensionsQueryFetching, totalCount, - pageCount, formattedExtensions, refetch: refetchExtensions, } = useKExtensionsQuery({ - params: { isAvailable: true, ...kExtensionsQueryParams }, + params: { isAvailable: true, ...queryParams }, onSuccess: data => { const innerLocalExtensionStatusItems = data.map(({ name, statusState, statusConditions }) => { const localExtensionsStatus = getLocalExtensionStatusItem({ @@ -114,11 +93,8 @@ function Extensions() { }; }); setLocalExtensionStatusItems(innerLocalExtensionStatusItems); - - setIsManualRefetch(false); }, }); - const hasExtensions = totalCount > 0; const currentFormattedExtension = find(formattedExtensions, { name: currentExtensionName }); const enabledWatchExtensions = (() => { @@ -175,36 +151,18 @@ function Extensions() { onSuccess: () => handleUpdateInstallPlanEnabledSuccess(), }); */ - const suggestions = [ - { key: 'q', label: t('NAME') }, - { - key: 'status', - label: t('INSTALLATION_STATUS'), - options: [ - { key: 'notInstalled', label: t('NOT_INSTALLED') }, - { key: 'installed', label: t('INSTALLED') }, - { key: 'installFailed', label: t('INSTALL_FAILED') }, - { key: 'upgradeFailed', label: t('UPDATE_FAILED') }, - { key: 'uninstallFailed', label: t('UNINSTALL_FAILED') }, - ], - }, - { - key: 'enabled', - label: t('ENABLED_STATE'), - options: [ - { key: 'true', label: t('ENABLED') }, - { key: 'false', label: t('DISABLED') }, - ], - }, - ]; - - const columns: Column[] = useMemo( + const columns: ColumnDef[] = useMemo( () => [ { - title: t('NAME'), - field: 'q', - render: (value: string, row) => { - const formattedExtension = row as FormattedExtension; + accessorKey: 'q', + header: t('NAME'), + meta: { + th: { + width: '50%', + }, + }, + cell: ({ row }) => { + const formattedExtension = row.original; const { name, displayIcon, localeDisplayName, localeDescription } = formattedExtension; return ( key === 'status')?.options, - canHide: true, - render: (value, formattedExtension) => { + accessorKey: 'status', + header: t('INSTALLATION_STATUS'), + meta: { + th: { + width: '10%', + }, + }, + enableHiding: true, + cell: ({ row }) => { + const formattedExtension = row.original; const { name } = formattedExtension; const localExtensionsStatus = getLocalExtensionStatusItem({ extensionName: name, @@ -238,18 +201,31 @@ function Extensions() { }, }, { - title: t('VERSION'), - field: 'installedVersion', - canHide: true, - render: value => value ?? '-', + accessorKey: 'installedVersion', + header: t('VERSION'), + meta: { + th: { + width: '10%', + }, + }, + enableHiding: true, + cell: ({ getValue }) => { + const value = getValue(); + return value ?? '-'; + }, }, { - title: t('ENABLED_STATE'), - field: 'enabled', - filterOptions: suggestions.find(({ key }) => key === 'enabled')?.options, - canHide: true, - render: (value, row) => { - const { isEnabled, isDisabled } = row as FormattedExtension; + accessorKey: 'enabled', + header: t('ENABLED_STATE'), + meta: { + th: { + width: '10%', + }, + }, + enableHiding: true, + cell: ({ row }) => { + const formattedExtension = row.original; + const { isEnabled, isDisabled } = formattedExtension; if (isEnabled) { return {t('ENABLED')}; } @@ -260,10 +236,11 @@ function Extensions() { }, }, { - title: t('INSTALLATION_TIME'), - field: 'installTimestamp', - render: (value: string, row) => { - const { displayInstallTime } = row as FormattedExtension; + accessorKey: 'installTimestamp', + header: t('INSTALLATION_TIME'), + cell: ({ row }) => { + const formattedExtension = row.original; + const { displayInstallTime } = formattedExtension; return displayInstallTime ?? '-'; }, }, @@ -308,32 +285,85 @@ function Extensions() { }, }, */ ], - [currentExtensionName, localExtensionStatusItems], + [getLocalExtensionStatusItem], ); const tableData = useMemo(() => formattedExtensions, [JSON.stringify(formattedExtensions)]); - const { instance } = useDataTable({ + + const renderToolbarRight = () => { + if (!(isOnline && formattedMarketplaceConfig)) { + return null; + } + + return ( + { + refetchMarketplaceConfig(); + refetchExtensions(); + }} + /> + ); + }; + + const [baseConfig] = useState(() => + DataTable.getDefaultTableOptions({ + tableName: 'extensions.management', + manual: true, + }), + ); + const table = DataTable.useTable({ + ...baseConfig, columns, + loading: isExtensionsQueryFetching, data: tableData, - manualFilters: true, - manualSortBy: true, - manualPagination: true, - initialState: EXTENSIONS_TABLE_INITIAL_STATE, - pageCount, + rowCount: totalCount, + state, + // autoResetPageIndex: true, + meta: { + ...baseConfig.meta, + refetch: refetchExtensions, + getProps: { + empty: () => { + if (!hasFilters) { + return getExtensionsEmptyProps(); + } + return { + style: { height: 252 }, + title: getExtensionsEmptyPropsWithFilters({ hasFilters }).title, + }; + }, + toolbar: () => ({ toolbarRight: renderToolbarRight() }), + filters: () => ({ + simpleMode: false, + suggestions: [ + { key: 'q', label: t('NAME') }, + { + key: 'status', + label: t('INSTALLATION_STATUS'), + options: [ + { key: 'notInstalled', label: t('NOT_INSTALLED') }, + { key: 'installed', label: t('INSTALLED') }, + { key: 'installFailed', label: t('INSTALL_FAILED') }, + { key: 'upgradeFailed', label: t('UPDATE_FAILED') }, + { key: 'uninstallFailed', label: t('UNINSTALL_FAILED') }, + ], + }, + { + key: 'enabled', + label: t('ENABLED_STATE'), + options: [ + { key: 'true', label: t('ENABLED') }, + { key: 'false', label: t('DISABLED') }, + ], + }, + ], + }), + }, + }, + onParamsChange: setState, }); - const tableState = instance.state; - const { pageSize, pageIndex, filters, sortBy } = tableState; - - useDeepCompareEffect(() => { - const params = transformRequestParams({ - pageSize, - pageIndex, - filters, - // @ts-expect-error - sortBy, - }); - setKExtensionsQueryParams(params); - }, [tableState.pageSize, tableState.pageIndex, tableState.filters, tableState.sortBy]); if (isMarketplaceConfigQueryLoading) { return ( @@ -347,51 +377,11 @@ function Extensions() { return ; } - if (isExtensionsQueryLoading) { - return ; - } - return ( <> - - { - refetchMarketplaceConfig(); - refetchExtensions(); - }} - /> - ) - } - refetch={() => { - setIsManualRefetch(true); - refetchExtensions(); - }} - /> - {!hasExtensions && - (hasFilters ? ( - instance.setAllFilters([])} - /> - ) : ( - - ))} - - - - + + + {extensionInstallModal.isOpen && currentFormattedExtension && ( ); } - -export { Extensions }; diff --git a/packages/core/src/containers/Extensions/Management/components/Extensions/ExtensionsEmpty.styles.ts b/packages/core/src/containers/Extensions/Management/components/Extensions/ExtensionsEmpty.styles.ts index de41a0ddbb2..0b7ff3f46e4 100644 --- a/packages/core/src/containers/Extensions/Management/components/Extensions/ExtensionsEmpty.styles.ts +++ b/packages/core/src/containers/Extensions/Management/components/Extensions/ExtensionsEmpty.styles.ts @@ -3,11 +3,4 @@ * https://github.com/kubesphere/console/blob/master/LICENSE */ -import styled from 'styled-components'; -import { Empty } from '@kubed/components'; - export { EmptyButton } from './shared.styles'; - -export const StyledEmpty = styled(Empty)` - padding: 32px 0 80px; -`; diff --git a/packages/core/src/containers/Extensions/Management/components/Extensions/ExtensionsEmpty.tsx b/packages/core/src/containers/Extensions/Management/components/Extensions/ExtensionsEmpty.tsx index 7e1d67bdac0..70e964d82f2 100644 --- a/packages/core/src/containers/Extensions/Management/components/Extensions/ExtensionsEmpty.tsx +++ b/packages/core/src/containers/Extensions/Management/components/Extensions/ExtensionsEmpty.tsx @@ -4,26 +4,30 @@ */ import React from 'react'; +import type { PropsWithChildren } from 'react'; import { useNavigate } from 'react-router-dom'; +import type { EmptyProps } from '@kubed/components'; import { PlugCircle } from '@kubed/icons'; import { EXTENSIONS_PAGE_PATHS } from '../../../../../constants/extension'; -import { StyledEmpty, EmptyButton } from './ExtensionsEmpty.styles'; +import { EmptyButton } from './ExtensionsEmpty.styles'; -function ExtensionsEmpty() { +function Button() { const navigate = useNavigate(); return ( - } - title={t('SUBSCRIPTION_EXTENSION_NOT_FOUND')} - description={t('SUBSCRIPTION_EXTENSION_NOT_FOUND_DESCRIPTION')} - > - navigate(EXTENSIONS_PAGE_PATHS.marketplace.index)}> - {t('DISCOVER_EXTENSIONS')} - - + navigate(EXTENSIONS_PAGE_PATHS.marketplace.index)}> + {t('DISCOVER_EXTENSIONS')} + ); } -export { ExtensionsEmpty }; +export function getExtensionsEmptyProps(): PropsWithChildren { + return { + style: { paddingBottom: 80 }, + image: , + title: t('SUBSCRIPTION_EXTENSION_NOT_FOUND'), + description: t('SUBSCRIPTION_EXTENSION_NOT_FOUND_DESCRIPTION'), + children: