ydb-embedded-ui 3.0.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +17 -0
- package/dist/components/ClusterInfo/ClusterInfo.tsx +1 -1
- package/dist/components/InfoViewer/InfoViewer.tsx +1 -1
- package/dist/components/InfoViewer/schemaInfo/CDCStreamInfo.tsx +5 -2
- package/dist/components/InfoViewer/schemaInfo/PersQueueGroupInfo.tsx +6 -5
- package/dist/components/InfoViewer/schemaInfo/TableIndexInfo.tsx +5 -2
- package/dist/components/ProblemFilter/ProblemFilter.tsx +18 -0
- package/dist/components/ProblemFilter/index.ts +1 -0
- package/dist/components/UptimeFIlter/UptimeFilter.tsx +4 -3
- package/dist/containers/Nodes/Nodes.js +2 -2
- package/dist/containers/NodesViewer/NodesViewer.js +2 -2
- package/dist/containers/Pool/Pool.js +2 -2
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +2 -3
- package/dist/containers/Tenant/Diagnostics/Network/Network.js +2 -2
- package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +11 -9
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +6 -3
- package/dist/containers/Tenant/Diagnostics/TopShards/DateRange/DateRange.scss +13 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/DateRange/DateRange.tsx +75 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/DateRange/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.scss +17 -1
- package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +278 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/i18n/en.json +4 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/i18n/index.ts +11 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/i18n/ru.json +4 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/index.ts +1 -0
- package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +35 -22
- package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.scss +8 -0
- package/dist/containers/Tenant/Tenant.tsx +1 -1
- package/dist/containers/Tenant/utils/index.ts +8 -0
- package/dist/containers/Tenant/utils/schema.ts +45 -0
- package/dist/containers/Tenants/Tenants.js +2 -2
- package/dist/services/api.d.ts +3 -0
- package/dist/services/api.js +1 -1
- package/dist/store/reducers/{nodes.js → nodes.ts} +20 -14
- package/dist/store/reducers/shardsWorkload.ts +182 -0
- package/dist/store/reducers/{tooltip.js → tooltip.ts} +28 -11
- package/dist/store/state-url-mapping.js +8 -0
- package/dist/types/api/nodes.ts +3 -3
- package/dist/types/api/schema.ts +1 -1
- package/dist/types/api/tenant.ts +131 -0
- package/dist/types/store/nodes.ts +32 -0
- package/dist/types/store/shardsWorkload.ts +28 -0
- package/dist/types/store/tooltip.ts +25 -0
- package/dist/utils/constants.ts +2 -0
- package/dist/utils/nodes.ts +4 -4
- package/dist/utils/query.ts +1 -1
- package/dist/utils/tooltip.js +8 -6
- package/package.json +2 -2
- package/dist/components/ProblemFilter/ProblemFilter.js +0 -24
- package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.js +0 -246
- package/dist/store/reducers/shardsWorkload.js +0 -101
- package/dist/utils/actionsConstants.js +0 -4
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [3.1.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.0.1...v3.1.0) (2022-12-13)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* **TopShards:** date range filter ([aab4396](https://github.com/ydb-platform/ydb-embedded-ui/commit/aab439600ec28d30799c4a7ef7a9c68fcacc148c))
|
9
|
+
|
10
|
+
## [3.0.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.0.0...v3.0.1) (2022-12-12)
|
11
|
+
|
12
|
+
|
13
|
+
### Bug Fixes
|
14
|
+
|
15
|
+
* **Overview:** display titles for topic, stream and tableIndex ([2ee7889](https://github.com/ydb-platform/ydb-embedded-ui/commit/2ee788932d4f0a6fbe3e9e0526b8ba50e3103d76))
|
16
|
+
* **SchemaOverview:** display entity name ([2d28a2a](https://github.com/ydb-platform/ydb-embedded-ui/commit/2d28a2ad30263e31bc3c8b783d4f42af92537624))
|
17
|
+
* **TenantOverview:** display database type in title ([5f73eed](https://github.com/ydb-platform/ydb-embedded-ui/commit/5f73eed6f9043586885f8e68137d8f31923e8e3b))
|
18
|
+
* **TopShards:** render a message for empty data ([8cda003](https://github.com/ydb-platform/ydb-embedded-ui/commit/8cda0038396b356b10033b44824933f711e1175e))
|
19
|
+
|
3
20
|
## [3.0.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v2.6.0...v3.0.0) (2022-12-05)
|
4
21
|
|
5
22
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import type {TEvDescribeSchemeResult, TCdcStreamDescription} from '../../../types/api/schema';
|
2
2
|
import {useTypedSelector} from '../../../utils/hooks';
|
3
3
|
import {selectSchemaData} from '../../../store/reducers/schema';
|
4
|
+
import {getEntityName} from '../../../containers/Tenant/utils';
|
4
5
|
|
5
6
|
import {formatCdcStreamItem, formatPQGroupItem, formatCommonItem} from '../formatters';
|
6
7
|
import {InfoViewer, InfoViewerItem} from '..';
|
@@ -15,8 +16,10 @@ interface CDCStreamInfoProps {
|
|
15
16
|
export const CDCStreamInfo = ({data, childrenPaths}: CDCStreamInfoProps) => {
|
16
17
|
const pqGroupData = useTypedSelector((state) => selectSchemaData(state, childrenPaths?.[0]));
|
17
18
|
|
19
|
+
const entityName = getEntityName(data?.PathDescription);
|
20
|
+
|
18
21
|
if (!data || !pqGroupData) {
|
19
|
-
return <div className="error">No
|
22
|
+
return <div className="error">No {entityName} data</div>;
|
20
23
|
}
|
21
24
|
|
22
25
|
const cdcStream = data.PathDescription?.CdcStreamDescription;
|
@@ -41,5 +44,5 @@ export const CDCStreamInfo = ({data, childrenPaths}: CDCStreamInfoProps) => {
|
|
41
44
|
),
|
42
45
|
);
|
43
46
|
|
44
|
-
return <InfoViewer title={
|
47
|
+
return <InfoViewer title={entityName} info={info} />;
|
45
48
|
};
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import type {TEvDescribeSchemeResult} from '../../../types/api/schema';
|
2
|
+
import {getEntityName} from '../../../containers/Tenant/utils';
|
2
3
|
|
3
|
-
import {
|
4
|
+
import {formatPQGroupItem} from '../formatters';
|
4
5
|
import {InfoViewer, InfoViewerItem} from '..';
|
5
6
|
|
6
7
|
interface PersQueueGrouopInfoProps {
|
@@ -8,15 +9,15 @@ interface PersQueueGrouopInfoProps {
|
|
8
9
|
}
|
9
10
|
|
10
11
|
export const PersQueueGroupInfo = ({data}: PersQueueGrouopInfoProps) => {
|
12
|
+
const entityName = getEntityName(data?.PathDescription);
|
13
|
+
|
11
14
|
if (!data) {
|
12
|
-
return <div className="error">No
|
15
|
+
return <div className="error">No {entityName} data</div>;
|
13
16
|
}
|
14
17
|
|
15
18
|
const pqGroup = data.PathDescription?.PersQueueGroup;
|
16
19
|
const info: Array<InfoViewerItem> = [];
|
17
20
|
|
18
|
-
info.push(formatCommonItem('PathType', data.PathDescription?.Self?.PathType));
|
19
|
-
|
20
21
|
info.push(formatPQGroupItem('Partitions', pqGroup?.Partitions || []));
|
21
22
|
info.push(
|
22
23
|
formatPQGroupItem(
|
@@ -25,5 +26,5 @@ export const PersQueueGroupInfo = ({data}: PersQueueGrouopInfoProps) => {
|
|
25
26
|
),
|
26
27
|
);
|
27
28
|
|
28
|
-
return
|
29
|
+
return <InfoViewer title={entityName} info={info} />;
|
29
30
|
};
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import type {TEvDescribeSchemeResult, TIndexDescription} from '../../../types/api/schema';
|
2
|
+
import {getEntityName} from '../../../containers/Tenant/utils';
|
2
3
|
|
3
4
|
import {formatTableIndexItem} from '../formatters';
|
4
5
|
import {InfoViewer, InfoViewerItem} from '..';
|
@@ -16,8 +17,10 @@ interface TableIndexInfoProps {
|
|
16
17
|
}
|
17
18
|
|
18
19
|
export const TableIndexInfo = ({data}: TableIndexInfoProps) => {
|
20
|
+
const entityName = getEntityName(data?.PathDescription);
|
21
|
+
|
19
22
|
if (!data) {
|
20
|
-
return <div className="error">
|
23
|
+
return <div className="error">No {entityName} data</div>;
|
21
24
|
}
|
22
25
|
|
23
26
|
const TableIndex = data.PathDescription?.TableIndex;
|
@@ -30,5 +33,5 @@ export const TableIndexInfo = ({data}: TableIndexInfoProps) => {
|
|
30
33
|
}
|
31
34
|
}
|
32
35
|
|
33
|
-
return
|
36
|
+
return <InfoViewer title={entityName} info={info} />;
|
34
37
|
};
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import {RadioButton} from '@gravity-ui/uikit';
|
2
|
+
|
3
|
+
import {ALL, PROBLEMS, IProblemFilterValues} from '../../utils/constants';
|
4
|
+
|
5
|
+
interface ProblemFilterProps {
|
6
|
+
value: IProblemFilterValues;
|
7
|
+
onChange: (value: string) => void;
|
8
|
+
className?: string;
|
9
|
+
}
|
10
|
+
|
11
|
+
export const ProblemFilter = ({value, onChange, className}: ProblemFilterProps) => {
|
12
|
+
return (
|
13
|
+
<RadioButton value={value} onUpdate={onChange} className={className}>
|
14
|
+
<RadioButton.Option value={ALL}>{ALL}</RadioButton.Option>
|
15
|
+
<RadioButton.Option value={PROBLEMS}>{PROBLEMS}</RadioButton.Option>
|
16
|
+
</RadioButton>
|
17
|
+
);
|
18
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './ProblemFilter';
|
@@ -3,13 +3,14 @@ import {RadioButton} from '@gravity-ui/uikit';
|
|
3
3
|
import {NodesUptimeFilterValues, NodesUptimeFilterTitles} from '../../utils/nodes';
|
4
4
|
|
5
5
|
interface UptimeFilterProps {
|
6
|
-
value:
|
6
|
+
value: NodesUptimeFilterValues;
|
7
7
|
onChange: (value: string) => void;
|
8
|
+
className?: string;
|
8
9
|
}
|
9
10
|
|
10
|
-
export const UptimeFilter = ({value, onChange}: UptimeFilterProps) => {
|
11
|
+
export const UptimeFilter = ({value, onChange, className}: UptimeFilterProps) => {
|
11
12
|
return (
|
12
|
-
<RadioButton value={value} onUpdate={onChange}>
|
13
|
+
<RadioButton value={value} onUpdate={onChange} className={className}>
|
13
14
|
<RadioButton.Option value={NodesUptimeFilterValues.All}>
|
14
15
|
{NodesUptimeFilterTitles[NodesUptimeFilterValues.All]}
|
15
16
|
</RadioButton.Option>
|
@@ -6,7 +6,7 @@ import {connect} from 'react-redux';
|
|
6
6
|
import DataTable from '@yandex-cloud/react-data-table';
|
7
7
|
import {Loader, TextInput, Label} from '@gravity-ui/uikit';
|
8
8
|
|
9
|
-
import ProblemFilter
|
9
|
+
import {ProblemFilter} from '../../components/ProblemFilter';
|
10
10
|
import {Illustration} from '../../components/Illustration';
|
11
11
|
import {AccessDenied} from '../../components/Errors/403';
|
12
12
|
import {UptimeFilter} from '../../components/UptimeFIlter';
|
@@ -46,7 +46,7 @@ class Nodes extends React.Component {
|
|
46
46
|
hideTooltip: PropTypes.func,
|
47
47
|
searchQuery: PropTypes.string,
|
48
48
|
handleSearchQuery: PropTypes.func,
|
49
|
-
problemFilter:
|
49
|
+
problemFilter: PropTypes.string,
|
50
50
|
changeFilter: PropTypes.func,
|
51
51
|
setHeader: PropTypes.func,
|
52
52
|
className: PropTypes.string,
|
@@ -6,7 +6,7 @@ import {connect} from 'react-redux';
|
|
6
6
|
import {TextInput, Label} from '@gravity-ui/uikit';
|
7
7
|
import DataTable from '@yandex-cloud/react-data-table';
|
8
8
|
|
9
|
-
import ProblemFilter
|
9
|
+
import {ProblemFilter} from '../../components/ProblemFilter';
|
10
10
|
import {UptimeFilter} from '../../components/UptimeFIlter';
|
11
11
|
import {Illustration} from '../../components/Illustration';
|
12
12
|
|
@@ -48,7 +48,7 @@ class NodesViewer extends React.PureComponent {
|
|
48
48
|
handleSearchQuery: PropTypes.func,
|
49
49
|
showTooltip: PropTypes.func,
|
50
50
|
hideTooltip: PropTypes.func,
|
51
|
-
problemFilter:
|
51
|
+
problemFilter: PropTypes.string,
|
52
52
|
nodesUptimeFilter: PropTypes.string,
|
53
53
|
setNodesUptimeFilter: PropTypes.func,
|
54
54
|
changeFilter: PropTypes.func,
|
@@ -10,7 +10,7 @@ import ReactList from 'react-list';
|
|
10
10
|
import EntityStatus from '../../components/EntityStatus/EntityStatus';
|
11
11
|
import GroupTreeViewer from '../../components/GroupTreeViewer/GroupTreeViewer';
|
12
12
|
import Breadcrumbs from '../../components/Breadcrumbs/Breadcrumbs';
|
13
|
-
import ProblemFilter
|
13
|
+
import {ProblemFilter} from '../../components/ProblemFilter/ProblemFilter';
|
14
14
|
import {Illustration} from '../../components/Illustration';
|
15
15
|
|
16
16
|
import {getPoolInfo} from '../../store/reducers/pool';
|
@@ -38,7 +38,7 @@ class Pool extends React.Component {
|
|
38
38
|
pool: PropTypes.object,
|
39
39
|
poolName: PropTypes.string,
|
40
40
|
match: PropTypes.object,
|
41
|
-
filter:
|
41
|
+
filter: PropTypes.string,
|
42
42
|
changeFilter: PropTypes.func,
|
43
43
|
};
|
44
44
|
|
@@ -13,8 +13,7 @@ import {Loader} from '../../../components/Loader';
|
|
13
13
|
import TopQueries from './TopQueries/TopQueries';
|
14
14
|
//@ts-ignore
|
15
15
|
import DetailedOverview from './DetailedOverview/DetailedOverview';
|
16
|
-
|
17
|
-
import TopShards from './TopShards/TopShards';
|
16
|
+
import {TopShards} from './TopShards';
|
18
17
|
//@ts-ignore
|
19
18
|
import Storage from '../../Storage/Storage';
|
20
19
|
//@ts-ignore
|
@@ -132,7 +131,7 @@ function Diagnostics(props: DiagnosticsProps) {
|
|
132
131
|
);
|
133
132
|
}
|
134
133
|
case GeneralPagesIds.topShards: {
|
135
|
-
return <TopShards
|
134
|
+
return <TopShards tenantPath={tenantNameString} type={type} />;
|
136
135
|
}
|
137
136
|
case GeneralPagesIds.nodes: {
|
138
137
|
return (
|
@@ -9,7 +9,7 @@ import {Loader, Checkbox} from '@gravity-ui/uikit';
|
|
9
9
|
|
10
10
|
import NodeNetwork from './NodeNetwork/NodeNetwork';
|
11
11
|
import Icon from '../../../../components/Icon/Icon';
|
12
|
-
import ProblemFilter
|
12
|
+
import {ProblemFilter} from '../../../../components/ProblemFilter';
|
13
13
|
import {Illustration} from '../../../../components/Illustration';
|
14
14
|
|
15
15
|
import {getNetworkInfo, setDataWasNotLoaded} from '../../../../store/reducers/network';
|
@@ -35,7 +35,7 @@ class Network extends React.Component {
|
|
35
35
|
loading: PropTypes.bool,
|
36
36
|
autorefresh: PropTypes.bool,
|
37
37
|
path: PropTypes.string,
|
38
|
-
filter:
|
38
|
+
filter: PropTypes.string,
|
39
39
|
changeFilter: PropTypes.func,
|
40
40
|
};
|
41
41
|
|
@@ -12,7 +12,10 @@ import {
|
|
12
12
|
PersQueueGroupInfo,
|
13
13
|
} from '../../../../components/InfoViewer/schemaInfo';
|
14
14
|
|
15
|
-
import {
|
15
|
+
import {
|
16
|
+
EPathType,
|
17
|
+
TEvDescribeSchemeResult,
|
18
|
+
} from '../../../../types/api/schema';
|
16
19
|
import {
|
17
20
|
isEntityWithMergedImplementation,
|
18
21
|
isColumnEntityType,
|
@@ -34,8 +37,9 @@ import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
|
|
34
37
|
|
35
38
|
import './Overview.scss';
|
36
39
|
|
37
|
-
function prepareOlapTableGeneral(
|
38
|
-
const
|
40
|
+
function prepareOlapTableGeneral(item?: TEvDescribeSchemeResult, olapStats?: any[]) {
|
41
|
+
const tableData = item?.PathDescription?.ColumnTableDescription;
|
42
|
+
|
39
43
|
const Bytes = olapStats?.reduce((acc, el) => {
|
40
44
|
acc += parseInt(el.Bytes) || 0;
|
41
45
|
return acc;
|
@@ -51,8 +55,9 @@ function prepareOlapTableGeneral(tableData: TColumnTableDescription = {}, olapSt
|
|
51
55
|
|
52
56
|
return {
|
53
57
|
PathDescription: {
|
58
|
+
Self: item?.PathDescription?.Self,
|
54
59
|
TableStats: {
|
55
|
-
ColumnShardCount,
|
60
|
+
ColumnShardCount: tableData?.ColumnShardCount,
|
56
61
|
Bytes: Bytes?.toLocaleString('ru-RU', {useGrouping: true}) ?? 0,
|
57
62
|
Rows: Rows?.toLocaleString('ru-RU', {useGrouping: true}) ?? 0,
|
58
63
|
Parts: tabletIds?.size ?? 0,
|
@@ -130,15 +135,12 @@ function Overview({type, tenantName, className}: OverviewProps) {
|
|
130
135
|
|
131
136
|
useAutofetcher(fetchData, [fetchData], autorefresh);
|
132
137
|
|
133
|
-
const tableSchema =
|
134
|
-
currentItem?.PathDescription?.Table || currentItem?.PathDescription?.ColumnTableDescription;
|
135
|
-
|
136
138
|
const schemaData = useMemo(() => {
|
137
139
|
return isTableType(type) && isColumnEntityType(type)
|
138
140
|
? // process data for ColumnTable
|
139
|
-
prepareOlapTableGeneral(
|
141
|
+
prepareOlapTableGeneral(currentItem, olapStats)
|
140
142
|
: currentItem;
|
141
|
-
}, [type,
|
143
|
+
}, [type, olapStats, currentItem]);
|
142
144
|
|
143
145
|
const renderLoader = () => {
|
144
146
|
return (
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
2
2
|
import {connect} from 'react-redux';
|
3
3
|
import cn from 'bem-cn-lite';
|
4
4
|
import PropTypes from 'prop-types';
|
5
|
+
import {Loader} from '@gravity-ui/uikit';
|
5
6
|
|
6
7
|
import EntityStatus from '../../../../components/EntityStatus/EntityStatus';
|
7
8
|
import InfoViewer from '../../../../components/InfoViewer/InfoViewer';
|
@@ -14,10 +15,11 @@ import {getTenantInfo} from '../../../../store/reducers/tenant';
|
|
14
15
|
import {formatCPU} from '../../../../utils';
|
15
16
|
import {bytesToGB} from '../../../../utils/utils';
|
16
17
|
import {TABLET_STATES} from '../../../../utils/constants';
|
18
|
+
import {AutoFetcher} from '../../../../utils/autofetcher';
|
19
|
+
|
20
|
+
import {mapDatabaseTypeToDBName} from '../../utils/schema';
|
17
21
|
|
18
22
|
import './TenantOverview.scss';
|
19
|
-
import {AutoFetcher} from '../../../../utils/autofetcher';
|
20
|
-
import {Loader} from '@gravity-ui/uikit';
|
21
23
|
|
22
24
|
const b = cn('tenant-overview');
|
23
25
|
|
@@ -113,6 +115,7 @@ class TenantOverview extends React.Component {
|
|
113
115
|
SystemTablets,
|
114
116
|
} = tenant;
|
115
117
|
|
118
|
+
const tenantName = mapDatabaseTypeToDBName(Type);
|
116
119
|
const memoryRaw = MemoryUsed ?? Metrics.Memory;
|
117
120
|
|
118
121
|
const memory = (memoryRaw && bytesToGB(memoryRaw)) || 'no data';
|
@@ -147,7 +150,7 @@ class TenantOverview extends React.Component {
|
|
147
150
|
this.renderLoader()
|
148
151
|
) : (
|
149
152
|
<div className={b()}>
|
150
|
-
<div className={b('top-label')}>
|
153
|
+
<div className={b('top-label')}>{tenantName}</div>
|
151
154
|
<div className={b('top')}>
|
152
155
|
{renderName(tenant)}
|
153
156
|
{this.props.additionalTenantInfo &&
|
@@ -0,0 +1,13 @@
|
|
1
|
+
.top-shards {
|
2
|
+
&__date-range {
|
3
|
+
&-input {
|
4
|
+
min-width: 190px;
|
5
|
+
padding: 5px 8px;
|
6
|
+
|
7
|
+
color: var(--yc-color-text-primary);
|
8
|
+
border: 1px solid var(--yc-color-line-generic);
|
9
|
+
border-radius: var(--yc-border-radius-m);
|
10
|
+
background: transparent;
|
11
|
+
}
|
12
|
+
}
|
13
|
+
}
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import cn from 'bem-cn-lite';
|
2
|
+
import {ChangeEventHandler} from 'react';
|
3
|
+
|
4
|
+
import './DateRange.scss';
|
5
|
+
|
6
|
+
const b = cn('top-shards');
|
7
|
+
|
8
|
+
export interface DateRangeValues {
|
9
|
+
/** ms from epoch */
|
10
|
+
from?: number;
|
11
|
+
/** ms from epoch */
|
12
|
+
to?: number;
|
13
|
+
}
|
14
|
+
|
15
|
+
interface DateRangeProps extends DateRangeValues {
|
16
|
+
className?: string;
|
17
|
+
onChange?: (value: DateRangeValues) => void;
|
18
|
+
}
|
19
|
+
|
20
|
+
const toTimezonelessISOString = (timestamp?: number) => {
|
21
|
+
if (!timestamp || isNaN(timestamp)) {
|
22
|
+
return undefined;
|
23
|
+
}
|
24
|
+
|
25
|
+
// shift by local offset to treat toISOString output as local time
|
26
|
+
const shiftedTimestamp = timestamp - new Date().getTimezoneOffset() * 60 * 1000;
|
27
|
+
return new Date(shiftedTimestamp).toISOString().substring(0, 'yyyy-MM-DDThh:mm'.length);
|
28
|
+
};
|
29
|
+
|
30
|
+
export const DateRange = ({from, to, className, onChange}: DateRangeProps) => {
|
31
|
+
const handleFromChange: ChangeEventHandler<HTMLInputElement> = ({target: {value}}) => {
|
32
|
+
let newFrom = value ? new Date(value).getTime() : undefined;
|
33
|
+
|
34
|
+
// some browsers allow selecting time after the boundary specified in `max`
|
35
|
+
if (newFrom && to && newFrom > to) {
|
36
|
+
newFrom = to;
|
37
|
+
}
|
38
|
+
|
39
|
+
onChange?.({from: newFrom, to});
|
40
|
+
};
|
41
|
+
|
42
|
+
const handleToChange: ChangeEventHandler<HTMLInputElement> = ({target: {value}}) => {
|
43
|
+
let newTo = value ? new Date(value).getTime() : undefined;
|
44
|
+
|
45
|
+
// some browsers allow selecting time before the boundary specified in `min`
|
46
|
+
if (from && newTo && from > newTo) {
|
47
|
+
newTo = from;
|
48
|
+
}
|
49
|
+
|
50
|
+
onChange?.({from, to: newTo});
|
51
|
+
};
|
52
|
+
|
53
|
+
const startISO = toTimezonelessISOString(from);
|
54
|
+
const endISO = toTimezonelessISOString(to);
|
55
|
+
|
56
|
+
return (
|
57
|
+
<div className={b('date-range', className)}>
|
58
|
+
<input
|
59
|
+
type="datetime-local"
|
60
|
+
value={startISO}
|
61
|
+
max={endISO}
|
62
|
+
onChange={handleFromChange}
|
63
|
+
className={b('date-range-input')}
|
64
|
+
/>
|
65
|
+
—
|
66
|
+
<input
|
67
|
+
type="datetime-local"
|
68
|
+
min={startISO}
|
69
|
+
value={endISO}
|
70
|
+
onChange={handleToChange}
|
71
|
+
className={b('date-range-input')}
|
72
|
+
/>
|
73
|
+
</div>
|
74
|
+
);
|
75
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './DateRange';
|
@@ -1,11 +1,27 @@
|
|
1
1
|
.top-shards {
|
2
|
+
display: flex;
|
3
|
+
flex-direction: column;
|
4
|
+
|
5
|
+
height: 100%;
|
6
|
+
|
2
7
|
background-color: var(--yc-color-base-background);
|
8
|
+
|
3
9
|
&__loader {
|
4
10
|
display: flex;
|
5
11
|
justify-content: center;
|
6
12
|
}
|
7
13
|
|
14
|
+
&__controls {
|
15
|
+
display: flex;
|
16
|
+
flex-wrap: wrap;
|
17
|
+
align-items: baseline;
|
18
|
+
gap: 16px;
|
19
|
+
|
20
|
+
margin-bottom: 10px;
|
21
|
+
}
|
22
|
+
|
8
23
|
&__table {
|
9
|
-
|
24
|
+
overflow: auto;
|
25
|
+
flex-grow: 1;
|
10
26
|
}
|
11
27
|
}
|