Skip to content

Commit 0667994

Browse files
committed
VTAdmin(web): Make screens with JSON output more readable
Signed-off-by: Noble Mittal <[email protected]>
1 parent a5cf92c commit 0667994

File tree

8 files changed

+110
-16
lines changed

8 files changed

+110
-16
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* Copyright 2025 The Vitess Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React, { useState } from 'react';
18+
import { JSONTree } from 'react-json-tree';
19+
20+
const vtAdminTheme = {
21+
scheme: 'vtadmin',
22+
author: 'custom',
23+
base00: '#ffffff',
24+
base01: '#f6f8fa',
25+
base02: '#e6e8eb',
26+
base03: '#8c8c8c',
27+
base04: '#3c3c3c',
28+
base05: '#2c2c2c',
29+
base06: '#0057b8',
30+
base07: '#000000',
31+
base08: '#00875a',
32+
base09: '#2c2c2c',
33+
base0A: '#e44d26',
34+
base0B: '#2c2c2c',
35+
base0C: '#1a73e8',
36+
base0D: '#3d5afe',
37+
base0E: '#3cba54',
38+
base0F: '#ff6f61',
39+
};
40+
41+
interface JSONViewTreeProps {
42+
data: any;
43+
}
44+
45+
const JSONViewTree: React.FC<JSONViewTreeProps> = ({ data }) => {
46+
const [expandAll, setExpandAll] = useState(false);
47+
const [treeKey, setTreeKey] = useState(0);
48+
49+
const handleExpand = () => {
50+
setExpandAll(true);
51+
setTreeKey((prev) => prev + 1);
52+
};
53+
54+
const handleCollapse = () => {
55+
setExpandAll(false);
56+
setTreeKey((prev) => prev + 1);
57+
};
58+
59+
const getItemString = (type: string, data: any) => {
60+
if (Array.isArray(data)) {
61+
return `${type}[${data.length}]`;
62+
}
63+
return type;
64+
};
65+
66+
if (!data) return null;
67+
return (
68+
<div className="p-1">
69+
<div className="flex mt-2 gap-2">
70+
<button onClick={handleExpand} className="btn btn-secondary btn-sm">
71+
Expand All
72+
</button>
73+
<button onClick={handleCollapse} className="btn btn-danger bg-transparent text-red-500 btn-sm">
74+
Collapse All
75+
</button>
76+
</div>
77+
<JSONTree
78+
key={treeKey}
79+
data={data}
80+
theme={{
81+
extend: vtAdminTheme,
82+
nestedNodeItemString: {
83+
color: vtAdminTheme.base0C,
84+
},
85+
}}
86+
invertTheme={false}
87+
hideRoot={true}
88+
getItemString={getItemString}
89+
shouldExpandNodeInitially={() => expandAll}
90+
/>
91+
</div>
92+
);
93+
};
94+
95+
export default JSONViewTree;

web/vtadmin/src/components/routes/VTExplain.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import style from './VTExplain.module.scss';
2626
import { Code } from '../Code';
2727
import { useDocumentTitle } from '../../hooks/useDocumentTitle';
2828
import { Label } from '../inputs/Label';
29+
import JSONViewTree from '../jsonViewTree/JSONViewTree';
2930

3031
// TODO(doeg): persist form data in URL, error handling, loading state, um... hm. Most things still need doing.
3132
// This whole component is the hastiest prototype ever. :')
@@ -107,7 +108,7 @@ export const VTExplain = () => {
107108

108109
{error && (
109110
<section className={style.errorPanel}>
110-
<Code code={JSON.stringify(error, null, 2)} />
111+
<JSONViewTree data={error} />
111112
</section>
112113
)}
113114

web/vtadmin/src/components/routes/keyspace/Keyspace.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { Link, Redirect, Route } from 'react-router-dom';
1919
import { useKeyspace } from '../../../hooks/api';
2020
import { useDocumentTitle } from '../../../hooks/useDocumentTitle';
2121
import { isReadOnlyMode } from '../../../util/env';
22-
import { Code } from '../../Code';
2322
import { ContentContainer } from '../../layout/ContentContainer';
2423
import { NavCrumbs } from '../../layout/NavCrumbs';
2524
import { WorkspaceHeader } from '../../layout/WorkspaceHeader';
@@ -32,6 +31,7 @@ import { Advanced } from './Advanced';
3231
import style from './Keyspace.module.scss';
3332
import { KeyspaceShards } from './KeyspaceShards';
3433
import { KeyspaceVSchema } from './KeyspaceVSchema';
34+
import JSONViewTree from '../../jsonViewTree/JSONViewTree';
3535

3636
interface RouteParams {
3737
clusterID: string;
@@ -111,7 +111,7 @@ export const Keyspace = () => {
111111

112112
<Route path={`${path}/json`}>
113113
<QueryLoadingPlaceholder query={kq} />
114-
<Code code={JSON.stringify(keyspace, null, 2)} />
114+
<JSONViewTree data={keyspace} />
115115
</Route>
116116

117117
{!isReadOnlyMode() && (

web/vtadmin/src/components/routes/keyspace/KeyspaceVSchema.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import { useVSchema } from '../../../hooks/api';
18-
import { Code } from '../../Code';
18+
import JSONViewTree from '../../jsonViewTree/JSONViewTree';
1919
import { QueryErrorPlaceholder } from '../../placeholders/QueryErrorPlaceholder';
2020
import { QueryLoadingPlaceholder } from '../../placeholders/QueryLoadingPlaceholder';
2121

@@ -30,7 +30,7 @@ export const KeyspaceVSchema = ({ clusterID, name }: Props) => {
3030
<div>
3131
<QueryLoadingPlaceholder query={query} />
3232
<QueryErrorPlaceholder query={query} title="Couldn't load VSchema" />
33-
{query.isSuccess && <Code code={JSON.stringify(query.data, null, 2)} />}
33+
{query.isSuccess && <JSONViewTree data={query.data} />}
3434
</div>
3535
);
3636
};

web/vtadmin/src/components/routes/shard/Shard.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ import { WorkspaceTitle } from '../../layout/WorkspaceTitle';
2424
import { ContentContainer } from '../../layout/ContentContainer';
2525
import { Tab } from '../../tabs/Tab';
2626
import { TabContainer } from '../../tabs/TabContainer';
27-
import { Code } from '../../Code';
2827
import { useDocumentTitle } from '../../../hooks/useDocumentTitle';
2928
import { KeyspaceLink } from '../../links/KeyspaceLink';
3029
import { useKeyspace } from '../../../hooks/api';
3130
import { ShardTablets } from './ShardTablets';
3231
import Advanced from './Advanced';
32+
import JSONViewTree from '../../jsonViewTree/JSONViewTree';
3333

3434
interface RouteParams {
3535
clusterID: string;
@@ -122,7 +122,7 @@ export const Shard = () => {
122122
<ShardTablets {...params} />
123123
</Route>
124124

125-
<Route path={`${path}/json`}>{shard && <Code code={JSON.stringify(shard, null, 2)} />}</Route>
125+
<Route path={`${path}/json`}>{shard && <JSONViewTree data={shard} />}</Route>
126126
<Route path={`${path}/advanced`}>
127127
<Advanced />
128128
</Route>

web/vtadmin/src/components/routes/stream/Stream.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ import { Link, useParams } from 'react-router-dom';
1818
import { useWorkflow } from '../../../hooks/api';
1919
import { useDocumentTitle } from '../../../hooks/useDocumentTitle';
2020
import { formatStreamKey, getStream } from '../../../util/workflows';
21-
import { Code } from '../../Code';
2221
import { ContentContainer } from '../../layout/ContentContainer';
2322
import { NavCrumbs } from '../../layout/NavCrumbs';
2423
import { WorkspaceHeader } from '../../layout/WorkspaceHeader';
2524
import { WorkspaceTitle } from '../../layout/WorkspaceTitle';
2625
import style from './Stream.module.scss';
26+
import JSONViewTree from '../../jsonViewTree/JSONViewTree';
2727

2828
interface RouteParams {
2929
clusterID: string;
@@ -72,7 +72,7 @@ export const Stream = () => {
7272
</div>
7373
</WorkspaceHeader>
7474
<ContentContainer>
75-
<Code code={JSON.stringify(stream, null, 2)} />
75+
<JSONViewTree data={stream} />
7676
</ContentContainer>
7777
</div>
7878
);

web/vtadmin/src/components/routes/tablet/Tablet.tsx

+3-5
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { useExperimentalTabletDebugVars, useTablet } from '../../../hooks/api';
1919
import { useDocumentTitle } from '../../../hooks/useDocumentTitle';
2020
import { isReadOnlyMode } from '../../../util/env';
2121
import { formatDisplayType, formatState } from '../../../util/tablets';
22-
import { Code } from '../../Code';
2322
import { ContentContainer } from '../../layout/ContentContainer';
2423
import { NavCrumbs } from '../../layout/NavCrumbs';
2524
import { WorkspaceHeader } from '../../layout/WorkspaceHeader';
@@ -34,6 +33,7 @@ import style from './Tablet.module.scss';
3433
import { TabletCharts } from './TabletCharts';
3534
import { env } from '../../../util/env';
3635
import FullStatus from './FullStatus';
36+
import JSONViewTree from '../../jsonViewTree/JSONViewTree';
3737

3838
interface RouteParams {
3939
alias: string;
@@ -120,11 +120,9 @@ export const Tablet = () => {
120120

121121
<Route path={`${path}/json`}>
122122
<div>
123-
<Code code={JSON.stringify(tablet, null, 2)} />
123+
<JSONViewTree data={tablet} />
124124

125-
{env().VITE_ENABLE_EXPERIMENTAL_TABLET_DEBUG_VARS && (
126-
<Code code={JSON.stringify(debugVars, null, 2)} />
127-
)}
125+
{env().VITE_ENABLE_EXPERIMENTAL_TABLET_DEBUG_VARS && <JSONViewTree data={debugVars} />}
128126
</div>
129127
</Route>
130128

web/vtadmin/src/components/routes/workflow/Workflow.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ import { ContentContainer } from '../../layout/ContentContainer';
3030
import { TabContainer } from '../../tabs/TabContainer';
3131
import { Tab } from '../../tabs/Tab';
3232
import { getStreams } from '../../../util/workflows';
33-
import { Code } from '../../Code';
3433
import { ShardLink } from '../../links/ShardLink';
3534
import { WorkflowVDiff } from './WorkflowVDiff';
3635
import { Select } from '../../inputs/Select';
3736
import { formatDateTimeShort } from '../../../util/time';
37+
import JSONViewTree from '../../jsonViewTree/JSONViewTree';
3838

3939
interface RouteParams {
4040
clusterID: string;
@@ -189,7 +189,7 @@ export const Workflow = () => {
189189
</Route>
190190

191191
<Route path={`${path}/json`}>
192-
<Code code={JSON.stringify(data, null, 2)} />
192+
<JSONViewTree data={data} />
193193
</Route>
194194

195195
<Redirect exact from={path} to={`${path}/streams`} />

0 commit comments

Comments
 (0)