diff --git a/src/components/CellWithPopover/CellWithPopover.scss b/src/components/CellWithPopover/CellWithPopover.scss index 6c0c428a7..aac328a2a 100644 --- a/src/components/CellWithPopover/CellWithPopover.scss +++ b/src/components/CellWithPopover/CellWithPopover.scss @@ -1,8 +1,12 @@ .ydb-cell-with-popover { - display: flex; + display: inline-flex; max-width: 100%; + &_full-width { + display: flex; + } + &__popover { display: inline-block; overflow: hidden; diff --git a/src/components/CellWithPopover/CellWithPopover.tsx b/src/components/CellWithPopover/CellWithPopover.tsx index a2daa0a57..3714cee09 100644 --- a/src/components/CellWithPopover/CellWithPopover.tsx +++ b/src/components/CellWithPopover/CellWithPopover.tsx @@ -22,7 +22,7 @@ export function CellWithPopover({ ...props }: CellWithPopoverProps) { return ( -
+
{ commonInfo.push( {label: 'Version', value: node?.Version}, - {label: 'Uptime', value: node?.Uptime}, + { + label: 'Uptime', + value: , + }, {label: 'DC', value: node?.DataCenterDescription || node?.DC}, {label: 'Rack', value: node?.Rack}, ); diff --git a/src/components/TooltipsContent/TabletTooltipContent/TabletTooltipContent.tsx b/src/components/TooltipsContent/TabletTooltipContent/TabletTooltipContent.tsx index d01c80d89..337de45ae 100644 --- a/src/components/TooltipsContent/TabletTooltipContent/TabletTooltipContent.tsx +++ b/src/components/TooltipsContent/TabletTooltipContent/TabletTooltipContent.tsx @@ -1,10 +1,12 @@ import type {TTabletStateInfo} from '../../../types/api/tablet'; -import {getUptimeFromDateFormatted} from '../../../utils/dataFormatters/dataFormatters'; import {InfoViewer, createInfoFormatter, formatObject} from '../../InfoViewer'; +import {TabletUptime} from '../../UptimeViewer/UptimeViewer'; const formatTablet = createInfoFormatter({ values: { - ChangeTime: (value) => getUptimeFromDateFormatted(value), + ChangeTime: (value) => { + return ; + }, }, labels: { TabletId: 'Tablet', diff --git a/src/components/UptimeViewer/UptimeViewer.tsx b/src/components/UptimeViewer/UptimeViewer.tsx new file mode 100644 index 000000000..bb6927cb1 --- /dev/null +++ b/src/components/UptimeViewer/UptimeViewer.tsx @@ -0,0 +1,79 @@ +import {DefinitionList} from '@gravity-ui/uikit'; + +import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; +import { + formatDateTime, + getDowntimeFromDateFormatted, + getUptimeFromDateFormatted, +} from '../../utils/dataFormatters/dataFormatters'; +import {CellWithPopover} from '../CellWithPopover/CellWithPopover'; + +import i18n from './i18n'; + +interface NodeUptimeProps { + StartTime?: string; + DisconnectTime?: string; +} + +export function NodeUptime({StartTime, DisconnectTime}: NodeUptimeProps) { + let uptime: string | undefined; + + if (DisconnectTime) { + uptime = getDowntimeFromDateFormatted(DisconnectTime); + } else { + uptime = getUptimeFromDateFormatted(StartTime); + } + + if (!uptime) { + return EMPTY_DATA_PLACEHOLDER; + } + return ( + + {StartTime ? ( + + {formatDateTime(StartTime, {withTimeZone: true})} + + ) : null} + {DisconnectTime ? ( + + {formatDateTime(DisconnectTime, {withTimeZone: true})} + + ) : null} + + } + > + {uptime} + + ); +} + +interface TabletUptimeProps { + ChangeTime?: string; +} + +export function TabletUptime({ChangeTime}: TabletUptimeProps) { + const uptime = getUptimeFromDateFormatted(ChangeTime); + + if (!uptime) { + return EMPTY_DATA_PLACEHOLDER; + } + return ( + + + {formatDateTime(ChangeTime, {withTimeZone: true})} + + + } + > + {uptime} + + ); +} diff --git a/src/components/UptimeViewer/i18n/en.json b/src/components/UptimeViewer/i18n/en.json new file mode 100644 index 000000000..928702208 --- /dev/null +++ b/src/components/UptimeViewer/i18n/en.json @@ -0,0 +1,5 @@ +{ + "start-time": "Start time", + "disconnect-time": "Disconnect time", + "change-time": "Change time" +} diff --git a/src/components/UptimeViewer/i18n/index.ts b/src/components/UptimeViewer/i18n/index.ts new file mode 100644 index 000000000..d7a125339 --- /dev/null +++ b/src/components/UptimeViewer/i18n/index.ts @@ -0,0 +1,7 @@ +import {registerKeysets} from '../../../utils/i18n'; + +import en from './en.json'; + +const COMPONENT = 'ydb-uptime-viewer'; + +export default registerKeysets(COMPONENT, {en}); diff --git a/src/components/nodesColumns/columns.tsx b/src/components/nodesColumns/columns.tsx index 4803b049a..5c64f1301 100644 --- a/src/components/nodesColumns/columns.tsx +++ b/src/components/nodesColumns/columns.tsx @@ -23,6 +23,7 @@ import {PoolsGraph} from '../PoolsGraph/PoolsGraph'; import {ProgressViewer} from '../ProgressViewer/ProgressViewer'; import {TabletsStatistic} from '../TabletsStatistic'; import {formatPool} from '../TooltipsContent'; +import {NodeUptime} from '../UptimeViewer/UptimeViewer'; import {UsageLabel} from '../UsageLabel/UsageLabel'; import {NODES_COLUMNS_IDS, NODES_COLUMNS_TITLES} from './constants'; @@ -103,12 +104,16 @@ export function getVersionColumn(): Column { }, }; } -export function getUptimeColumn(): Column { +export function getUptimeColumn< + T extends {StartTime?: string; DisconnectTime?: string}, +>(): Column { return { name: NODES_COLUMNS_IDS.Uptime, header: NODES_COLUMNS_TITLES.Uptime, sortAccessor: ({StartTime}) => (StartTime ? -StartTime : 0), - render: ({row}) => row.Uptime, + render: ({row}) => { + return ; + }, align: DataTable.RIGHT, width: 120, }; diff --git a/src/containers/Tablet/components/TabletInfo/TabletInfo.tsx b/src/containers/Tablet/components/TabletInfo/TabletInfo.tsx index a18f449f4..c5bfdb841 100644 --- a/src/containers/Tablet/components/TabletInfo/TabletInfo.tsx +++ b/src/containers/Tablet/components/TabletInfo/TabletInfo.tsx @@ -5,12 +5,12 @@ import type {InfoViewerItem} from '../../../../components/InfoViewer'; import {InfoViewer} from '../../../../components/InfoViewer'; import {LinkWithIcon} from '../../../../components/LinkWithIcon/LinkWithIcon'; import {TabletState} from '../../../../components/TabletState/TabletState'; +import {TabletUptime} from '../../../../components/UptimeViewer/UptimeViewer'; import {getTabletPagePath} from '../../../../routes'; import {selectIsUserAllowedToMakeChanges} from '../../../../store/reducers/authentication/authentication'; import {ETabletState} from '../../../../types/api/tablet'; import type {TTabletStateInfo} from '../../../../types/api/tablet'; import {cn} from '../../../../utils/cn'; -import {getUptimeFromDateFormatted} from '../../../../utils/dataFormatters/dataFormatters'; import {createTabletDeveloperUIHref} from '../../../../utils/developerUI/developerUI'; import {useTypedSelector} from '../../../../utils/hooks'; import {getDefaultNodePath} from '../../../Node/NodePages'; @@ -72,7 +72,7 @@ export const TabletInfo = ({tablet}: TabletInfoProps) => { if (hasUptime) { tabletInfo.push({ label: tabletInfoKeyset('field_uptime'), - value: getUptimeFromDateFormatted(ChangeTime), + value: , }); } diff --git a/src/containers/Tablet/components/TabletTable/TabletTable.tsx b/src/containers/Tablet/components/TabletTable/TabletTable.tsx index ebe4b5132..d63b51c7b 100644 --- a/src/containers/Tablet/components/TabletTable/TabletTable.tsx +++ b/src/containers/Tablet/components/TabletTable/TabletTable.tsx @@ -5,8 +5,8 @@ import {EntityStatus} from '../../../../components/EntityStatus/EntityStatus'; import {InternalLink} from '../../../../components/InternalLink/InternalLink'; import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; import {TabletState} from '../../../../components/TabletState/TabletState'; +import {TabletUptime} from '../../../../components/UptimeViewer/UptimeViewer'; import type {ITabletPreparedHistoryItem} from '../../../../types/store/tablet'; -import {getUptimeFromDateFormatted} from '../../../../utils/dataFormatters/dataFormatters'; import {getDefaultNodePath} from '../../../Node/NodePages'; const TABLET_COLUMNS_WIDTH_LS_KEY = 'tabletTableColumnsWidth'; @@ -21,7 +21,10 @@ const columns: Column[] = [ name: 'Change time', align: DataTable.RIGHT, sortable: false, - render: ({row}) => getUptimeFromDateFormatted(row.changeTime), + render: ({row}) => { + return ; + }, + width: 120, }, { name: 'State', diff --git a/src/containers/Tablets/TabletsTable.tsx b/src/containers/Tablets/TabletsTable.tsx index c97f3df29..6ac88fb2a 100644 --- a/src/containers/Tablets/TabletsTable.tsx +++ b/src/containers/Tablets/TabletsTable.tsx @@ -9,12 +9,12 @@ import {ResizeableDataTable} from '../../components/ResizeableDataTable/Resizeab import {TableSkeleton} from '../../components/TableSkeleton/TableSkeleton'; import {TabletNameWrapper} from '../../components/TabletNameWrapper/TabletNameWrapper'; import {TabletState} from '../../components/TabletState/TabletState'; +import {TabletUptime} from '../../components/UptimeViewer/UptimeViewer'; import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication'; import {tabletApi} from '../../store/reducers/tablet'; import {ETabletState} from '../../types/api/tablet'; import type {TTabletStateInfo} from '../../types/api/tablet'; import {DEFAULT_TABLE_SETTINGS, EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; -import {getUptimeFromDateFormatted} from '../../utils/dataFormatters/dataFormatters'; import {useTypedSelector} from '../../utils/hooks'; import {getDefaultNodePath} from '../Node/NodePages'; @@ -97,10 +97,11 @@ function getColumns({database}: {database?: string}) { return i18n('Uptime'); }, render: ({row}) => { - return getUptimeFromDateFormatted(row.ChangeTime); + return ; }, sortAccessor: (row) => -Number(row.ChangeTime), align: 'right', + width: 120, }, { name: 'Actions', diff --git a/src/utils/dataFormatters/__test__/formatUptime.test.ts b/src/utils/dataFormatters/__test__/formatUptime.test.ts index 190dfa7e5..ca9f80211 100644 --- a/src/utils/dataFormatters/__test__/formatUptime.test.ts +++ b/src/utils/dataFormatters/__test__/formatUptime.test.ts @@ -1,4 +1,3 @@ -import {EMPTY_DATA_PLACEHOLDER} from '../../constants'; import {UNBREAKABLE_GAP} from '../../utils'; import { formatUptimeInSeconds, @@ -65,6 +64,6 @@ describe('formatUptimeInSeconds', () => { ); }); it('should return empty placeholder on NaN', () => { - expect(formatUptimeInSeconds(Number.NaN)).toBe(EMPTY_DATA_PLACEHOLDER); + expect(formatUptimeInSeconds(Number.NaN)).toBe(undefined); }); }); diff --git a/src/utils/dataFormatters/dataFormatters.ts b/src/utils/dataFormatters/dataFormatters.ts index a1851ee9a..ceae86709 100644 --- a/src/utils/dataFormatters/dataFormatters.ts +++ b/src/utils/dataFormatters/dataFormatters.ts @@ -6,7 +6,7 @@ import { getSizeWithSignificantDigits, } from '../bytesParsers/formatBytes'; import type {BytesSizes} from '../bytesParsers/formatBytes'; -import {EMPTY_DATA_PLACEHOLDER, HOUR_IN_SECONDS} from '../constants'; +import {HOUR_IN_SECONDS} from '../constants'; import {configuredNumeral} from '../numeral'; import {UNBREAKABLE_GAP, isNumeric} from '../utils'; @@ -46,7 +46,7 @@ export const stringifyVdiskId = (id?: TVDiskID | TVSlotId) => { */ export function formatUptimeInSeconds(seconds: number) { if (!isNumeric(seconds)) { - return EMPTY_DATA_PLACEHOLDER; + return undefined; } // duration.format() doesn't work well with negative values @@ -214,8 +214,12 @@ export const formatCPUWithLabel = (value?: number) => { return `${localizedCores} ${i18n('format-cpu.cores', {count: cores})}`; }; -export const formatDateTime = (value?: number | string, defaultValue = '') => { - const formattedData = dateTimeParse(Number(value))?.format('YYYY-MM-DD HH:mm'); +export const formatDateTime = ( + value?: number | string, + {withTimeZone, defaultValue = ''}: {withTimeZone?: boolean; defaultValue?: string} = {}, +) => { + const tz = withTimeZone ? ' z' : ''; + const formattedData = dateTimeParse(Number(value))?.format(`YYYY-MM-DD HH:mm${tz}`); return formattedData ?? defaultValue; }; diff --git a/src/utils/nodes.ts b/src/utils/nodes.ts index 631313543..db827bae4 100644 --- a/src/utils/nodes.ts +++ b/src/utils/nodes.ts @@ -8,10 +8,6 @@ import type {TNodeInfo} from '../types/api/nodesList'; import type {NodeHostsMap} from '../types/store/nodesList'; import {HOUR_IN_SECONDS} from './constants'; -import { - getDowntimeFromDateFormatted, - getUptimeFromDateFormatted, -} from './dataFormatters/dataFormatters'; import {valueIsDefined} from '.'; @@ -60,7 +56,6 @@ export interface PreparedNodeSystemState extends TSystemStateInfo { Rack?: string; DC?: string; LoadAveragePercents?: number[]; - Uptime: string; TenantName?: string; SharedCacheLimit?: number; SharedCacheUsed?: number; @@ -74,14 +69,6 @@ export function prepareNodeSystemState( const DC = systemState.Location?.DataCenter || systemState.DataCenter; const TenantName = systemState?.Tenants?.[0]; - let Uptime: PreparedNodeSystemState['Uptime']; - - if (systemState.DisconnectTime) { - Uptime = getDowntimeFromDateFormatted(systemState.DisconnectTime); - } else { - Uptime = getUptimeFromDateFormatted(systemState.StartTime); - } - const LoadAveragePercents = calculateLoadAveragePercents(systemState); // 0 limit means that limit is not set, so it should be undefined @@ -94,7 +81,6 @@ export function prepareNodeSystemState( ...systemState, Rack, DC, - Uptime, LoadAveragePercents, TenantName, SharedCacheLimit,