diff --git a/src/containers/Cluster/Cluster.tsx b/src/containers/Cluster/Cluster.tsx index a72d9fcfdf..1628166bec 100644 --- a/src/containers/Cluster/Cluster.tsx +++ b/src/containers/Cluster/Cluster.tsx @@ -28,6 +28,7 @@ import type { AdditionalTenantsProps, } from '../../types/additionalProps'; import {EFlag} from '../../types/api/enums'; +import {uiFactory} from '../../uiFactory/uiFactory'; import {cn} from '../../utils/cn'; import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../utils/hooks'; import {useAppTitle} from '../App/AppTitleContext'; @@ -39,7 +40,13 @@ import {VersionsContainer} from '../Versions/Versions'; import {ClusterOverview} from './ClusterOverview/ClusterOverview'; import type {ClusterTab} from './utils'; -import {clusterTabs, clusterTabsIds, getClusterPath, isClusterTab} from './utils'; +import { + clusterTabs, + clusterTabsIds, + getClusterPath, + isClusterTab, + useShouldShowEventsTab, +} from './utils'; import './Cluster.scss'; @@ -60,6 +67,7 @@ export function Cluster({ const isClusterDashboardAvailable = useClusterDashboardAvailable(); const shouldShowNetworkTable = useShouldShowClusterNetworkTable(); + const shouldShowEventsTab = useShouldShowEventsTab(); const [autoRefreshInterval] = useAutoRefreshInterval(); @@ -99,12 +107,17 @@ export function Cluster({ }, [dispatch]); const actualClusterTabs = React.useMemo(() => { - if (shouldShowNetworkTable) { - return clusterTabs; - } else { - return clusterTabs.filter((tab) => tab.id !== clusterTabsIds.network); + let tabs = clusterTabs; + + if (!shouldShowNetworkTable) { + tabs = tabs.filter((tab) => tab.id !== clusterTabsIds.network); } - }, [shouldShowNetworkTable]); + if (!shouldShowEventsTab) { + tabs = tabs.filter((tab) => tab.id !== clusterTabsIds.events); + } + + return tabs; + }, [shouldShowEventsTab, shouldShowNetworkTable]); const getClusterTitle = () => { if (infoLoading) { @@ -240,6 +253,17 @@ export function Cluster({ > + {shouldShowEventsTab && ( + + {uiFactory.renderEvents?.()} + + )} + ( @@ -257,13 +281,19 @@ function useClusterTab() { const defaultTab = useTypedSelector((state) => state.cluster.defaultClusterTab); const shouldShowNetworkTable = useShouldShowClusterNetworkTable(); + const shouldShowEventsTab = useShouldShowEventsTab(); const match = useRouteMatch<{activeTab: string}>(routes.cluster); const {activeTab: activeTabFromParams} = match?.params || {}; let activeTab: ClusterTab; - if (!shouldShowNetworkTable && activeTabFromParams === clusterTabsIds.network) { + const shouldSwitchFromNetworkToDefault = + !shouldShowNetworkTable && activeTabFromParams === clusterTabsIds.network; + const shouldSwitchFromEventsToDefault = + !shouldShowEventsTab && activeTabFromParams === clusterTabsIds.events; + + if (shouldSwitchFromNetworkToDefault || shouldSwitchFromEventsToDefault) { activeTab = INITIAL_DEFAULT_CLUSTER_TAB; } else if (isClusterTab(activeTabFromParams)) { activeTab = activeTabFromParams; diff --git a/src/containers/Cluster/i18n/en.json b/src/containers/Cluster/i18n/en.json index 57ad7248fa..a78205562b 100644 --- a/src/containers/Cluster/i18n/en.json +++ b/src/containers/Cluster/i18n/en.json @@ -40,5 +40,13 @@ "context_cpu-description": "CPU load is calculated as the cumulative usage across all actor system pools on all nodes in the cluster", "context_memory-description": "Memory usage is the total memory consumed by all nodes in the cluster", "context_storage-description": "Storage usage is a cumulative usage of raw disk space of all media types", - "context_network-description": "Network usage is the average outgoing bandwidth usage across all nodes in the cluster" + "context_network-description": "Network usage is the average outgoing bandwidth usage across all nodes in the cluster", + + "tab_databases": "Databases", + "tab_nodes": "Nodes", + "tab_storage": "Storage", + "tab_network": "Network", + "tab_versions": "Versions", + "tab_tablets": "Tablets", + "tab_events": "Events" } diff --git a/src/containers/Cluster/utils.tsx b/src/containers/Cluster/utils.tsx index 47c8fa77d9..5560aeacc5 100644 --- a/src/containers/Cluster/utils.tsx +++ b/src/containers/Cluster/utils.tsx @@ -1,7 +1,11 @@ import type {CreateHrefOptions} from '../../routes'; import routes, {createHref} from '../../routes'; +import {useClusterEventsAvailable} from '../../store/reducers/capabilities/hooks'; import type {ClusterGroupsStats} from '../../store/reducers/cluster/types'; import type {ValueOf} from '../../types/common'; +import {uiFactory} from '../../uiFactory/uiFactory'; + +import i18n from './i18n'; export const clusterTabsIds = { tenants: 'tenants', @@ -10,36 +14,55 @@ export const clusterTabsIds = { network: 'network', versions: 'versions', tablets: 'tablets', + events: 'events', } as const; export type ClusterTab = ValueOf; const tenants = { id: clusterTabsIds.tenants, - title: 'Databases', + get title() { + return i18n('tab_databases'); + }, }; const nodes = { id: clusterTabsIds.nodes, - title: 'Nodes', + get title() { + return i18n('tab_nodes'); + }, }; const storage = { id: clusterTabsIds.storage, - title: 'Storage', + get title() { + return i18n('tab_storage'); + }, }; const network = { id: clusterTabsIds.network, - title: 'Network', + get title() { + return i18n('tab_network'); + }, }; const versions = { id: clusterTabsIds.versions, - title: 'Versions', + get title() { + return i18n('tab_versions'); + }, }; const tablets = { id: clusterTabsIds.tablets, - title: 'Tablets', + get title() { + return i18n('tab_tablets'); + }, +}; +const events = { + id: clusterTabsIds.events, + get title() { + return i18n('tab_events'); + }, }; -export const clusterTabs = [tenants, nodes, storage, network, tablets, versions]; +export const clusterTabs = [tenants, nodes, storage, network, tablets, versions, events]; export function isClusterTab(tab: any): tab is ClusterTab { return Object.values(clusterTabsIds).includes(tab); @@ -58,3 +81,7 @@ export const getTotalStorageGroupsUsed = (groupStats: ClusterGroupsStats) => { return acc; }, 0); }; + +export function useShouldShowEventsTab() { + return useClusterEventsAvailable() && uiFactory.renderEvents; +} diff --git a/src/store/reducers/capabilities/hooks.ts b/src/store/reducers/capabilities/hooks.ts index 2e58e5a9a8..d19f5820a9 100644 --- a/src/store/reducers/capabilities/hooks.ts +++ b/src/store/reducers/capabilities/hooks.ts @@ -157,3 +157,7 @@ export const useEditClusterFeatureAvailable = () => { export const useDeleteClusterFeatureAvailable = () => { return useGetMetaFeatureVersion('/meta/delete_cluster') >= 1; }; + +export const useClusterEventsAvailable = () => { + return useGetMetaFeatureVersion('/meta/events') >= 1; +}; diff --git a/src/types/api/capabilities.ts b/src/types/api/capabilities.ts index 739bce59b6..bba1c81228 100644 --- a/src/types/api/capabilities.ts +++ b/src/types/api/capabilities.ts @@ -49,4 +49,5 @@ export type MetaCapability = | '/meta/stop_database' | '/meta/create_cluster' | '/meta/update_cluster' - | '/meta/delete_cluster'; + | '/meta/delete_cluster' + | '/meta/events'; diff --git a/src/uiFactory/types.ts b/src/uiFactory/types.ts index 6818a39e84..7785948275 100644 --- a/src/uiFactory/types.ts +++ b/src/uiFactory/types.ts @@ -37,6 +37,8 @@ export interface UIFactory { scrollContainerRef: React.RefObject; }) => React.ReactNode; + renderEvents?: () => React.ReactNode; + healthcheck: { getHealthckechViewTitles: GetHealthcheckViewTitles; getHealthcheckViewsOrder: GetHealthcheckViewsOrder;