import React from 'react'; import {Magnifier} from '@gravity-ui/icons'; import DataTable from '@gravity-ui/react-data-table'; import {Flex, Icon, Select, Text} from '@gravity-ui/uikit'; import {ResponseError} from '../../components/Errors/ResponseError'; import {ResizeableDataTable} from '../../components/ResizeableDataTable/ResizeableDataTable'; import {Search} from '../../components/Search'; import {TableColumnSetup} from '../../components/TableColumnSetup/TableColumnSetup'; import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout'; import {useEmMetaAvailable} from '../../store/reducers/capabilities/hooks'; import {changeClustersFilters, clustersApi} from '../../store/reducers/clusters/clusters'; import { filterClusters, selectAvailabilityFilter, selectClusterNameFilter, selectGalaxyFilter, selectServiceFilter, selectStatusFilter, selectVersionFilter, } from '../../store/reducers/clusters/selectors'; import type {ClusterAvailability, PreparedCluster} from '../../store/reducers/clusters/types'; import {uiFactory} from '../../uiFactory/uiFactory'; import {DEFAULT_TABLE_SETTINGS, EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../utils/hooks'; import {useSelectedColumns} from '../../utils/hooks/useSelectedColumns'; import {getMinorVersion} from '../../utils/versions'; import {ClusterDrawerHealthcheck} from './ClusterDrawerHealthcheck'; import {CLUSTERS_COLUMNS_WIDTH_LS_KEY, getClustersColumns} from './columns'; import { CLUSTERS_SELECTED_COLUMNS_KEY, COLUMNS_NAMES, COLUMNS_TITLES, DEFAULT_COLUMNS, } from './constants'; import i18n from './i18n'; import {b} from './shared'; import {calculateClusterPath} from './utils'; import './Clusters.scss'; interface ClustersProps { scrollContainerRef: React.RefObject; } export function Clusters({scrollContainerRef}: ClustersProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); const query = clustersApi.useGetClustersListQuery(undefined, { pollingInterval: autoRefreshInterval, }); const dispatch = useTypedDispatch(); const emMetaAvailable = useEmMetaAvailable(); const isEditClusterAvailable = emMetaAvailable && uiFactory.onEditCluster !== undefined; const isDeleteClusterAvailable = emMetaAvailable && uiFactory.onDeleteCluster !== undefined; const clusterName = useTypedSelector(selectClusterNameFilter); const status = useTypedSelector(selectStatusFilter); const service = useTypedSelector(selectServiceFilter); const version = useTypedSelector(selectVersionFilter); const galaxy = useTypedSelector(selectGalaxyFilter); const availability = useTypedSelector(selectAvailabilityFilter); const changeStatus = (value: string[]) => { dispatch(changeClustersFilters({status: value})); }; const changeService = (value: string[]) => { dispatch(changeClustersFilters({service: value})); }; const changeClusterName = (value: string) => { dispatch(changeClustersFilters({clusterName: value})); }; const changeVersion = (value: string[]) => { dispatch(changeClustersFilters({version: value})); }; const changeGalaxy = (value: string[]) => { dispatch(changeClustersFilters({galaxy: value})); }; const changeAvailability = (value: string[]) => { dispatch( changeClustersFilters({ availability: value.filter( (item): item is ClusterAvailability => item === 'available' || item === 'unavailable', ), }), ); }; const [healthcheckClusterName, setHealthcheckClusterName] = React.useState( undefined, ); const handleStatusClick = React.useCallback((row: {name?: string}) => { if (row.name) { setHealthcheckClusterName(row.name); } }, []); const handleDrawerClose = React.useCallback(() => { setHealthcheckClusterName(undefined); }, []); const openCluster = React.useCallback((row: PreparedCluster) => { window.location.assign(calculateClusterPath(row)); }, []); const rawColumns = React.useMemo(() => { return getClustersColumns({ isEditClusterAvailable, isDeleteClusterAvailable, onStatusClick: handleStatusClick, }); }, [isDeleteClusterAvailable, isEditClusterAvailable, handleStatusClick]); const clusters = query.data; const hasUnavailableClusters = clusters?.some( (cluster) => cluster.settings?.unavailable === true, ); const showAvailabilityFilter = hasUnavailableClusters || availability.length !== 1 || availability[0] !== 'available'; const availabilityOptions = [ {value: 'available', content: i18n('value_available')}, {value: 'unavailable', content: i18n('value_unavailable')}, ]; const availabilitySummary = availability.length === availabilityOptions.length ? i18n('controls_select-placeholder') : availabilityOptions.find((option) => option.value === availability[0])?.content; // Check which columns have data across all clusters const {hasAnyStatus, hasAnyService, hasAnyGalaxy} = React.useMemo(() => { if (!clusters || clusters.length === 0) { return {hasAnyStatus: true, hasAnyService: true, hasAnyGalaxy: true}; } return { hasAnyStatus: clusters.some((cluster) => cluster.status), hasAnyService: clusters.some((cluster) => cluster.service), hasAnyGalaxy: clusters.some((cluster) => cluster.galaxy), }; }, [clusters]); const {columnsToShow, columnsToSelect, setColumns, selectedColumnIds} = useSelectedColumns( rawColumns, CLUSTERS_SELECTED_COLUMNS_KEY, COLUMNS_TITLES, DEFAULT_COLUMNS, [COLUMNS_NAMES.TITLE], ); // Filter out empty columns that user has NOT explicitly selected // This hides them from both the table and the column selector const filteredColumnsToSelect = React.useMemo(() => { return columnsToSelect.filter((column) => { // If user explicitly selected this column, always show it if (selectedColumnIds.has(column.id)) { return true; } // Otherwise, hide empty columns if (column.id === COLUMNS_NAMES.STATUS && !hasAnyStatus) { return false; } if (column.id === COLUMNS_NAMES.SERVICE && !hasAnyService) { return false; } if (column.id === COLUMNS_NAMES.GALAXY && !hasAnyGalaxy) { return false; } return true; }); }, [columnsToSelect, selectedColumnIds, hasAnyStatus, hasAnyService, hasAnyGalaxy]); // Filter columns to show based on filtered selector const filteredColumnsToShow = React.useMemo(() => { const allowedColumnIds = new Set(filteredColumnsToSelect.map((col) => col.id)); return columnsToShow.filter((column) => allowedColumnIds.has(column.name)); }, [columnsToShow, filteredColumnsToSelect]); const {servicesToSelect, versions, galaxiesToSelect} = React.useMemo(() => { const clustersServices = new Set(); const uniqVersions = new Set(); const clustersGalaxies = new Set(); const clusterList = clusters ?? []; clusterList.forEach((cluster) => { if (cluster.service) { clustersServices.add(cluster.service); } if (cluster.galaxy) { clustersGalaxies.add(cluster.galaxy); } cluster.cluster?.Versions?.forEach((v) => { uniqVersions.add(getMinorVersion(v)); }); }); return { servicesToSelect: Array.from(clustersServices).map((value) => ({ value, content: value, })), versions: Array.from(uniqVersions).map((value) => ({value, content: value})), galaxiesToSelect: Array.from(clustersGalaxies).map((value) => ({ value, content: value, })), }; }, [clusters]); const filteredClusters = React.useMemo(() => { return filterClusters(clusters ?? [], { clusterName, status, service, version, galaxy, availability, }); }, [clusterName, clusters, service, status, version, galaxy, availability]); const statuses = React.useMemo(() => { return Array.from( new Set( (clusters ?? []).map((cluster) => cluster.status).filter(Boolean), ) as Set, ) .sort() .map((el) => ({value: el, content: el})); }, [clusters]); const renderControls = () => { return ( } onChange={changeClusterName} value={clusterName} width={320} /> {galaxiesToSelect.length > 0 || galaxy.length > 0 ? ( ) : null} {servicesToSelect.length > 0 || service.length > 0 ? ( ) : null} {showAvailabilityFilter ? (