diff --git a/src/containers/Nodes/getNodesColumns.tsx b/src/containers/Nodes/getNodesColumns.tsx index dd0cfa6dc..d6ae6fc51 100644 --- a/src/containers/Nodes/getNodesColumns.tsx +++ b/src/containers/Nodes/getNodesColumns.tsx @@ -5,7 +5,10 @@ import {PoolsGraph} from '../../components/PoolsGraph/PoolsGraph'; import {ProgressViewer} from '../../components/ProgressViewer/ProgressViewer'; import {TabletsStatistic} from '../../components/TabletsStatistic'; import {NodeHostWrapper} from '../../components/NodeHostWrapper/NodeHostWrapper'; -import {formatBytesToGigabyte} from '../../utils/dataFormatters/dataFormatters'; +import { + formatBytesToGigabyte, + formatStorageValuesToGb, +} from '../../utils/dataFormatters/dataFormatters'; import type {NodesPreparedEntity} from '../../store/reducers/nodes/types'; import type {GetNodeRefFunc} from '../../types/additionalProps'; import {getLoadSeverityForNode} from '../../store/reducers/tenantOverview/topNodesByLoad/utils'; @@ -23,6 +26,7 @@ const NODES_COLUMNS_IDS = { LoadAverage: 'LoadAverage', Tablets: 'Tablets', TopNodesLoadAverage: 'TopNodesLoadAverage', + TopNodesMemory: 'TopNodesMemory', }; interface GetNodesColumnsProps { @@ -83,6 +87,7 @@ const uptimeColumn: Column = { sortAccessor: ({StartTime}) => StartTime && -StartTime, align: DataTable.RIGHT, width: '110px', + sortable: false, }; const memoryColumn: Column = { @@ -149,6 +154,7 @@ const getTabletsColumn = (tabletsPath?: string): Column => ); }, align: DataTable.LEFT, + sortable: false, }); const topNodesLoadAverageColumn: Column = { @@ -168,6 +174,27 @@ const topNodesLoadAverageColumn: Column = { sortable: false, }; +const topNodesMemoryColumn: Column = { + name: NODES_COLUMNS_IDS.TopNodesMemory, + header: 'Memory', + render: ({row}) => + row.MemoryUsed ? ( + <> + + + ) : ( + '—' + ), + align: DataTable.LEFT, + width: 140, + sortable: false, +}; + export function getNodesColumns({ tabletsPath, getNodeRef, @@ -197,3 +224,17 @@ export function getTopNodesByCpuColumns( ): Column[] { return [cpuColumn, nodeIdColumn, getHostColumn(getNodeRef)]; } + +export function getTopNodesByMemoryColumns({ + tabletsPath, + getNodeRef, +}: GetNodesColumnsProps): Column[] { + return [ + nodeIdColumn, + getHostColumn(getNodeRef, true), + uptimeColumn, + topNodesMemoryColumn, + topNodesLoadAverageColumn, + getTabletsColumn(tabletsPath), + ]; +} diff --git a/src/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss b/src/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss index 4d54d61de..9ccf7c531 100644 --- a/src/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +++ b/src/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss @@ -11,7 +11,6 @@ flex-direction: column; min-width: 300px; - height: max-content; } &__modal { diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx new file mode 100644 index 000000000..b7df68e05 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx @@ -0,0 +1,9 @@ +import {TopNodesByMemory} from './TopNodesByMemory'; + +interface TenantMemoryProps { + path: string; +} + +export function TenantMemory({path}: TenantMemoryProps) { + return ; +} diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx new file mode 100644 index 000000000..266009391 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx @@ -0,0 +1,55 @@ +import {useDispatch} from 'react-redux'; +import {useCallback} from 'react'; + +import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks'; +import { + getTopNodesByMemory, + selectTopNodesByMemory, + setDataWasNotLoaded, +} from '../../../../../store/reducers/tenantOverview/topNodesByMemory/topNodesByMemory'; +import type {AdditionalNodesProps} from '../../../../../types/additionalProps'; +import {getTopNodesByMemoryColumns} from '../../../../Nodes/getNodesColumns'; +import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout'; + +import i18n from '../i18n'; + +interface TopNodesByMemoryProps { + path: string; + additionalNodesProps?: AdditionalNodesProps; +} + +export function TopNodesByMemory({path, additionalNodesProps}: TopNodesByMemoryProps) { + const dispatch = useDispatch(); + + const {wasLoaded, loading, error} = useTypedSelector((state) => state.topNodesByMemory); + const {autorefresh} = useTypedSelector((state) => state.schema); + const topNodes = useTypedSelector(selectTopNodesByMemory); + const columns = getTopNodesByMemoryColumns({ + getNodeRef: additionalNodesProps?.getNodeRef, + }); + + const fetchNodes = useCallback( + (isBackground) => { + if (!isBackground) { + dispatch(setDataWasNotLoaded()); + } + + dispatch(getTopNodesByMemory({tenant: path})); + }, + [dispatch, path], + ); + + useAutofetcher(fetchNodes, [fetchNodes], autorefresh); + + return ( + + ); +} diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss index 9f1b8f1fb..172b9f4de 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss @@ -1,6 +1,9 @@ @import '../../../../styles/mixins.scss'; .tenant-overview { + overflow: auto; + + height: 100%; padding-bottom: 20px; &__loader { @@ -81,6 +84,13 @@ line-height: var(--yc-text-body-2-line-height); } + &__info { + position: sticky; + left: 0; + + width: max-content; + } + &__title { margin-bottom: 10px; diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index e8fd59736..ab32ebb9e 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -16,9 +16,9 @@ import {TenantCpu} from './TenantCpu/TenantCpu'; import {HealthcheckDetails} from './Healthcheck/HealthcheckDetails'; import {MetricsCards, type TenantMetrics} from './MetricsCards/MetricsCards'; import {TenantStorage} from './TenantStorage/TenantStorage'; +import {TenantMemory} from './TenantMemory/TenantMemory'; import {useHealthcheck} from './useHealthcheck'; -import i18n from './i18n'; import './TenantOverview.scss'; const b = cn('tenant-overview'); @@ -127,7 +127,7 @@ export function TenantOverview({ return ; } case TENANT_METRICS_TABS_IDS.memory: { - return i18n('label.under-development'); + return ; } case TENANT_METRICS_TABS_IDS.healthcheck: { return ; @@ -148,19 +148,21 @@ export function TenantOverview({ return (
-
{tenantType}
-
- {renderName()} - {additionalTenantProps?.getMonitoringLink?.(Name, Type)} +
+
{tenantType}
+
+ {renderName()} + {additionalTenantProps?.getMonitoringLink?.(Name, Type)} +
+
- {renderTabContent()}
); diff --git a/src/store/reducers/index.ts b/src/store/reducers/index.ts index 131135630..a5ac0850b 100644 --- a/src/store/reducers/index.ts +++ b/src/store/reducers/index.ts @@ -3,6 +3,7 @@ import {combineReducers} from 'redux'; import nodes from './nodes/nodes'; import {topNodesByLoad} from './tenantOverview/topNodesByLoad/topNodesByLoad'; import {topNodesByCpu} from './tenantOverview/topNodesByCpu/topNodesByCpu'; +import {topNodesByMemory} from './tenantOverview/topNodesByMemory/topNodesByMemory'; import cluster from './cluster/cluster'; import clusterNodes from './clusterNodes/clusterNodes'; import tenant from './tenant/tenant'; @@ -47,6 +48,7 @@ export const rootReducer = { nodes, topNodesByLoad, topNodesByCpu, + topNodesByMemory, cluster, clusterNodes, tenant, diff --git a/src/store/reducers/nodes/types.ts b/src/store/reducers/nodes/types.ts index 13e16ee29..c7d27e252 100644 --- a/src/store/reducers/nodes/types.ts +++ b/src/store/reducers/nodes/types.ts @@ -33,6 +33,7 @@ export interface NodesPreparedEntity { StartTime?: string; Uptime: string; MemoryUsed?: string; + MemoryLimit?: string; PoolStats?: TPoolStats[]; LoadAverage?: number[]; Tablets?: TFullTabletStateInfo[] | TComputeTabletStateInfo[]; diff --git a/src/store/reducers/nodes/utils.ts b/src/store/reducers/nodes/utils.ts index 388207534..b972603e9 100644 --- a/src/store/reducers/nodes/utils.ts +++ b/src/store/reducers/nodes/utils.ts @@ -1,6 +1,7 @@ import type {TComputeInfo, TComputeNodeInfo, TComputeTenantInfo} from '../../../types/api/compute'; import type {TNodesInfo} from '../../../types/api/nodes'; import {calcUptime} from '../../../utils/dataFormatters/dataFormatters'; +import {generateEvaluator} from '../../../utils/generateEvaluator'; import type {NodesHandledResponse, NodesPreparedEntity} from './types'; @@ -64,3 +65,5 @@ export const prepareNodesData = (data: TNodesInfo): NodesHandledResponse => { FoundNodes: Number(data.FoundNodes), }; }; + +export const getLoadSeverityForNode = generateEvaluator(60, 80, ['success', 'warning', 'danger']); diff --git a/src/store/reducers/tenantOverview/topNodesByMemory/topNodesByMemory.ts b/src/store/reducers/tenantOverview/topNodesByMemory/topNodesByMemory.ts new file mode 100644 index 000000000..ddc1ed66f --- /dev/null +++ b/src/store/reducers/tenantOverview/topNodesByMemory/topNodesByMemory.ts @@ -0,0 +1,87 @@ +import type {Reducer} from 'redux'; + +import {TENANT_OVERVIEW_TABLES_LIMIT} from '../../../../utils/constants'; +import {createApiRequest, createRequestActionTypes} from '../../../utils'; +import {prepareNodesData} from '../../nodes/utils'; +import type {NodesApiRequestParams} from '../../nodes/types'; +import type {TopNodesByMemoryAction, TopNodesByMemoryState, TopNodesByMemorySlice} from './types'; + +export const FETCH_TOP_NODES_BY_MEMORY = createRequestActionTypes( + 'topNodesByMemory', + 'FETCH_TOP_NODES_BY_MEMORY', +); +const SET_DATA_WAS_NOT_LOADED = 'topNodesByMemory/SET_DATA_WAS_NOT_LOADED'; + +const initialState = { + loading: false, + wasLoaded: false, +}; + +export const topNodesByMemory: Reducer = ( + state = initialState, + action, +) => { + switch (action.type) { + case FETCH_TOP_NODES_BY_MEMORY.REQUEST: { + return { + ...state, + loading: true, + }; + } + case FETCH_TOP_NODES_BY_MEMORY.SUCCESS: { + return { + ...state, + data: action.data?.Nodes, + loading: false, + wasLoaded: true, + error: undefined, + }; + } + case FETCH_TOP_NODES_BY_MEMORY.FAILURE: { + if (action.error?.isCancelled) { + return state; + } + + return { + ...state, + error: action.error, + loading: false, + }; + } + case SET_DATA_WAS_NOT_LOADED: { + return { + ...state, + wasLoaded: false, + }; + } + default: + return state; + } +}; + +const concurrentId = 'getTopNodeByMemory'; + +export function getTopNodesByMemory({ + type = 'any', + sortOrder = -1, + sortValue = 'Memory', + limit = TENANT_OVERVIEW_TABLES_LIMIT, + ...params +}: NodesApiRequestParams) { + return createApiRequest({ + request: window.api.getNodes( + {type, sortOrder, sortValue, limit, ...params}, + {concurrentId}, + ), + actions: FETCH_TOP_NODES_BY_MEMORY, + dataHandler: prepareNodesData, + }); +} + +export const selectTopNodesByMemory = (state: TopNodesByMemorySlice) => state.topNodesByMemory.data; + +export const setDataWasNotLoaded = () => { + return { + type: SET_DATA_WAS_NOT_LOADED, + } as const; +}; diff --git a/src/store/reducers/tenantOverview/topNodesByMemory/types.ts b/src/store/reducers/tenantOverview/topNodesByMemory/types.ts new file mode 100644 index 000000000..8600042a6 --- /dev/null +++ b/src/store/reducers/tenantOverview/topNodesByMemory/types.ts @@ -0,0 +1,29 @@ +import type {IResponseError} from '../../../../types/api/error'; +import type {ApiRequestAction} from '../../../utils'; +import type {NodesPreparedEntity} from '../../nodes/types'; +import {FETCH_TOP_NODES_BY_MEMORY, setDataWasNotLoaded} from './topNodesByMemory'; + +export interface TopNodesByMemoryState { + loading: boolean; + wasLoaded: boolean; + data?: NodesPreparedEntity[]; + error?: IResponseError; +} + +export interface TopNodesByMemoryHandledResponse { + Nodes?: NodesPreparedEntity[]; +} + +type TopNodesByMemoryApiRequestAction = ApiRequestAction< + typeof FETCH_TOP_NODES_BY_MEMORY, + TopNodesByMemoryHandledResponse, + IResponseError +>; + +export type TopNodesByMemoryAction = + | TopNodesByMemoryApiRequestAction + | ReturnType; + +export interface TopNodesByMemorySlice { + topNodesByMemory: TopNodesByMemoryState; +} diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss index 4880cf8b6..d360880cb 100644 --- a/src/styles/mixins.scss +++ b/src/styles/mixins.scss @@ -177,6 +177,10 @@ .data-table__table-wrapper { padding-bottom: 20px; } + + &_sticky-head_fixed { + overflow: initial; + } } } }