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,