ydb-embedded-ui 4.5.0 → 4.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/components/CriticalActionDialog/CriticalActionDialog.tsx +3 -0
- package/dist/containers/Tablet/Tablet.scss +4 -0
- package/dist/containers/Tablet/Tablet.tsx +2 -1
- package/dist/containers/Tablet/TabletControls/TabletControls.tsx +19 -27
- package/dist/containers/Tenant/Diagnostics/Consumers/columns/columns.tsx +2 -2
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +37 -38
- package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +15 -28
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +2 -2
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +5 -3
- package/dist/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx +2 -3
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +3 -3
- package/dist/containers/Tenant/Tenant.tsx +14 -15
- package/dist/containers/Tenant/TenantPages.tsx +3 -6
- package/dist/containers/Tenant/utils/schemaActions.ts +4 -4
- package/dist/routes.ts +1 -1
- package/dist/store/reducers/index.ts +1 -1
- package/dist/store/reducers/tenant/constants.ts +19 -0
- package/dist/store/reducers/{tenant.js → tenant/tenant.ts} +27 -36
- package/dist/store/reducers/tenant/types.ts +25 -0
- package/dist/utils/index.js +2 -1
- package/package.json +2 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.js +0 -25
- package/dist/components/Breadcrumbs/Breadcrumbs.scss +0 -5
- package/dist/components/Collapse/Collapse.js +0 -84
- package/dist/components/Collapse/Collapse.scss +0 -70
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [4.5.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.5.0...v4.5.1) (2023-06-02)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* **Tablet:** fetch data on action finish ([#405](https://github.com/ydb-platform/ydb-embedded-ui/issues/405)) ([f1d71c5](https://github.com/ydb-platform/ydb-embedded-ui/commit/f1d71c5af330a0a13246f8d87433e6bba1d3509a))
|
9
|
+
|
3
10
|
## [4.5.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.4.2...v4.5.0) (2023-06-01)
|
4
11
|
|
5
12
|
|
@@ -13,6 +13,7 @@ interface CriticalActionDialogProps {
|
|
13
13
|
text: string;
|
14
14
|
onClose: VoidFunction;
|
15
15
|
onConfirm: () => Promise<unknown>;
|
16
|
+
onConfirmActionFinish: VoidFunction;
|
16
17
|
}
|
17
18
|
|
18
19
|
export const CriticalActionDialog = ({
|
@@ -20,6 +21,7 @@ export const CriticalActionDialog = ({
|
|
20
21
|
text,
|
21
22
|
onClose,
|
22
23
|
onConfirm,
|
24
|
+
onConfirmActionFinish,
|
23
25
|
}: CriticalActionDialogProps) => {
|
24
26
|
const [isLoading, setIsLoading] = useState(false);
|
25
27
|
|
@@ -28,6 +30,7 @@ export const CriticalActionDialog = ({
|
|
28
30
|
setIsLoading(true);
|
29
31
|
|
30
32
|
return onConfirm().then(() => {
|
33
|
+
onConfirmActionFinish();
|
31
34
|
setIsLoading(false);
|
32
35
|
onClose();
|
33
36
|
});
|
@@ -108,9 +108,10 @@ export const Tablet = () => {
|
|
108
108
|
<Icon name="external" />
|
109
109
|
</a>
|
110
110
|
{Leader && <Tag text="Leader" type="blue" />}
|
111
|
+
<span className={b('loader')}>{loading && <Loader size="s" />}</span>
|
111
112
|
</div>
|
112
113
|
<TabletInfo tablet={tablet} tenantPath={tenantPath} />
|
113
|
-
<TabletControls tablet={tablet} />
|
114
|
+
<TabletControls tablet={tablet} fetchData={fetchData} />
|
114
115
|
</div>
|
115
116
|
<div className={b('rigth-pane')}>
|
116
117
|
<TabletTable history={history} />
|
@@ -17,18 +17,19 @@ type VisibleDialogType = EVisibleDialogType | null;
|
|
17
17
|
|
18
18
|
interface TabletControlsProps {
|
19
19
|
tablet: TTabletStateInfo;
|
20
|
+
fetchData: VoidFunction;
|
20
21
|
}
|
21
22
|
|
22
|
-
export const TabletControls = ({tablet}: TabletControlsProps) => {
|
23
|
+
export const TabletControls = ({tablet, fetchData}: TabletControlsProps) => {
|
23
24
|
const {TabletId, HiveId} = tablet;
|
24
25
|
|
25
26
|
const [isDialogVisible, setIsDialogVisible] = useState(false);
|
26
27
|
const [visibleDialogType, setVisibleDialogType] = useState<VisibleDialogType>(null);
|
27
|
-
const [
|
28
|
+
const [isTabletActionLoading, setIsTabletActionLoading] = useState(false);
|
28
29
|
|
29
30
|
// Enable controls after data update
|
30
31
|
useEffect(() => {
|
31
|
-
|
32
|
+
setIsTabletActionLoading(false);
|
32
33
|
}, [tablet]);
|
33
34
|
|
34
35
|
const makeShowDialog = (type: VisibleDialogType) => () => {
|
@@ -46,15 +47,15 @@ export const TabletControls = ({tablet}: TabletControlsProps) => {
|
|
46
47
|
};
|
47
48
|
|
48
49
|
const _onKillClick = () => {
|
49
|
-
|
50
|
+
setIsTabletActionLoading(true);
|
50
51
|
return window.api.killTablet(TabletId);
|
51
52
|
};
|
52
53
|
const _onStopClick = () => {
|
53
|
-
|
54
|
+
setIsTabletActionLoading(true);
|
54
55
|
return window.api.stopTablet(TabletId, HiveId);
|
55
56
|
};
|
56
57
|
const _onResumeClick = () => {
|
57
|
-
|
58
|
+
setIsTabletActionLoading(true);
|
58
59
|
return window.api.resumeTablet(TabletId, HiveId);
|
59
60
|
};
|
60
61
|
|
@@ -62,25 +63,11 @@ export const TabletControls = ({tablet}: TabletControlsProps) => {
|
|
62
63
|
return HiveId && HiveId !== '0';
|
63
64
|
};
|
64
65
|
|
65
|
-
const isDisabledResume =
|
66
|
-
|
67
|
-
return true;
|
68
|
-
}
|
69
|
-
|
70
|
-
return tablet.State !== ETabletState.Stopped && tablet.State !== ETabletState.Dead;
|
71
|
-
};
|
72
|
-
|
73
|
-
const isDisabledKill = () => {
|
74
|
-
return isTabletActionsDisabled;
|
75
|
-
};
|
76
|
-
|
77
|
-
const isDisabledStop = () => {
|
78
|
-
if (isTabletActionsDisabled) {
|
79
|
-
return true;
|
80
|
-
}
|
66
|
+
const isDisabledResume =
|
67
|
+
tablet.State !== ETabletState.Stopped && tablet.State !== ETabletState.Dead;
|
81
68
|
|
82
|
-
|
83
|
-
|
69
|
+
const isDisabledStop =
|
70
|
+
tablet.State === ETabletState.Stopped || tablet.State === ETabletState.Deleted;
|
84
71
|
|
85
72
|
const renderDialog = () => {
|
86
73
|
if (!isDialogVisible) {
|
@@ -95,6 +82,7 @@ export const TabletControls = ({tablet}: TabletControlsProps) => {
|
|
95
82
|
text={i18n('dialog.kill')}
|
96
83
|
onClose={hideDialog}
|
97
84
|
onConfirm={_onKillClick}
|
85
|
+
onConfirmActionFinish={fetchData}
|
98
86
|
/>
|
99
87
|
);
|
100
88
|
}
|
@@ -105,6 +93,7 @@ export const TabletControls = ({tablet}: TabletControlsProps) => {
|
|
105
93
|
text={i18n('dialog.stop')}
|
106
94
|
onClose={hideDialog}
|
107
95
|
onConfirm={_onStopClick}
|
96
|
+
onConfirmActionFinish={fetchData}
|
108
97
|
/>
|
109
98
|
);
|
110
99
|
}
|
@@ -115,6 +104,7 @@ export const TabletControls = ({tablet}: TabletControlsProps) => {
|
|
115
104
|
text={i18n('dialog.resume')}
|
116
105
|
onClose={hideDialog}
|
117
106
|
onConfirm={_onResumeClick}
|
107
|
+
onConfirmActionFinish={fetchData}
|
118
108
|
/>
|
119
109
|
);
|
120
110
|
}
|
@@ -128,7 +118,7 @@ export const TabletControls = ({tablet}: TabletControlsProps) => {
|
|
128
118
|
<Button
|
129
119
|
onClick={showKillDialog}
|
130
120
|
view="action"
|
131
|
-
|
121
|
+
loading={isTabletActionLoading}
|
132
122
|
className={b('control')}
|
133
123
|
>
|
134
124
|
{i18n('controls.kill')}
|
@@ -138,7 +128,8 @@ export const TabletControls = ({tablet}: TabletControlsProps) => {
|
|
138
128
|
<Button
|
139
129
|
onClick={showStopDialog}
|
140
130
|
view="action"
|
141
|
-
disabled={isDisabledStop
|
131
|
+
disabled={isDisabledStop}
|
132
|
+
loading={!isDisabledStop && isTabletActionLoading}
|
142
133
|
className={b('control')}
|
143
134
|
>
|
144
135
|
{i18n('controls.stop')}
|
@@ -146,7 +137,8 @@ export const TabletControls = ({tablet}: TabletControlsProps) => {
|
|
146
137
|
<Button
|
147
138
|
onClick={showResumeDialog}
|
148
139
|
view="action"
|
149
|
-
disabled={isDisabledResume
|
140
|
+
disabled={isDisabledResume}
|
141
|
+
loading={!isDisabledResume && isTabletActionLoading}
|
150
142
|
className={b('control')}
|
151
143
|
>
|
152
144
|
{i18n('controls.resume')}
|
@@ -3,13 +3,13 @@ import block from 'bem-cn-lite';
|
|
3
3
|
import qs from 'qs';
|
4
4
|
|
5
5
|
import type {IPreparedConsumerData} from '../../../../../types/store/topic';
|
6
|
+
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
|
6
7
|
import {SpeedMultiMeter} from '../../../../../components/SpeedMultiMeter';
|
7
8
|
import {InternalLink} from '../../../../../components/InternalLink';
|
8
9
|
import {formatMsToUptime} from '../../../../../utils';
|
9
10
|
import routes, {createHref} from '../../../../../routes';
|
10
11
|
|
11
12
|
import {TenantTabsGroups} from '../../../TenantPages';
|
12
|
-
import {GeneralPagesIds} from '../../DiagnosticsPages';
|
13
13
|
|
14
14
|
import {
|
15
15
|
CONSUMERS_COLUMNS_IDS,
|
@@ -42,7 +42,7 @@ export const columns: Column<IPreparedConsumerData>[] = [
|
|
42
42
|
<InternalLink
|
43
43
|
to={createHref(routes.tenant, undefined, {
|
44
44
|
...queryParams,
|
45
|
-
[TenantTabsGroups.generalTab]:
|
45
|
+
[TenantTabsGroups.generalTab]: TENANT_DIAGNOSTICS_TABS_IDS.partitions,
|
46
46
|
selectedConsumer: row.name,
|
47
47
|
})}
|
48
48
|
>
|
@@ -7,37 +7,36 @@ import {useLocation} from 'react-router';
|
|
7
7
|
|
8
8
|
import {Switch, Tabs} from '@gravity-ui/uikit';
|
9
9
|
|
10
|
+
import type {EPathType} from '../../../types/api/schema';
|
11
|
+
|
12
|
+
import {useTypedSelector} from '../../../utils/hooks';
|
13
|
+
import routes, {createHref} from '../../../routes';
|
14
|
+
import type {TenantDiagnosticsTab, TenantGeneralTab} from '../../../store/reducers/tenant/types';
|
15
|
+
import {enableAutorefresh, disableAutorefresh} from '../../../store/reducers/schema';
|
16
|
+
import {setTopLevelTab, setDiagnosticsTab} from '../../../store/reducers/tenant/tenant';
|
17
|
+
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../store/reducers/tenant/constants';
|
18
|
+
|
10
19
|
import {Loader} from '../../../components/Loader';
|
11
20
|
|
12
|
-
import {TopQueries} from './TopQueries';
|
13
|
-
//@ts-ignore
|
14
|
-
import DetailedOverview from './DetailedOverview/DetailedOverview';
|
15
|
-
import {TopShards} from './TopShards';
|
16
|
-
//@ts-ignore
|
17
|
-
import Storage from '../../Storage/Storage';
|
18
|
-
//@ts-ignore
|
19
|
-
import Network from './Network/Network';
|
20
|
-
//@ts-ignore
|
21
|
-
import Describe from './Describe/Describe';
|
22
|
-
//@ts-ignore
|
23
|
-
import HotKeys from './HotKeys/HotKeys';
|
24
|
-
//@ts-ignore
|
25
21
|
import {Heatmap} from '../../Heatmap';
|
26
22
|
import {Nodes} from '../../Nodes';
|
27
|
-
|
23
|
+
import Storage from '../../Storage/Storage';
|
28
24
|
import {Tablets} from '../../Tablets';
|
29
|
-
|
25
|
+
|
26
|
+
import Describe from './Describe/Describe';
|
27
|
+
import HotKeys from './HotKeys/HotKeys';
|
28
|
+
import Network from './Network/Network';
|
30
29
|
import {Partitions} from './Partitions/Partitions';
|
30
|
+
import {Consumers} from './Consumers';
|
31
|
+
import {TopQueries} from './TopQueries';
|
32
|
+
import {TopShards} from './TopShards';
|
33
|
+
import DetailedOverview from './DetailedOverview/DetailedOverview';
|
31
34
|
|
32
|
-
import routes, {createHref} from '../../../routes';
|
33
|
-
import type {EPathType} from '../../../types/api/schema';
|
34
|
-
import {TenantGeneralTabsIds, TenantTabsGroups} from '../TenantPages';
|
35
|
-
import {GeneralPagesIds, DATABASE_PAGES, getPagesByType} from './DiagnosticsPages';
|
36
|
-
//@ts-ignore
|
37
|
-
import {enableAutorefresh, disableAutorefresh} from '../../../store/reducers/schema';
|
38
|
-
import {setTopLevelTab, setDiagnosticsTab} from '../../../store/reducers/tenant';
|
39
35
|
import {isDatabaseEntityType} from '../utils/schema';
|
40
36
|
|
37
|
+
import {TenantTabsGroups} from '../TenantPages';
|
38
|
+
import {DATABASE_PAGES, getPagesByType} from './DiagnosticsPages';
|
39
|
+
|
41
40
|
import './Diagnostics.scss';
|
42
41
|
|
43
42
|
interface DiagnosticsProps {
|
@@ -51,8 +50,8 @@ const b = cn('kv-tenant-diagnostics');
|
|
51
50
|
function Diagnostics(props: DiagnosticsProps) {
|
52
51
|
const dispatch = useDispatch();
|
53
52
|
const {currentSchemaPath, autorefresh} = useSelector((state: any) => state.schema);
|
54
|
-
const {diagnosticsTab =
|
55
|
-
(state
|
53
|
+
const {diagnosticsTab = TENANT_DIAGNOSTICS_TABS_IDS.overview, wasLoaded} = useTypedSelector(
|
54
|
+
(state) => state.tenant,
|
56
55
|
);
|
57
56
|
|
58
57
|
const location = useLocation();
|
@@ -73,7 +72,7 @@ function Diagnostics(props: DiagnosticsProps) {
|
|
73
72
|
return getPagesByType(props.type);
|
74
73
|
}, [props.type, isDatabase]);
|
75
74
|
|
76
|
-
const forwardToDiagnosticTab = (tab:
|
75
|
+
const forwardToDiagnosticTab = (tab: TenantDiagnosticsTab) => {
|
77
76
|
dispatch(setDiagnosticsTab(tab));
|
78
77
|
};
|
79
78
|
const activeTab = useMemo(() => {
|
@@ -97,7 +96,7 @@ function Diagnostics(props: DiagnosticsProps) {
|
|
97
96
|
}
|
98
97
|
};
|
99
98
|
|
100
|
-
const forwardToGeneralTab = (tab:
|
99
|
+
const forwardToGeneralTab = (tab: TenantGeneralTab) => {
|
101
100
|
dispatch(setTopLevelTab(tab));
|
102
101
|
};
|
103
102
|
|
@@ -107,7 +106,7 @@ function Diagnostics(props: DiagnosticsProps) {
|
|
107
106
|
const tenantNameString = tenantName as string;
|
108
107
|
|
109
108
|
switch (diagnosticsTab) {
|
110
|
-
case
|
109
|
+
case TENANT_DIAGNOSTICS_TABS_IDS.overview: {
|
111
110
|
return (
|
112
111
|
<DetailedOverview
|
113
112
|
type={type}
|
@@ -116,7 +115,7 @@ function Diagnostics(props: DiagnosticsProps) {
|
|
116
115
|
/>
|
117
116
|
);
|
118
117
|
}
|
119
|
-
case
|
118
|
+
case TENANT_DIAGNOSTICS_TABS_IDS.topQueries: {
|
120
119
|
return (
|
121
120
|
<TopQueries
|
122
121
|
path={tenantNameString}
|
@@ -125,10 +124,10 @@ function Diagnostics(props: DiagnosticsProps) {
|
|
125
124
|
/>
|
126
125
|
);
|
127
126
|
}
|
128
|
-
case
|
127
|
+
case TENANT_DIAGNOSTICS_TABS_IDS.topShards: {
|
129
128
|
return <TopShards tenantPath={tenantNameString} type={type} />;
|
130
129
|
}
|
131
|
-
case
|
130
|
+
case TENANT_DIAGNOSTICS_TABS_IDS.nodes: {
|
132
131
|
return (
|
133
132
|
<Nodes
|
134
133
|
path={currentSchemaPath}
|
@@ -137,28 +136,28 @@ function Diagnostics(props: DiagnosticsProps) {
|
|
137
136
|
/>
|
138
137
|
);
|
139
138
|
}
|
140
|
-
case
|
139
|
+
case TENANT_DIAGNOSTICS_TABS_IDS.tablets: {
|
141
140
|
return <Tablets path={currentSchemaPath} />;
|
142
141
|
}
|
143
|
-
case
|
142
|
+
case TENANT_DIAGNOSTICS_TABS_IDS.storage: {
|
144
143
|
return <Storage tenant={tenantNameString} database={true} />;
|
145
144
|
}
|
146
|
-
case
|
145
|
+
case TENANT_DIAGNOSTICS_TABS_IDS.network: {
|
147
146
|
return <Network path={tenantNameString} />;
|
148
147
|
}
|
149
|
-
case
|
148
|
+
case TENANT_DIAGNOSTICS_TABS_IDS.describe: {
|
150
149
|
return <Describe tenant={tenantNameString} type={type} />;
|
151
150
|
}
|
152
|
-
case
|
151
|
+
case TENANT_DIAGNOSTICS_TABS_IDS.hotKeys: {
|
153
152
|
return <HotKeys type={type} />;
|
154
153
|
}
|
155
|
-
case
|
154
|
+
case TENANT_DIAGNOSTICS_TABS_IDS.graph: {
|
156
155
|
return <Heatmap path={currentSchemaPath} />;
|
157
156
|
}
|
158
|
-
case
|
157
|
+
case TENANT_DIAGNOSTICS_TABS_IDS.consumers: {
|
159
158
|
return <Consumers path={currentSchemaPath} type={type} />;
|
160
159
|
}
|
161
|
-
case
|
160
|
+
case TENANT_DIAGNOSTICS_TABS_IDS.partitions: {
|
162
161
|
return <Partitions path={currentSchemaPath} />;
|
163
162
|
}
|
164
163
|
default: {
|
@@ -1,80 +1,67 @@
|
|
1
|
+
import type {TenantDiagnosticsTab} from '../../../store/reducers/tenant/types';
|
2
|
+
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../store/reducers/tenant/constants';
|
1
3
|
import {EPathType} from '../../../types/api/schema';
|
2
4
|
|
3
|
-
export enum GeneralPagesIds {
|
4
|
-
'overview' = 'Overview',
|
5
|
-
'topQueries' = 'topQueries',
|
6
|
-
'topShards' = 'topShards',
|
7
|
-
'nodes' = 'Nodes',
|
8
|
-
'tablets' = 'Tablets',
|
9
|
-
'storage' = 'Storage',
|
10
|
-
'network' = 'Network',
|
11
|
-
'describe' = 'Describe',
|
12
|
-
'hotKeys' = 'hotKeys',
|
13
|
-
'graph' = 'graph',
|
14
|
-
'consumers' = 'consumers',
|
15
|
-
'partitions' = 'partitions',
|
16
|
-
}
|
17
|
-
|
18
5
|
type Page = {
|
19
|
-
id:
|
6
|
+
id: TenantDiagnosticsTab;
|
20
7
|
title: string;
|
21
8
|
};
|
22
9
|
|
23
10
|
const overview = {
|
24
|
-
id:
|
11
|
+
id: TENANT_DIAGNOSTICS_TABS_IDS.overview,
|
25
12
|
title: 'Info',
|
26
13
|
};
|
27
14
|
|
28
15
|
const topQueries = {
|
29
|
-
id:
|
16
|
+
id: TENANT_DIAGNOSTICS_TABS_IDS.topQueries,
|
30
17
|
title: 'Top queries',
|
31
18
|
};
|
32
19
|
|
33
20
|
const topShards = {
|
34
|
-
id:
|
21
|
+
id: TENANT_DIAGNOSTICS_TABS_IDS.topShards,
|
35
22
|
title: 'Top shards',
|
36
23
|
};
|
37
24
|
|
38
25
|
const nodes = {
|
39
|
-
id:
|
26
|
+
id: TENANT_DIAGNOSTICS_TABS_IDS.nodes,
|
40
27
|
title: 'Nodes',
|
41
28
|
};
|
42
29
|
|
43
30
|
const tablets = {
|
44
|
-
id:
|
31
|
+
id: TENANT_DIAGNOSTICS_TABS_IDS.tablets,
|
45
32
|
title: 'Tablets',
|
46
33
|
};
|
47
34
|
const storage = {
|
48
|
-
id:
|
35
|
+
id: TENANT_DIAGNOSTICS_TABS_IDS.storage,
|
49
36
|
title: 'Storage',
|
50
37
|
};
|
51
38
|
const network = {
|
52
|
-
id:
|
39
|
+
id: TENANT_DIAGNOSTICS_TABS_IDS.network,
|
53
40
|
title: 'Network',
|
54
41
|
};
|
55
42
|
|
56
43
|
const describe = {
|
57
|
-
id:
|
44
|
+
id: TENANT_DIAGNOSTICS_TABS_IDS.describe,
|
58
45
|
title: 'Describe',
|
59
46
|
};
|
60
47
|
|
61
48
|
const hotKeys = {
|
62
|
-
id:
|
49
|
+
id: TENANT_DIAGNOSTICS_TABS_IDS.hotKeys,
|
63
50
|
title: 'Hot keys',
|
64
51
|
};
|
65
52
|
|
66
53
|
const graph = {
|
67
|
-
id:
|
54
|
+
id: TENANT_DIAGNOSTICS_TABS_IDS.graph,
|
68
55
|
title: 'Graph',
|
69
56
|
};
|
70
57
|
|
71
58
|
const consumers = {
|
72
|
-
id:
|
59
|
+
id: TENANT_DIAGNOSTICS_TABS_IDS.consumers,
|
73
60
|
title: 'Consumers',
|
74
61
|
};
|
75
62
|
|
76
63
|
const partitions = {
|
77
|
-
id:
|
64
|
+
id: TENANT_DIAGNOSTICS_TABS_IDS.partitions,
|
78
65
|
title: 'Partitions',
|
79
66
|
};
|
80
67
|
|
@@ -9,7 +9,7 @@ import InfoViewer from '../../../../components/InfoViewer/InfoViewer';
|
|
9
9
|
import PoolUsage from '../../../../components/PoolUsage/PoolUsage';
|
10
10
|
import {Tablet} from '../../../../components/Tablet';
|
11
11
|
|
12
|
-
import {getTenantInfo} from '../../../../store/reducers/tenant';
|
12
|
+
import {getTenantInfo} from '../../../../store/reducers/tenant/tenant';
|
13
13
|
|
14
14
|
import {formatCPU} from '../../../../utils';
|
15
15
|
import {bytesToGB} from '../../../../utils/utils';
|
@@ -191,7 +191,7 @@ class TenantOverview extends React.Component {
|
|
191
191
|
}
|
192
192
|
|
193
193
|
function mapStateToProps(state) {
|
194
|
-
const {tenant = {}, loading,
|
194
|
+
const {tenant = {}, loading, error: {status} = {}} = state.tenant;
|
195
195
|
const {autorefresh} = state.schema;
|
196
196
|
|
197
197
|
return {
|
@@ -22,7 +22,9 @@ import type {KeyValueRow} from '../../../../types/api/query';
|
|
22
22
|
import type {EPathType} from '../../../../types/api/schema';
|
23
23
|
import type {ITopQueriesFilters} from '../../../../types/store/executeTopQueries';
|
24
24
|
import type {IQueryResult} from '../../../../types/store/query';
|
25
|
+
import type {TenantGeneralTab} from '../../../../store/reducers/tenant/types';
|
25
26
|
|
27
|
+
import {TENANT_GENERAL_TABS_IDS} from '../../../../store/reducers/tenant/constants';
|
26
28
|
import {formatDateTime, formatNumber} from '../../../../utils';
|
27
29
|
import {DEFAULT_TABLE_SETTINGS, HOUR_IN_SECONDS} from '../../../../utils/constants';
|
28
30
|
import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
|
@@ -30,7 +32,7 @@ import {prepareQueryError} from '../../../../utils/query';
|
|
30
32
|
import routes, {createHref} from '../../../../routes';
|
31
33
|
|
32
34
|
import {isColumnEntityType} from '../../utils/schema';
|
33
|
-
import {
|
35
|
+
import {TenantTabsGroups} from '../../TenantPages';
|
34
36
|
|
35
37
|
import i18n from './i18n';
|
36
38
|
import './TopQueries.scss';
|
@@ -86,7 +88,7 @@ const COLUMNS: Column<KeyValueRow>[] = [
|
|
86
88
|
|
87
89
|
interface TopQueriesProps {
|
88
90
|
path: string;
|
89
|
-
changeSchemaTab: (tab:
|
91
|
+
changeSchemaTab: (tab: TenantGeneralTab) => void;
|
90
92
|
type?: EPathType;
|
91
93
|
}
|
92
94
|
|
@@ -178,7 +180,7 @@ export const TopQueries = ({path, type}: TopQueriesProps) => {
|
|
178
180
|
|
179
181
|
const queryPath = createHref(routes.tenant, undefined, {
|
180
182
|
...queryParams,
|
181
|
-
[TenantTabsGroups.general]:
|
183
|
+
[TenantTabsGroups.general]: TENANT_GENERAL_TABS_IDS.query,
|
182
184
|
});
|
183
185
|
|
184
186
|
history.push(queryPath);
|
@@ -5,12 +5,11 @@ import cn from 'bem-cn-lite';
|
|
5
5
|
import {useThemeValue} from '@gravity-ui/uikit';
|
6
6
|
|
7
7
|
import type {EPathType} from '../../../types/api/schema';
|
8
|
+
import {TENANT_GENERAL_TABS_IDS} from '../../../store/reducers/tenant/constants';
|
8
9
|
|
9
10
|
import QueryEditor from '../QueryEditor/QueryEditor';
|
10
11
|
import Diagnostics from '../Diagnostics/Diagnostics';
|
11
12
|
|
12
|
-
import {TenantGeneralTabsIds} from '../TenantPages';
|
13
|
-
|
14
13
|
import './ObjectGeneral.scss';
|
15
14
|
|
16
15
|
const b = cn('object-general');
|
@@ -35,7 +34,7 @@ function ObjectGeneral(props: ObjectGeneralProps) {
|
|
35
34
|
const renderTabContent = () => {
|
36
35
|
const {type, additionalTenantInfo, additionalNodesInfo} = props;
|
37
36
|
switch (generalTab) {
|
38
|
-
case
|
37
|
+
case TENANT_GENERAL_TABS_IDS.query: {
|
39
38
|
return <QueryEditor path={tenantName as string} theme={theme} type={type} />;
|
40
39
|
}
|
41
40
|
default: {
|
@@ -35,7 +35,6 @@ import {
|
|
35
35
|
DEFAULT_SIZE_TENANT_SUMMARY_KEY,
|
36
36
|
} from '../../../utils/constants';
|
37
37
|
import {
|
38
|
-
TenantGeneralTabsIds,
|
39
38
|
TenantInfoTabsIds,
|
40
39
|
TenantTabsGroups,
|
41
40
|
TENANT_INFO_TABS,
|
@@ -48,7 +47,8 @@ import {
|
|
48
47
|
PaneVisibilityToggleButtons,
|
49
48
|
} from '../utils/paneVisibilityToggleHelpers';
|
50
49
|
import {setShowPreview} from '../../../store/reducers/schema';
|
51
|
-
import {setTopLevelTab} from '../../../store/reducers/tenant';
|
50
|
+
import {setTopLevelTab} from '../../../store/reducers/tenant/tenant';
|
51
|
+
import {TENANT_GENERAL_TABS_IDS} from '../../../store/reducers/tenant/constants';
|
52
52
|
|
53
53
|
import './ObjectSummary.scss';
|
54
54
|
|
@@ -275,7 +275,7 @@ function ObjectSummary(props: ObjectSummaryProps) {
|
|
275
275
|
|
276
276
|
const onOpenPreview = () => {
|
277
277
|
dispatch(setShowPreview(true));
|
278
|
-
dispatch(setTopLevelTab(
|
278
|
+
dispatch(setTopLevelTab(TENANT_GENERAL_TABS_IDS.query));
|
279
279
|
};
|
280
280
|
|
281
281
|
const renderCommonInfoControls = () => {
|
@@ -4,28 +4,27 @@ import cn from 'bem-cn-lite';
|
|
4
4
|
import {useLocation} from 'react-router';
|
5
5
|
import qs from 'qs';
|
6
6
|
|
7
|
-
import {
|
7
|
+
import type {TEvDescribeSchemeResult} from '../../types/api/schema';
|
8
8
|
|
9
|
+
import {DEFAULT_IS_TENANT_SUMMARY_COLLAPSED, DEFAULT_SIZE_TENANT_KEY} from '../../utils/constants';
|
10
|
+
import {useTypedSelector} from '../../utils/hooks';
|
11
|
+
import routes, {CLUSTER_PAGES, createHref} from '../../routes';
|
9
12
|
import {setHeader} from '../../store/reducers/header';
|
13
|
+
import {disableAutorefresh, getSchema, resetLoadingState} from '../../store/reducers/schema';
|
14
|
+
import {getSchemaAcl} from '../../store/reducers/schemaAcl';
|
15
|
+
import {getTenantInfo, clearTenant} from '../../store/reducers/tenant/tenant';
|
16
|
+
|
17
|
+
import SplitPane from '../../components/SplitPane';
|
18
|
+
import {AccessDenied} from '../../components/Errors/403';
|
19
|
+
|
10
20
|
import ObjectGeneralTabs from './ObjectGeneralTabs/ObjectGeneralTabs';
|
11
21
|
import ObjectSummary from './ObjectSummary/ObjectSummary';
|
12
22
|
import ObjectGeneral from './ObjectGeneral/ObjectGeneral';
|
13
|
-
|
14
|
-
import SplitPane from '../../components/SplitPane';
|
15
|
-
//@ts-ignore
|
16
|
-
import {DEFAULT_IS_TENANT_SUMMARY_COLLAPSED, DEFAULT_SIZE_TENANT_KEY} from '../../utils/constants';
|
17
|
-
//@ts-ignore
|
18
|
-
import {disableAutorefresh, getSchema, resetLoadingState} from '../../store/reducers/schema';
|
19
|
-
//@ts-ignore
|
20
|
-
import {getSchemaAcl} from '../../store/reducers/schemaAcl';
|
23
|
+
|
21
24
|
import {
|
22
25
|
PaneVisibilityActionTypes,
|
23
26
|
paneVisibilityToggleReducerCreator,
|
24
27
|
} from './utils/paneVisibilityToggleHelpers';
|
25
|
-
//@ts-ignore
|
26
|
-
import {getTenantInfo, clearTenant} from '../../store/reducers/tenant';
|
27
|
-
import routes, {CLUSTER_PAGES, createHref} from '../../routes';
|
28
|
-
import type {TEvDescribeSchemeResult} from '../../types/api/schema';
|
29
28
|
|
30
29
|
import './Tenant.scss';
|
31
30
|
|
@@ -63,8 +62,8 @@ function Tenant(props: TenantProps) {
|
|
63
62
|
const {PathType: currentPathType, PathSubType: currentPathSubType} =
|
64
63
|
(currentItem as TEvDescribeSchemeResult).PathDescription?.Self || {};
|
65
64
|
|
66
|
-
const {
|
67
|
-
const {error: {status: schemaStatus = 200} = {}} =
|
65
|
+
const {error: {status: tenantStatus = 200} = {}} = useTypedSelector((state) => state.tenant);
|
66
|
+
const {error: {status: schemaStatus = 200} = {}} = useTypedSelector((state) => state.schema);
|
68
67
|
|
69
68
|
const dispatch = useDispatch();
|
70
69
|
|
@@ -1,9 +1,6 @@
|
|
1
1
|
import {Icon} from '../../components/Icon';
|
2
|
+
import {TENANT_GENERAL_TABS_IDS} from '../../store/reducers/tenant/constants';
|
2
3
|
|
3
|
-
export enum TenantGeneralTabsIds {
|
4
|
-
query = 'query',
|
5
|
-
diagnostics = 'diagnostics',
|
6
|
-
}
|
7
4
|
export enum TenantInfoTabsIds {
|
8
5
|
overview = 'overview',
|
9
6
|
acl = 'acl',
|
@@ -18,12 +15,12 @@ export enum TenantTabsGroups {
|
|
18
15
|
|
19
16
|
export const TENANT_GENERAL_TABS = [
|
20
17
|
{
|
21
|
-
id:
|
18
|
+
id: TENANT_GENERAL_TABS_IDS.query,
|
22
19
|
title: 'Query',
|
23
20
|
icon: <Icon name="query" viewBox="0 0 16 16" />,
|
24
21
|
},
|
25
22
|
{
|
26
|
-
id:
|
23
|
+
id: TENANT_GENERAL_TABS_IDS.diagnostics,
|
27
24
|
title: 'Diagnostics',
|
28
25
|
icon: <Icon name="diagnostics" viewBox="0 0 17 16" />,
|
29
26
|
},
|
@@ -3,9 +3,9 @@ import type {NavigationTreeNodeType, NavigationTreeProps} from 'ydb-ui-component
|
|
3
3
|
|
4
4
|
import {changeUserInput} from '../../../store/reducers/executeQuery';
|
5
5
|
import {setShowPreview} from '../../../store/reducers/schema';
|
6
|
-
import {setTopLevelTab} from '../../../store/reducers/tenant';
|
6
|
+
import {setTopLevelTab} from '../../../store/reducers/tenant/tenant';
|
7
|
+
import {TENANT_GENERAL_TABS_IDS} from '../../../store/reducers/tenant/constants';
|
7
8
|
import createToast from '../../../utils/createToast';
|
8
|
-
import {TenantGeneralTabsIds} from '../TenantPages';
|
9
9
|
|
10
10
|
const createTableTemplate = (path: string) => {
|
11
11
|
return `CREATE TABLE \`${path}/my_table\`
|
@@ -37,7 +37,7 @@ const bindActions = (
|
|
37
37
|
) => {
|
38
38
|
const inputQuery = (tmpl: (path: string) => string) => () => {
|
39
39
|
dispatch(changeUserInput({input: tmpl(path)}));
|
40
|
-
dispatch(setTopLevelTab(
|
40
|
+
dispatch(setTopLevelTab(TENANT_GENERAL_TABS_IDS.query));
|
41
41
|
setActivePath(path);
|
42
42
|
};
|
43
43
|
|
@@ -66,7 +66,7 @@ const bindActions = (
|
|
66
66
|
},
|
67
67
|
openPreview: () => {
|
68
68
|
dispatch(setShowPreview(true));
|
69
|
-
dispatch(setTopLevelTab(
|
69
|
+
dispatch(setTopLevelTab(TENANT_GENERAL_TABS_IDS.query));
|
70
70
|
setActivePath(path);
|
71
71
|
},
|
72
72
|
};
|
package/dist/routes.ts
CHANGED
@@ -3,7 +3,7 @@ import {combineReducers} from 'redux';
|
|
3
3
|
import nodes from './nodes';
|
4
4
|
import cluster from './cluster/cluster';
|
5
5
|
import clusterNodes from './clusterNodes/clusterNodes';
|
6
|
-
import tenant from './tenant';
|
6
|
+
import tenant from './tenant/tenant';
|
7
7
|
import storage from './storage';
|
8
8
|
import node from './node';
|
9
9
|
import tooltip from './tooltip';
|
@@ -0,0 +1,19 @@
|
|
1
|
+
export const TENANT_GENERAL_TABS_IDS = {
|
2
|
+
query: 'query',
|
3
|
+
diagnostics: 'diagnostics',
|
4
|
+
} as const;
|
5
|
+
|
6
|
+
export const TENANT_DIAGNOSTICS_TABS_IDS = {
|
7
|
+
overview: 'overview',
|
8
|
+
topQueries: 'topQueries',
|
9
|
+
topShards: 'topShards',
|
10
|
+
nodes: 'nodes',
|
11
|
+
tablets: 'tablets',
|
12
|
+
storage: 'storage',
|
13
|
+
network: 'network',
|
14
|
+
describe: 'describe',
|
15
|
+
hotKeys: 'hotKeys',
|
16
|
+
graph: 'graph',
|
17
|
+
consumers: 'consumers',
|
18
|
+
partitions: 'partitions',
|
19
|
+
} as const;
|
@@ -1,13 +1,20 @@
|
|
1
|
-
import {
|
2
|
-
|
3
|
-
import
|
1
|
+
import type {Reducer} from 'redux';
|
2
|
+
|
3
|
+
import type {TTenant} from '../../../types/api/tenant';
|
4
|
+
import type {TenantAction, TenantDiagnosticsTab, TenantGeneralTab, TenantState} from './types';
|
5
|
+
|
6
|
+
import '../../../services/api';
|
7
|
+
import {createRequestActionTypes, createApiRequest} from '../../utils';
|
8
|
+
|
9
|
+
export const FETCH_TENANT = createRequestActionTypes('tenant', 'FETCH_TENANT');
|
4
10
|
|
5
|
-
const FETCH_TENANT = createRequestActionTypes('tenant', 'FETCH_TENANT');
|
6
11
|
const SET_TOP_LEVEL_TAB = 'tenant/SET_TOP_LEVEL_TAB';
|
7
12
|
const SET_DIAGNOSTICS_TAB = 'tenant/SET_DIAGNOSTICS_TAB';
|
8
13
|
const CLEAR_TENANT = 'tenant/CLEAR_TENANT';
|
9
14
|
|
10
|
-
const
|
15
|
+
const initialState = {loading: false, wasLoaded: false};
|
16
|
+
|
17
|
+
const tenantReducer: Reducer<TenantState, TenantAction> = (state = initialState, action) => {
|
11
18
|
switch (action.type) {
|
12
19
|
case FETCH_TENANT.REQUEST: {
|
13
20
|
return {
|
@@ -17,12 +24,9 @@ const tenantReducer = (state = {loading: false, wasLoaded: false, tenant: {}}, a
|
|
17
24
|
}
|
18
25
|
|
19
26
|
case FETCH_TENANT.SUCCESS: {
|
20
|
-
const {tenant, tenantNodes} = action.data;
|
21
|
-
|
22
27
|
return {
|
23
28
|
...state,
|
24
|
-
tenant,
|
25
|
-
tenantNodes,
|
29
|
+
tenant: action.data,
|
26
30
|
loading: false,
|
27
31
|
wasLoaded: true,
|
28
32
|
error: undefined,
|
@@ -32,7 +36,7 @@ const tenantReducer = (state = {loading: false, wasLoaded: false, tenant: {}}, a
|
|
32
36
|
case FETCH_TENANT.FAILURE: {
|
33
37
|
return {
|
34
38
|
...state,
|
35
|
-
|
39
|
+
error: action.error,
|
36
40
|
loading: false,
|
37
41
|
wasLoaded: true,
|
38
42
|
};
|
@@ -41,7 +45,7 @@ const tenantReducer = (state = {loading: false, wasLoaded: false, tenant: {}}, a
|
|
41
45
|
case CLEAR_TENANT: {
|
42
46
|
return {
|
43
47
|
...state,
|
44
|
-
tenant:
|
48
|
+
tenant: undefined,
|
45
49
|
loading: true,
|
46
50
|
};
|
47
51
|
}
|
@@ -65,45 +69,32 @@ const tenantReducer = (state = {loading: false, wasLoaded: false, tenant: {}}, a
|
|
65
69
|
}
|
66
70
|
};
|
67
71
|
|
68
|
-
export const
|
69
|
-
return {type: CLEAR_TENANT};
|
70
|
-
};
|
71
|
-
|
72
|
-
export const getTenantInfo = ({path}) => {
|
72
|
+
export const getTenantInfo = ({path}: {path: string}) => {
|
73
73
|
return createApiRequest({
|
74
|
-
request:
|
74
|
+
request: window.api.getTenantInfo({path}),
|
75
75
|
actions: FETCH_TENANT,
|
76
|
-
dataHandler: (
|
77
|
-
|
78
|
-
const tenantNodes = _.map(nodesData?.Tenants[0]?.Nodes, (item) => {
|
79
|
-
if (item.Host && item.Endpoints) {
|
80
|
-
const address = _.find(item.Endpoints, {Name: 'http-mon'})?.Address;
|
81
|
-
return {
|
82
|
-
id: item.NodeId,
|
83
|
-
backend: `${item.Host}${address ? address : ''}`,
|
84
|
-
};
|
85
|
-
}
|
86
|
-
|
87
|
-
return undefined;
|
88
|
-
}).filter(Boolean);
|
89
|
-
|
90
|
-
return {tenant, tenantNodes};
|
76
|
+
dataHandler: (tenantData): TTenant | undefined => {
|
77
|
+
return tenantData.TenantInfo?.[0];
|
91
78
|
},
|
92
79
|
});
|
93
80
|
};
|
94
81
|
|
95
|
-
export
|
82
|
+
export const clearTenant = () => {
|
83
|
+
return {type: CLEAR_TENANT} as const;
|
84
|
+
};
|
85
|
+
|
86
|
+
export function setTopLevelTab(tab: TenantGeneralTab) {
|
96
87
|
return {
|
97
88
|
type: SET_TOP_LEVEL_TAB,
|
98
89
|
data: tab,
|
99
|
-
};
|
90
|
+
} as const;
|
100
91
|
}
|
101
92
|
|
102
|
-
export function setDiagnosticsTab(tab) {
|
93
|
+
export function setDiagnosticsTab(tab: TenantDiagnosticsTab) {
|
103
94
|
return {
|
104
95
|
type: SET_DIAGNOSTICS_TAB,
|
105
96
|
data: tab,
|
106
|
-
};
|
97
|
+
} as const;
|
107
98
|
}
|
108
99
|
|
109
100
|
export default tenantReducer;
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import type {IResponseError} from '../../../types/api/error';
|
2
|
+
import type {TTenant} from '../../../types/api/tenant';
|
3
|
+
import type {ValueOf} from '../../../types/common';
|
4
|
+
import type {ApiRequestAction} from '../../utils';
|
5
|
+
|
6
|
+
import {TENANT_DIAGNOSTICS_TABS_IDS, TENANT_GENERAL_TABS_IDS} from './constants';
|
7
|
+
import {FETCH_TENANT, clearTenant, setDiagnosticsTab, setTopLevelTab} from './tenant';
|
8
|
+
|
9
|
+
export type TenantGeneralTab = ValueOf<typeof TENANT_GENERAL_TABS_IDS>;
|
10
|
+
export type TenantDiagnosticsTab = ValueOf<typeof TENANT_DIAGNOSTICS_TABS_IDS>;
|
11
|
+
|
12
|
+
export interface TenantState {
|
13
|
+
loading: boolean;
|
14
|
+
wasLoaded: boolean;
|
15
|
+
topLevelTab?: TenantGeneralTab;
|
16
|
+
diagnosticsTab?: TenantDiagnosticsTab;
|
17
|
+
tenant?: TTenant;
|
18
|
+
error?: IResponseError;
|
19
|
+
}
|
20
|
+
|
21
|
+
export type TenantAction =
|
22
|
+
| ApiRequestAction<typeof FETCH_TENANT, TTenant | undefined, IResponseError>
|
23
|
+
| ReturnType<typeof clearTenant>
|
24
|
+
| ReturnType<typeof setTopLevelTab>
|
25
|
+
| ReturnType<typeof setDiagnosticsTab>;
|
package/dist/utils/index.js
CHANGED
@@ -97,7 +97,8 @@ export const formatDateTime = (value) => {
|
|
97
97
|
|
98
98
|
export const calcUptimeInSeconds = (milliseconds) => {
|
99
99
|
const currentDate = new Date();
|
100
|
-
|
100
|
+
const diff = currentDate - Number(milliseconds);
|
101
|
+
return diff <= 0 ? 0 : diff / 1000;
|
101
102
|
};
|
102
103
|
|
103
104
|
export const calcUptime = (milliseconds) => {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "ydb-embedded-ui",
|
3
|
-
"version": "4.5.
|
3
|
+
"version": "4.5.1",
|
4
4
|
"files": [
|
5
5
|
"dist"
|
6
6
|
],
|
@@ -48,6 +48,7 @@
|
|
48
48
|
"//build:embedded": "echo 'PUBLIC_URL is a setting for create-react-app. Embedded version is built and hosted as is on ydb servers, with no way of knowing the final URL pattern. PUBLIC_URL=. keeps paths to all static relative, allowing servers to handle them as needed'",
|
49
49
|
"build:embedded": "rm -rf build && PUBLIC_URL=. REACT_APP_BACKEND=http://localhost:8765 npm run build",
|
50
50
|
"lint:styles": "stylelint 'src/**/*.scss'",
|
51
|
+
"unimported": "npx unimported --no-cache",
|
51
52
|
"package": "rm -rf dist && copyfiles -u 1 'src/**/*' dist",
|
52
53
|
"test": "react-app-rewired test",
|
53
54
|
"eject": "react-scripts eject",
|
@@ -1,25 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import PropTypes from 'prop-types';
|
3
|
-
import cn from 'bem-cn-lite';
|
4
|
-
import {Breadcrumbs as BreadcrumbsUiKit} from '@gravity-ui/uikit';
|
5
|
-
|
6
|
-
import './Breadcrumbs.scss';
|
7
|
-
|
8
|
-
const b = cn('kv-breadcrumbs');
|
9
|
-
|
10
|
-
class Breadcrumbs extends React.Component {
|
11
|
-
static propTypes = {
|
12
|
-
items: PropTypes.array,
|
13
|
-
};
|
14
|
-
|
15
|
-
static defaultProps = {
|
16
|
-
items: [],
|
17
|
-
};
|
18
|
-
|
19
|
-
render() {
|
20
|
-
const {items} = this.props;
|
21
|
-
return <BreadcrumbsUiKit items={items} firstDisplayedItemsCount={1} className={b()} />;
|
22
|
-
}
|
23
|
-
}
|
24
|
-
|
25
|
-
export default Breadcrumbs;
|
@@ -1,84 +0,0 @@
|
|
1
|
-
import {useState, useEffect, useRef} from 'react';
|
2
|
-
import block from 'bem-cn-lite';
|
3
|
-
import {ArrowToggle, Button} from '@gravity-ui/uikit';
|
4
|
-
|
5
|
-
import './Collapse.scss';
|
6
|
-
|
7
|
-
const b = block('yc-collapse');
|
8
|
-
|
9
|
-
function useEffectSkipFirst(fn, arr) {
|
10
|
-
const isFirst = useRef(true);
|
11
|
-
|
12
|
-
useEffect(() => {
|
13
|
-
if (isFirst.current) {
|
14
|
-
isFirst.current = false;
|
15
|
-
return;
|
16
|
-
}
|
17
|
-
|
18
|
-
return fn();
|
19
|
-
}, arr);
|
20
|
-
}
|
21
|
-
|
22
|
-
export const Collapse = ({
|
23
|
-
title,
|
24
|
-
children,
|
25
|
-
arrowView = 'icon',
|
26
|
-
emptyState = 'No data',
|
27
|
-
titleSize = 'l',
|
28
|
-
contentMarginTop = 12,
|
29
|
-
defaultIsExpand,
|
30
|
-
onChange,
|
31
|
-
}) => {
|
32
|
-
const [isExpand, setIsExpand] = useState(defaultIsExpand);
|
33
|
-
|
34
|
-
const arrowDirection = isExpand ? 'top' : 'bottom';
|
35
|
-
|
36
|
-
const arrowComponent =
|
37
|
-
arrowView === 'button' ? (
|
38
|
-
<Button view="flat" className={b('arrow-button')}>
|
39
|
-
<ArrowToggle className={b('arrow')} direction={arrowDirection} size={20} />
|
40
|
-
</Button>
|
41
|
-
) : (
|
42
|
-
<ArrowToggle className={b('arrow')} direction={arrowDirection} size={20} />
|
43
|
-
);
|
44
|
-
|
45
|
-
useEffectSkipFirst(onChange, [isExpand]);
|
46
|
-
|
47
|
-
return (
|
48
|
-
<div className={b()}>
|
49
|
-
<div
|
50
|
-
className={b('panel', {
|
51
|
-
'no-data': !children,
|
52
|
-
})}
|
53
|
-
onClick={() => {
|
54
|
-
setIsExpand(!isExpand);
|
55
|
-
}}
|
56
|
-
>
|
57
|
-
{typeof title === 'string' ? (
|
58
|
-
<h2
|
59
|
-
className={b('title', {
|
60
|
-
size: titleSize,
|
61
|
-
})}
|
62
|
-
>
|
63
|
-
{title}
|
64
|
-
</h2>
|
65
|
-
) : (
|
66
|
-
title
|
67
|
-
)}
|
68
|
-
|
69
|
-
{children && <div className={b('arrow-wrapper')}>{arrowComponent}</div>}
|
70
|
-
</div>
|
71
|
-
|
72
|
-
{!children && <h4 className={b('empty-state-title')}>{emptyState}</h4>}
|
73
|
-
|
74
|
-
{children && (
|
75
|
-
<div
|
76
|
-
className={b('content', {visible: isExpand})}
|
77
|
-
style={{marginTop: contentMarginTop}}
|
78
|
-
>
|
79
|
-
{children}
|
80
|
-
</div>
|
81
|
-
)}
|
82
|
-
</div>
|
83
|
-
);
|
84
|
-
};
|
@@ -1,70 +0,0 @@
|
|
1
|
-
.yc-collapse {
|
2
|
-
&__panel {
|
3
|
-
display: inline-flex;
|
4
|
-
align-items: center;
|
5
|
-
|
6
|
-
cursor: pointer;
|
7
|
-
|
8
|
-
&_no-data {
|
9
|
-
cursor: default;
|
10
|
-
}
|
11
|
-
}
|
12
|
-
|
13
|
-
&__title {
|
14
|
-
margin: 0;
|
15
|
-
}
|
16
|
-
|
17
|
-
&__title_secondary {
|
18
|
-
color: var(--yc-color-text-secondary);
|
19
|
-
}
|
20
|
-
|
21
|
-
&__title_size {
|
22
|
-
&_s {
|
23
|
-
font-size: var(--yc-text-body-1-font-size);
|
24
|
-
line-height: var(--yc-text-body-1-line-height);
|
25
|
-
}
|
26
|
-
|
27
|
-
&_m {
|
28
|
-
font-size: var(--yc-text-body-2-font-size);
|
29
|
-
line-height: var(--yc-text-body-2-line-height);
|
30
|
-
}
|
31
|
-
|
32
|
-
&_l {
|
33
|
-
font-size: var(--yc-text-body-3-font-size);
|
34
|
-
line-height: var(--yc-text-body-3-line-height);
|
35
|
-
}
|
36
|
-
}
|
37
|
-
|
38
|
-
&__arrow-wrapper {
|
39
|
-
margin-left: 8px;
|
40
|
-
}
|
41
|
-
|
42
|
-
&__arrow-wrapper_secondary {
|
43
|
-
color: var(--yc-color-text-secondary);
|
44
|
-
.button2__text {
|
45
|
-
color: var(--yc-color-text-secondary);
|
46
|
-
}
|
47
|
-
}
|
48
|
-
|
49
|
-
&__content {
|
50
|
-
display: none;
|
51
|
-
|
52
|
-
&_visible {
|
53
|
-
display: block;
|
54
|
-
}
|
55
|
-
}
|
56
|
-
|
57
|
-
&__empty-state-title {
|
58
|
-
font-size: var(--yc-text-body-1-font-size);
|
59
|
-
font-weight: normal;
|
60
|
-
line-height: var(--yc-text-body-1-line-height);
|
61
|
-
|
62
|
-
opacity: 0.5;
|
63
|
-
}
|
64
|
-
|
65
|
-
&__arrow-button {
|
66
|
-
.button2__text {
|
67
|
-
margin: 0 4px;
|
68
|
-
}
|
69
|
-
}
|
70
|
-
}
|