This is an automated email from the ASF dual-hosted git repository.
arafat2198 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 32a8c093e3 HDDS-11160. Improve Insights page UI (#7327)
32a8c093e3 is described below
commit 32a8c093e3b4ec072fa3d8823d169a9284e4d4a8
Author: Abhishek Pal <[email protected]>
AuthorDate: Fri Oct 25 17:29:55 2024 +0530
HDDS-11160. Improve Insights page UI (#7327)
---
.../webapps/recon/ozone-recon-web/api/db.json | 643 +++++++++------------
.../webapps/recon/ozone-recon-web/src/app.tsx | 3 +-
.../src/constants/breadcrumbs.constants.tsx | 2 +-
.../src/v2/components/breadcrumbs/breadcrumbs.tsx | 55 ++
.../overviewCard/overviewSummaryCard.tsx | 10 +-
.../{duPieChart => plots}/duPieChart.tsx | 0
.../v2/components/plots/insightsContainerPlot.tsx | 149 +++++
.../src/v2/components/plots/insightsFilePlot.tsx | 251 ++++++++
.../src/v2/components/select/multiSelect.tsx | 56 +-
.../tables/insights/containerMismatchTable.tsx | 215 +++++++
.../tables/insights/deletePendingDirsTable.tsx | 142 +++++
.../tables/insights/deletePendingKeysTable.tsx | 194 +++++++
.../tables/insights/deletedContainerKeysTable.tsx | 163 ++++++
.../tables/insights/expandedKeyTable.tsx | 93 +++
.../tables/insights/expandedPendingKeysTable.tsx | 81 +++
.../components/tables/insights/openKeysTable.tsx | 213 +++++++
.../src/v2/components/tables/volumesTable.tsx | 1 -
.../{ => v2}/constants/breadcrumbs.constants.tsx | 7 +-
.../constants/limit.constants.tsx} | 36 +-
.../src/v2/pages/buckets/buckets.tsx | 21 +-
.../src/v2/pages/diskUsage/diskUsage.tsx | 2 +-
.../src/v2/pages/insights/insights.less | 36 ++
.../src/v2/pages/insights/insights.tsx | 196 +++++++
.../src/v2/pages/insights/omInsights.tsx | 208 +++++++
.../src/v2/pages/overview/overview.tsx | 6 +-
.../src/v2/pages/volumes/volumes.tsx | 8 +-
.../recon/ozone-recon-web/src/v2/routes-v2.tsx | 10 +
.../ozone-recon-web/src/v2/types/insights.types.ts | 199 +++++++
28 files changed, 2538 insertions(+), 462 deletions(-)
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json
index c620202767..784ee8302e 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json
@@ -5169,438 +5169,317 @@
]
},
"nonFSO": {
- "keysSummary": {
- "totalUnreplicatedDataSize": 10485760,
- "totalReplicatedDataSize": 31457280,
- "totalOpenKeys": 10
- },
+ "lastKey": "/s3v/legacy/dir1/dir2/dir3/an9uf2eeox/19/113328137261088807",
+ "replicatedDataSize": 31457280,
+ "unreplicatedDataSize": 10485760,
"nonFSO": [
{
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/01/110569623850191713",
- "path": "nonfso 1",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2439/110569623850191714",
- "path": "nonfso 2",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2440/110569623850191715",
- "path": "nonfso 11",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2441/110569623850191716",
- "path": "nonfso 12",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2440/110569623850191717",
- "path": "nonfso 21",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2441/110569623850191718",
- "path": "nonfso 22",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- }
- ],
- "status": "OK"
- },
- "fso": {
- "fso": [
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2401/110569623850191713",
- "path": "1",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2402/110569623850191714",
- "path": "2",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2403/110569623850191715",
- "path": "3",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2404/110569623850191716",
- "path": "4",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2405/110569623850191717",
- "path": "5",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2406/110569623850191718",
- "path": "6",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2407/110569623850191719",
- "path": "7",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2408/110569623850191720",
- "path": "8",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2409/110569623850191721",
- "path": "9",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
- "replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key": "fso 10",
- "path": "10",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "key": "/s3v/legacy/dir1/dir2/dir3/an9uf2eeox/10/113328137245098014",
+ "path": "dir1/dir2/dir3/an9uf2eeox/10",
+ "inStateSince": 1729250141069,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
"replicationType": "RATIS"
- }
+ },
+ "creationTime": 1729250161542,
+ "modificationTime": 1729250161542,
+ "isKey": true
},
{
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2411/110569623850191722",
- "path": "11",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "key": "/s3v/legacy/dir1/dir2/dir3/an9uf2eeox/11/113328137245818911",
+ "path": "dir1/dir2/dir3/an9uf2eeox/11",
+ "inStateSince": 1729250141080,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
"replicationType": "RATIS"
- }
+ },
+ "creationTime": 1729250161543,
+ "modificationTime": 1729250161543,
+ "isKey": true
},
{
- "key": "fso 12",
- "path": "12",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "key": "/s3v/legacy/dir1/dir2/dir3/an9uf2eeox/12/113328137247195168",
+ "path": "dir1/dir2/dir3/an9uf2eeox/12",
+ "inStateSince": 1729250141091,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
"replicationType": "RATIS"
- }
+ },
+ "creationTime": 1729250161543,
+ "modificationTime": 1729250161543,
+ "isKey": true
},
{
- "key": "fso 13",
- "path": "13",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "key": "/s3v/legacy/dir1/dir2/dir3/an9uf2eeox/13/113328137248178209",
+ "path": "dir1/dir2/dir3/an9uf2eeox/13",
+ "inStateSince": 1729250141116,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
"replicationType": "RATIS"
- }
+ },
+ "creationTime": 1729250161543,
+ "modificationTime": 1729250161543,
+ "isKey": true
},
{
- "key": "fso 14",
- "path": "14",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "key": "/s3v/legacy/dir1/dir2/dir3/an9uf2eeox/14/113328137249685538",
+ "path": "dir1/dir2/dir3/an9uf2eeox/14",
+ "inStateSince": 1729250141139,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
"replicationType": "RATIS"
- }
+ },
+ "creationTime": 1729250161543,
+ "modificationTime": 1729250161543,
+ "isKey": true
},
{
- "key": "fso 15",
- "path": "15",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "key": "/s3v/legacy/dir1/dir2/dir3/an9uf2eeox/15/113328137250930723",
+ "path": "dir1/dir2/dir3/an9uf2eeox/15",
+ "inStateSince": 1729250141158,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
"replicationType": "RATIS"
- }
+ },
+ "creationTime": 1729250161543,
+ "modificationTime": 1729250161543,
+ "isKey": true
},
{
- "key": "fso 16 key",
- "path": "16",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "key": "/s3v/legacy/dir1/dir2/dir3/an9uf2eeox/16/113328137252569124",
+ "path": "dir1/dir2/dir3/an9uf2eeox/16",
+ "inStateSince": 1729250141183,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
"replicationType": "RATIS"
- }
+ },
+ "creationTime": 1729250161543,
+ "modificationTime": 1729250161543,
+ "isKey": true
},
{
- "key": "fso 17 key",
- "path": "17",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "key": "/s3v/legacy/dir1/dir2/dir3/an9uf2eeox/17/113328137259778085",
+ "path": "dir1/dir2/dir3/an9uf2eeox/17",
+ "inStateSince": 1729250141293,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
"replicationType": "RATIS"
- }
+ },
+ "creationTime": 1729250161543,
+ "modificationTime": 1729250161543,
+ "isKey": true
},
{
- "key": "fso 18 key",
- "path": "18",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "key": "/s3v/legacy/dir1/dir2/dir3/an9uf2eeox/18/113328137261023270",
+ "path": "dir1/dir2/dir3/an9uf2eeox/18",
+ "inStateSince": 1729250141312,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
"replicationType": "RATIS"
- }
+ },
+ "creationTime": 1729250161543,
+ "modificationTime": 1729250161543,
+ "isKey": true
},
{
- "key": "fso 19 key",
- "path": "19",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "key": "/s3v/legacy/dir1/dir2/dir3/an9uf2eeox/19/113328137261088807",
+ "path": "dir1/dir2/dir3/an9uf2eeox/19",
+ "inStateSince": 1729250141313,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
"replicationType": "RATIS"
- }
- },
- {
- "key":
"/-9223372036854775552/-9223372036854775040/-9223372036852420095/2411/110569623850191723",
- "path": "21",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
+ },
+ "creationTime": 1729250161543,
+ "modificationTime": 1729250161543,
+ "isKey": true
+ }
+ ],
+ "status": "OK"
+ },
+ "fso": {
+ "lastKey":
"/-9223372036854775552/-9223372036854775040/-9223372036854774524/9/113328113600626690",
+ "replicatedDataSize": 31457280,
+ "unreplicatedDataSize": 10485760,
+ "fso": [{
+ "key":
"/-9223372036854775552/-9223372036854775040/-9223372036854774524/0/113328113600561153",
+ "path": "dir1/dir2/dir3/pnrnqh5gux/0",
+ "inStateSince": 1729249780277,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key": "fso 22",
- "path": "22",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
+ "replicationType": "RATIS"
+ },
+ "creationTime": 1729249820562,
+ "modificationTime": 1729249820562,
+ "isKey": true
+ },
+ {
+ "key":
"/-9223372036854775552/-9223372036854775040/-9223372036854774524/1/113328113600626694",
+ "path": "dir1/dir2/dir3/pnrnqh5gux/1",
+ "inStateSince": 1729249780277,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key": "fso 23",
- "path": "23",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
+ "replicationType": "RATIS"
+ },
+ "creationTime": 1729249820562,
+ "modificationTime": 1729249820562,
+ "isKey": true
+ },
+ {
+ "key":
"/-9223372036854775552/-9223372036854775040/-9223372036854774524/2/113328113600626691",
+ "path": "dir1/dir2/dir3/pnrnqh5gux/2",
+ "inStateSince": 1729249780277,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key": "fso 24",
- "path": "24",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
+ "replicationType": "RATIS"
+ },
+ "creationTime": 1729249820563,
+ "modificationTime": 1729249820563,
+ "isKey": true
+ },
+ {
+ "key":
"/-9223372036854775552/-9223372036854775040/-9223372036854774524/3/113328113600692233",
+ "path": "dir1/dir2/dir3/pnrnqh5gux/3",
+ "inStateSince": 1729249780277,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key": "fso 25",
- "path": "25",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
+ "replicationType": "RATIS"
+ },
+ "creationTime": 1729249820563,
+ "modificationTime": 1729249820563,
+ "isKey": true
+ },
+ {
+ "key":
"/-9223372036854775552/-9223372036854775040/-9223372036854774524/4/113328113600626695",
+ "path": "dir1/dir2/dir3/pnrnqh5gux/4",
+ "inStateSince": 1729249780277,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key": "fso 26 key",
- "path": "26",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
+ "replicationType": "RATIS"
+ },
+ "creationTime": 1729249820563,
+ "modificationTime": 1729249820563,
+ "isKey": true
+ },
+ {
+ "key":
"/-9223372036854775552/-9223372036854775040/-9223372036854774524/5/113328113600561152",
+ "path": "dir1/dir2/dir3/pnrnqh5gux/5",
+ "inStateSince": 1729249780277,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key": "fso 17 key",
- "path": "27",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
+ "replicationType": "RATIS"
+ },
+ "creationTime": 1729249820564,
+ "modificationTime": 1729249820564,
+ "isKey": true
+ },
+ {
+ "key":
"/-9223372036854775552/-9223372036854775040/-9223372036854774524/6/113328113600626692",
+ "path": "dir1/dir2/dir3/pnrnqh5gux/6",
+ "inStateSince": 1729249780277,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key": "fso 18 key",
- "path": "28",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
+ "replicationType": "RATIS"
+ },
+ "creationTime": 1729249820564,
+ "modificationTime": 1729249820564,
+ "isKey": true
+ },
+ {
+ "key":
"/-9223372036854775552/-9223372036854775040/-9223372036854774524/7/113328113600626696",
+ "path": "dir1/dir2/dir3/pnrnqh5gux/7",
+ "inStateSince": 1729249780277,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key": "fso 19 key",
- "path": "29",
- "inStateSince": 1686156886632,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
+ "replicationType": "RATIS"
+ },
+ "creationTime": 1729249820564,
+ "modificationTime": 1729249820564,
+ "isKey": true
+ },
+ {
+ "key":
"/-9223372036854775552/-9223372036854775040/-9223372036854774524/8/113328113600626693",
+ "path": "dir1/dir2/dir3/pnrnqh5gux/8",
+ "inStateSince": 1729249780277,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- },
- {
- "key": "fso 20 key",
- "path": "20",
- "inStateSince": 1686156887186,
- "size": 268435456,
- "replicatedSize": 268435456,
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
+ "replicationType": "RATIS"
+ },
+ "creationTime": 1729249820564,
+ "modificationTime": 1729249820564,
+ "isKey": true
+ },
+ {
+ "key":
"/-9223372036854775552/-9223372036854775040/-9223372036854774524/9/113328113600626690",
+ "path": "dir1/dir2/dir3/pnrnqh5gux/9",
+ "inStateSince": 1729249780277,
+ "size": 1048576,
+ "replicatedSize": 3145728,
"replicationInfo": {
- "replicationFactor": "ONE",
- "requiredNodes": 1,
- "replicationType": "RATIS"
- }
- }
- ],
+ "replicationFactor": "THREE",
+ "requiredNodes": 3,
+ "replicationType": "RATIS"
+ },
+ "creationTime": 1729249820565,
+ "modificationTime": 1729249820565,
+ "isKey": true
+ }],
"status": "OK"
},
"keydeletePending": {
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.tsx
index 78954ebb5a..ceb633f603 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.tsx
@@ -22,6 +22,7 @@ import { Switch as AntDSwitch, Layout } from 'antd';
import NavBar from './components/navBar/navBar';
import NavBarV2 from '@/v2/components/navBar/navBar';
import Breadcrumbs from './components/breadcrumbs/breadcrumbs';
+import BreadcrumbsV2 from '@/v2/components/breadcrumbs/breadcrumbs';
import { HashRouter as Router, Switch, Route, Redirect } from
'react-router-dom';
import { routes } from '@/routes';
import { routesV2 } from '@/v2/routes-v2';
@@ -70,7 +71,7 @@ class App extends React.Component<Record<string, object>,
IAppState> {
<Layout className={layoutClass}>
<Header>
<div style={{ margin: '16px 0', display: 'flex', justifyContent:
'space-between' }}>
- <Breadcrumbs />
+ {(enableNewUI) ? <BreadcrumbsV2 /> : <Breadcrumbs />}
<AntDSwitch
disabled={true}
checkedChildren={<div style={{ paddingLeft: '2px' }}>New
UI</div>}
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/constants/breadcrumbs.constants.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/constants/breadcrumbs.constants.tsx
index bc81e86dcb..a9f7a53eda 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/constants/breadcrumbs.constants.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/constants/breadcrumbs.constants.tsx
@@ -31,5 +31,5 @@ export const breadcrumbNameMap: IBreadcrumbNameMap = {
'/Insights': 'Insights',
'/DiskUsage': 'Disk Usage',
'/Heatmap': 'Heatmap',
- '/Om': 'Om',
+ '/Om': 'Om'
};
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/breadcrumbs/breadcrumbs.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/breadcrumbs/breadcrumbs.tsx
new file mode 100644
index 0000000000..8e1c34decb
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/breadcrumbs/breadcrumbs.tsx
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Breadcrumb } from 'antd';
+import { HomeOutlined } from '@ant-design/icons';
+import { Link, useLocation } from 'react-router-dom';
+
+import { breadcrumbNameMap } from '@/v2/constants/breadcrumbs.constants';
+
+const Breadcrumbs: React.FC<{}> = () => {
+ const location = useLocation();
+ //Split and filter to remove empty strings
+ const pathSnippets = location.pathname.split('/').filter(i => i);
+
+ const extraBreadcrumbItems = pathSnippets.map((_: string, index: number) => {
+ const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
+ return (
+ <Breadcrumb.Item key={url}>
+ <Link to={url}>
+ {breadcrumbNameMap[url]}
+ </Link>
+ </Breadcrumb.Item>
+ )
+ });
+
+ const breadcrumbItems = [(
+ <Breadcrumb.Item key='home'>
+ <Link to='/'><HomeOutlined /></Link>
+ </Breadcrumb.Item>
+ )].concat(extraBreadcrumbItems);
+
+ return (
+ <Breadcrumb>
+ {breadcrumbItems}
+ </Breadcrumb>
+ );
+}
+
+export default Breadcrumbs;
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewSummaryCard.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewSummaryCard.tsx
index e383512f20..8736b3e0d2 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewSummaryCard.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewSummaryCard.tsx
@@ -39,6 +39,7 @@ type OverviewTableCardProps = {
data?: string | React.ReactElement;
linkToUrl?: string;
showHeader?: boolean;
+ state?: Record<string, any>;
}
// ------------- Styles -------------- //
@@ -63,15 +64,18 @@ const OverviewSummaryCard: React.FC<OverviewTableCardProps>
= ({
columns = [],
tableData = [],
linkToUrl = '',
- showHeader = false
+ showHeader = false,
+ state
}) => {
-
const titleElement = (linkToUrl)
? (
<div className='card-title-div'>
{title}
<Link
- to={linkToUrl}
+ to={{
+ pathname: linkToUrl,
+ state: state
+ }}
style={{
fontWeight: 400
}}>View Insights</Link>
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/duPieChart/duPieChart.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/plots/duPieChart.tsx
similarity index 100%
rename from
hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/duPieChart/duPieChart.tsx
rename to
hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/plots/duPieChart.tsx
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/plots/insightsContainerPlot.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/plots/insightsContainerPlot.tsx
new file mode 100644
index 0000000000..851c355e76
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/plots/insightsContainerPlot.tsx
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import filesize from 'filesize';
+import { EChartsOption } from 'echarts';
+
+import EChart from '@/v2/components/eChart/eChart';
+import { ContainerCountResponse, ContainerPlotData } from
'@/v2/types/insights.types';
+
+type ContainerSizeDistributionProps = {
+ containerCountResponse: ContainerCountResponse[];
+ containerSizeError: string | undefined;
+}
+
+const size = filesize.partial({ standard: 'iec', round: 0 });
+
+const ContainerSizeDistribution: React.FC<ContainerSizeDistributionProps> = ({
+ containerCountResponse,
+ containerSizeError
+}) => {
+
+ const [containerPlotData, setContainerPlotData] =
React.useState<ContainerPlotData>({
+ containerCountValues: [],
+ containerCountMap: new Map<number, number>()
+ });
+
+ function updatePlotData() {
+ const containerCountMap: Map<number, number> =
containerCountResponse.reduce(
+ (map: Map<number, number>, current) => {
+ const containerSize = current.containerSize;
+ const oldCount = map.get(containerSize) ?? 0;
+ map.set(containerSize, oldCount + current.count);
+ return map;
+ },
+ new Map<number, number>()
+ );
+
+ const containerCountValues =
Array.from(containerCountMap.keys()).map(value => {
+ const upperbound = size(value);
+ const upperboundPwr = Math.log2(value);
+
+ const lowerbound = upperboundPwr > 10 ? size(2 ** (upperboundPwr - 1)) :
size(0);
+ return `${lowerbound} - ${upperbound}`;
+ });
+
+ setContainerPlotData({
+ containerCountValues: containerCountValues,
+ containerCountMap: containerCountMap
+ });
+ }
+
+ React.useEffect(() => {
+ updatePlotData();
+ }, []);
+
+ const { containerCountMap, containerCountValues } = containerPlotData;
+
+ const containerPlotOptions: EChartsOption = {
+ tooltip: {
+ trigger: 'item',
+ formatter: ({ data }) => {
+ return `Size Range: <strong>${data.name}</strong><br>Count:
<strong>${data.value}</strong>`
+ }
+ },
+ legend: {
+ orient: 'vertical',
+ left: 'right'
+ },
+ series: {
+ type: 'pie',
+ radius: '50%',
+ data: Array.from(containerCountMap?.values() ?? []).map((value, idx) => {
+ return {
+ value: value,
+ name: containerCountValues[idx] ?? ''
+ }
+ }),
+ },
+ graphic: (containerSizeError) ? {
+ type: 'group',
+ left: 'center',
+ top: 'middle',
+ z: 100,
+ children: [
+ {
+ type: 'rect',
+ left: 'center',
+ top: 'middle',
+ z: 100,
+ shape: {
+ width: 500,
+ height: 500
+ },
+ style: {
+ fill: 'rgba(256, 256, 256, 0.5)'
+ }
+ },
+ {
+ type: 'rect',
+ left: 'center',
+ top: 'middle',
+ z: 100,
+ shape: {
+ width: 500,
+ height: 40
+ },
+ style: {
+ fill: '#FC909B'
+ }
+ },
+ {
+ type: 'text',
+ left: 'center',
+ top: 'middle',
+ z: 100,
+ style: {
+ text: `No data available. ${containerSizeError}`,
+ font: '20px sans-serif'
+ }
+ }
+ ]
+ } : undefined
+ }
+
+ return (<>
+ <EChart option={containerPlotOptions} style={{
+ width: '30vw',
+ height: '65vh'
+ }} />
+ </>)
+}
+
+export default ContainerSizeDistribution;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/plots/insightsFilePlot.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/plots/insightsFilePlot.tsx
new file mode 100644
index 0000000000..bb6453ed7c
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/plots/insightsFilePlot.tsx
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import filesize from 'filesize';
+import { EChartsOption } from 'echarts';
+import { ValueType } from 'react-select';
+
+import EChart from '@/v2/components/eChart/eChart';
+import MultiSelect, { Option } from '@/v2/components/select/multiSelect';
+import { FileCountResponse, FilePlotData } from '@/v2/types/insights.types';
+
+
+//-----Types------
+type FileSizeDistributionProps = {
+ volumeOptions: Option[];
+ volumeBucketMap: Map<string, Set<string>>;
+ fileCountResponse: FileCountResponse[];
+ fileCountError: string | undefined;
+}
+
+const size = filesize.partial({ standard: 'iec', round: 0 });
+
+const dropdownStyles: React.CSSProperties = {
+ display: 'flex',
+ justifyContent: 'space-between'
+}
+
+const FileSizeDistribution: React.FC<FileSizeDistributionProps> = ({
+ volumeOptions = [],
+ volumeBucketMap,
+ fileCountResponse,
+ fileCountError
+}) => {
+
+ const [bucketOptions, setBucketOptions] = React.useState<Option[]>([]);
+ const [selectedBuckets, setSelectedBuckets] = React.useState<Option[]>([]);
+ const [selectedVolumes, setSelectedVolumes] = React.useState<Option[]>([]);
+ const [isBucketSelectionEnabled, setBucketSelectionEnabled] =
React.useState<boolean>(false);
+
+ const [filePlotData, setFilePlotData] = React.useState<FilePlotData>({
+ fileCountValues: [],
+ fileCountMap: new Map<number, number>()
+ });
+
+ function handleVolumeChange(selectedVolumes: ValueType<Option, true>) {
+
+ // Disable bucket selection options if more than one volume is selected or
no volumes present
+ // If there is only one volume then the bucket selection is enabled
+ const bucketSelectionDisabled = ((selectedVolumes as Option[])?.length > 1
+ && volumeBucketMap.size !== 1);
+
+ let bucketOptions: Option[] = [];
+
+ // Update buckets if only one volume is selected
+ if (selectedVolumes?.length === 1) {
+ const selectedVolume = selectedVolumes[0].value;
+ if (volumeBucketMap.has(selectedVolume)) {
+ bucketOptions = Array.from(
+ volumeBucketMap.get(selectedVolume)!
+ ).map(bucket => ({
+ label: bucket,
+ value: bucket
+ }));
+ }
+ }
+ setBucketOptions([...bucketOptions]);
+ setSelectedVolumes(selectedVolumes as Option[]);
+ setSelectedBuckets([...bucketOptions]);
+ setBucketSelectionEnabled(!bucketSelectionDisabled);
+ }
+
+ function handleBucketChange(selectedBuckets: ValueType<Option, true>) {
+ setSelectedBuckets(selectedBuckets as Option[]);
+ }
+
+ function updatePlotData() {
+ // Aggregate count across volumes and buckets for use in plot
+ let filteredData = fileCountResponse;
+ const selectedVolumeValues = new Set(selectedVolumes.map(option =>
option.value));
+ const selectedBucketValues = new Set(selectedBuckets.map(option =>
option.value));
+ if (selectedVolumes.length >= 0) {
+ // Not all volumes are selected, need to filter based on the selected
values
+ filteredData = filteredData.filter(data =>
selectedVolumeValues.has(data.volume));
+
+ // We have selected a volume but all the buckets are deselected
+ if (selectedVolumes.length === 1 && selectedBuckets.length === 0) {
+ // Since no buckets are selected there is no data
+ filteredData = [];
+ }
+ }
+ if (selectedBuckets.length > 0) {
+ // Not all buckcets are selected, filter based on the selected values
+ filteredData = filteredData.filter(data =>
selectedBucketValues.has(data.bucket));
+ }
+
+ // This is a map of 'size : count of the size'
+ const fileCountMap: Map<number, number> = filteredData.reduce(
+ (map: Map<number, number>, current) => {
+ const fileSize = current.fileSize;
+ const oldCount = map.get(fileSize) ?? 0;
+ map.set(fileSize, oldCount + current.count);
+ return map;
+ },
+ new Map<number, number>
+ );
+
+ // Calculate the previous power of 2 to find the lower bound of the range
+ // Ex: for 2048, the lower bound is 1024
+ const fileCountValues = Array.from(fileCountMap.keys()).map(value => {
+ const upperbound = size(value);
+ const upperboundPwr = Math.log2(value);
+ // For 1024 i.e 2^10, the lower bound is 0, so we start binning after
2^10
+ const lowerbound = upperboundPwr > 10 ? size(2 ** (upperboundPwr - 1)) :
size(0);
+ return `${lowerbound} - ${upperbound}`;
+ });
+
+ setFilePlotData({
+ fileCountValues: fileCountValues,
+ // set the sorted value by size for the map
+ fileCountMap: new Map([...fileCountMap.entries()].sort((a, b) => a[0] -
b[0]))
+ });
+ }
+
+ // If the response is updated or the volume-bucket data is updated, update
plot
+ React.useEffect(() => {
+ updatePlotData();
+ handleVolumeChange(volumeOptions);
+ }, [
+ fileCountResponse, volumeBucketMap
+ ]);
+
+ // If the selected volumes and buckets change, update plot
+ React.useEffect(() => {
+ updatePlotData();
+ }, [selectedVolumes, selectedBuckets])
+
+ const { fileCountValues, fileCountMap } = filePlotData;
+
+ const filePlotOptions: EChartsOption = {
+ xAxis: {
+ type: 'category',
+ data: [...fileCountValues] ?? []
+ },
+ yAxis: {
+ type: 'value'
+ },
+ tooltip: {
+ trigger: 'item',
+ formatter: ({ name, value }) => {
+ return `Size Range: ${name}<br>Count: <strong>${value}</strong>`
+ }
+ },
+ series: {
+ itemStyle: {
+ color: '#04AD78'
+ },
+ data: Array.from(fileCountMap?.values() ?? []),
+ type: 'bar'
+ },
+ graphic: (fileCountError) ? {
+ type: 'group',
+ left: 'center',
+ top: 'middle',
+ z: 100,
+ children: [
+ {
+ type: 'rect',
+ left: 'center',
+ top: 'middle',
+ z: 100,
+ shape: {
+ width: 500,
+ height: 40
+ },
+ style: {
+ fill: '#FC909B'
+ }
+ },
+ {
+ type: 'text',
+ left: 'center',
+ top: 'middle',
+ z: 100,
+ style: {
+ text: `No data available. ${fileCountError}`,
+ font: '20px sans-serif'
+ }
+ }
+ ]
+ } : undefined
+ }
+
+ return (<>
+ <div style={dropdownStyles}>
+ <MultiSelect
+ options={volumeOptions}
+ defaultValue={selectedVolumes}
+ selected={selectedVolumes}
+ placeholder='Volumes'
+ onChange={handleVolumeChange}
+ onTagClose={() => { }}
+ fixedColumn=''
+ columnLength={volumeOptions.length}
+ style={{
+ control: (baseStyles, state) => ({
+ ...baseStyles,
+ minWidth: 345
+ })
+ }} />
+ <MultiSelect
+ options={bucketOptions}
+ defaultValue={selectedBuckets}
+ selected={selectedBuckets}
+ placeholder='Buckets'
+ onChange={handleBucketChange}
+ onTagClose={() => { }}
+ fixedColumn=''
+ columnLength={bucketOptions.length}
+ isDisabled={!isBucketSelectionEnabled}
+ style={{
+ control: (baseStyles, state) => ({
+ ...baseStyles,
+ minWidth: 345
+ })
+ }} />
+ </div>
+ <EChart option={filePlotOptions} style={{
+ width: '30vw',
+ height: '55.5vh',
+ marginTop: '5vh'
+ }} />
+ </>)
+}
+
+export default FileSizeDistribution;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/select/multiSelect.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/select/multiSelect.tsx
index 07b3f9eafa..3dfe19f9b4 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/select/multiSelect.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/select/multiSelect.tsx
@@ -23,7 +23,8 @@ import {
components,
OptionProps,
ValueType,
- ValueContainerProps
+ ValueContainerProps,
+ StylesConfig
} from 'react-select';
import { selectStyles } from "@/v2/constants/select.constants";
@@ -41,6 +42,7 @@ interface MultiSelectProps extends ReactSelectProps<Option,
true> {
placeholder: string;
fixedColumn: string;
columnLength: number;
+ style?: StylesConfig<Option, true>;
onChange: (arg0: ValueType<Option, true>) => void;
onTagClose: (arg0: string) => void;
}
@@ -72,9 +74,11 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
selected = [],
maxSelected = 5,
placeholder = 'Columns',
+ isDisabled = false,
fixedColumn,
columnLength,
tagRef,
+ style,
onTagClose = () => { }, // Assign default value as a void function
onChange = () => { }, // Assign default value as a void function
...props
@@ -90,34 +94,40 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
? child
: null
)}
- {placeholder}: {selected.length} selected
+ {isDisabled
+ ? placeholder
+ : `${placeholder}: ${selected.length} selected`
+}
</components.ValueContainer>
);
};
+ const finalStyles = {...selectStyles, ...style ?? {}}
+
return (
<ReactSelect
- {...props}
- isMulti={true}
- closeMenuOnSelect={false}
- hideSelectedOptions={false}
- isClearable={false}
- isSearchable={false}
- controlShouldRenderValue={false}
- classNamePrefix='multi-select'
- options={options}
- components={{
- ValueContainer,
- Option
- }}
- placeholder={placeholder}
- value={selected}
- isOptionDisabled={(option) => option.value === fixedColumn}
- onChange={(selected: ValueType<Option, true>) => {
- if (selected?.length === options.length) return onChange!(options);
- return onChange!(selected);
- }}
- styles={selectStyles} />
+ {...props}
+ isMulti={true}
+ closeMenuOnSelect={false}
+ hideSelectedOptions={false}
+ isClearable={false}
+ isSearchable={false}
+ controlShouldRenderValue={false}
+ classNamePrefix='multi-select'
+ options={options}
+ components={{
+ ValueContainer,
+ Option
+ }}
+ placeholder={placeholder}
+ value={selected}
+ isOptionDisabled={(option) => option.value === fixedColumn}
+ isDisabled={isDisabled}
+ onChange={(selected: ValueType<Option, true>) => {
+ if (selected?.length === options.length) return onChange!(options);
+ return onChange!(selected);
+ }}
+ styles={finalStyles} />
)
}
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/containerMismatchTable.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/containerMismatchTable.tsx
new file mode 100644
index 0000000000..818eca37f8
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/containerMismatchTable.tsx
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { AxiosError } from 'axios';
+import {
+ Dropdown,
+ Menu,
+ Popover,
+ Table,
+ Tooltip
+} from 'antd';
+import {
+ ColumnsType,
+ TablePaginationConfig
+} from 'antd/es/table';
+import {
+ MenuProps as FilterMenuProps
+} from 'antd/es/menu';
+import { FilterFilled, InfoCircleOutlined } from '@ant-design/icons';
+import { ValueType } from 'react-select';
+
+import Search from '@/v2/components/search/search';
+import SingleSelect, { Option } from '@/v2/components/select/singleSelect';
+import { showDataFetchError } from '@/utils/common';
+import { AxiosGetHelper } from '@/utils/axiosRequestHelper';
+import { useDebounce } from '@/v2/hooks/debounce.hook';
+import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants';
+
+import {
+ Container,
+ MismatchContainersResponse,
+ Pipelines
+} from '@/v2/types/insights.types';
+
+
+//-----Types-----
+type ContainerMismatchTableProps = {
+ paginationConfig: TablePaginationConfig;
+ limit: Option;
+ handleLimitChange: (arg0: ValueType<Option, false>) => void;
+ expandedRowRender: (arg0: any) => JSX.Element;
+ onRowExpand: (arg0: boolean, arg1: any) => void;
+}
+
+//-----Components------
+const ContainerMismatchTable: React.FC<ContainerMismatchTableProps> = ({
+ paginationConfig,
+ limit,
+ onRowExpand,
+ expandedRowRender,
+ handleLimitChange
+}) => {
+
+ const [loading, setLoading] = React.useState<boolean>(false);
+ const [data, setData] = React.useState<Container[]>();
+ const [searchTerm, setSearchTerm] = React.useState<string>('');
+
+ const cancelSignal = React.useRef<AbortController>();
+ const debouncedSearch = useDebounce(searchTerm, 300);
+
+ const handleExistAtChange: FilterMenuProps['onClick'] = ({ key }) => {
+ if (key === 'OM') {
+ fetchMismatchContainers('SCM');
+ } else {
+ fetchMismatchContainers('OM');
+ }
+ }
+
+ function filterData(data: Container[] | undefined) {
+ return data?.filter(
+ (data: Container) =>
data.containerId.toString().includes(debouncedSearch)
+ );
+ }
+
+ const COLUMNS: ColumnsType<Container> = [
+ {
+ title: 'Container ID',
+ dataIndex: 'containerId',
+ key: 'containerId',
+ width: '20%'
+
+ },
+ {
+ title: 'Count Of Keys',
+ dataIndex: 'numberOfKeys',
+ key: 'numberOfKeys',
+ sorter: (a: Container, b: Container) => a.numberOfKeys - b.numberOfKeys
+ },
+ {
+ title: 'Pipelines',
+ dataIndex: 'pipelines',
+ key: 'pipelines',
+ render: (pipelines: Pipelines[]) => {
+ const renderPipelineIds = (pipelineIds: Pipelines[]) => {
+ return pipelineIds?.map(pipeline => (
+ <div key={pipeline.id.id}>
+ {pipeline.id.id}
+ </div>
+ ));
+ }
+ return (
+ <Popover
+ content={
+ renderPipelineIds(pipelines)
+ }
+ title='Related Pipelines'
+ placement='bottomLeft'
+ trigger='hover'>
+ <strong>{pipelines.length}</strong> pipelines
+ </Popover>
+ )
+ }
+ },
+ {
+ title: <>
+ <Dropdown
+ overlay={
+ <Menu onClick={handleExistAtChange}>
+ <Menu.Item key='OM'> OM </Menu.Item>
+ <Menu.Item key='SCM'> SCM </Menu.Item>
+ </Menu>
+ }>
+ <label style={{ marginRight: '5%' }}> Exists
At <FilterFilled /> </label>
+ </Dropdown>
+ <Tooltip placement='top' title={
+ <span>
+ <strong>SCM</strong>: Container exists at SCM but missing at
OM.<br />
+ <strong>OM</strong>: Container exist at OM but missing at SCM.
+ </span>}>
+ <InfoCircleOutlined />
+ </Tooltip>
+ </>,
+ dataIndex: 'existsAt'
+ }
+ ];
+
+ function fetchMismatchContainers(missingIn: string) {
+ setLoading(true);
+ const { request, controller } = AxiosGetHelper(
+
`/api/v1/containers/mismatch?limit=${limit.value}&missingIn=${missingIn}`,
+ cancelSignal.current
+ );
+
+ cancelSignal.current = controller;
+ request.then(response => {
+ const mismatchedContainers: MismatchContainersResponse = response?.data;
+ setData(mismatchedContainers?.containerDiscrepancyInfo ?? []);
+ setLoading(false);
+ }).catch(error => {
+ setLoading(false);
+ showDataFetchError((error as AxiosError).toString());
+ })
+ }
+
+ React.useEffect(() => {
+ //Fetch containers missing in OM by default
+ fetchMismatchContainers('OM');
+
+ return (() => {
+ cancelSignal.current && cancelSignal.current.abort();
+ })
+ }, [limit.value]);
+
+ return (
+ <>
+ <div className='table-header-section'>
+ <div className='table-filter-section'>
+ <SingleSelect
+ options={LIMIT_OPTIONS}
+ defaultValue={limit}
+ placeholder='Limit'
+ onChange={handleLimitChange} />
+ </div>
+ <Search
+ disabled={(data?.length ?? 0) < 1}
+ searchInput={searchTerm}
+ onSearchChange={
+ (e: React.ChangeEvent<HTMLInputElement>) =>
setSearchTerm(e.target.value)
+ }
+ onChange={() => { }} />
+ </div>
+ <Table
+ expandable={{
+ expandRowByClick: true,
+ expandedRowRender: expandedRowRender,
+ onExpand: onRowExpand
+ }}
+ dataSource={filterData(data)}
+ columns={COLUMNS}
+ loading={loading}
+ pagination={paginationConfig}
+ rowKey='containerId'
+ locale={{ filterTitle: '' }}
+ scroll={{ x: 'max-content' }} />
+ </>
+ )
+}
+
+export default ContainerMismatchTable;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingDirsTable.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingDirsTable.tsx
new file mode 100644
index 0000000000..f0c6fc8161
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingDirsTable.tsx
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { AxiosError } from 'axios';
+import Table, {
+ ColumnsType,
+ TablePaginationConfig
+} from 'antd/es/table';
+import { ValueType } from 'react-select';
+
+import Search from '@/v2/components/search/search';
+import SingleSelect, { Option } from '@/v2/components/select/singleSelect';
+import { AxiosGetHelper } from '@/utils/axiosRequestHelper';
+import { byteToSize, showDataFetchError } from '@/utils/common';
+import { getFormattedTime } from '@/v2/utils/momentUtils';
+import { useDebounce } from '@/v2/hooks/debounce.hook';
+import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants';
+
+import { DeletedDirInfo } from '@/v2/types/insights.types';
+
+//-----Types------
+type DeletePendingDirTableProps = {
+ paginationConfig: TablePaginationConfig
+ limit: Option;
+ handleLimitChange: (arg0: ValueType<Option, false>) => void;
+}
+
+//-----Constants------
+const COLUMNS: ColumnsType<DeletedDirInfo> = [{
+ title: 'Directory Name',
+ dataIndex: 'key',
+ key: 'key'
+},
+{
+ title: 'In state since',
+ dataIndex: 'inStateSince',
+ key: 'inStateSince',
+ render: (inStateSince: number) => {
+ return getFormattedTime(inStateSince, 'll LTS');
+ }
+},
+{
+ title: 'Path',
+ dataIndex: 'path',
+ key: 'path'
+},
+{
+ title: 'Size',
+ dataIndex: 'size',
+ key: 'size',
+ render: (dataSize: number) => byteToSize(dataSize, 1)
+}];
+
+//-----Components------
+const DeletePendingDirTable: React.FC<DeletePendingDirTableProps> = ({
+ limit,
+ paginationConfig,
+ handleLimitChange
+}) => {
+
+ const [loading, setLoading] = React.useState<boolean>(false);
+ const [data, setData] = React.useState<DeletedDirInfo[]>();
+ const [searchTerm, setSearchTerm] = React.useState<string>('');
+
+ const cancelSignal = React.useRef<AbortController>();
+ const debouncedSearch = useDebounce(searchTerm, 300);
+
+ function filterData(data: DeletedDirInfo[] | undefined) {
+ return data?.filter(
+ (data: DeletedDirInfo) => data.key.includes(debouncedSearch)
+ );
+ }
+
+ function loadData() {
+ setLoading(true);
+
+ const { request, controller } = AxiosGetHelper(
+ `/api/v1/keys/deletePending/dirs?limit=${limit.value}`,
+ cancelSignal.current
+ );
+ cancelSignal.current = controller;
+
+ request.then(response => {
+ setData(response?.data?.deletedDirInfo ?? []);
+ setLoading(false);
+ }).catch(error => {
+ setLoading(false);
+ showDataFetchError((error as AxiosError).toString());
+ });
+ }
+
+ React.useEffect(() => {
+ loadData();
+
+ return (() => cancelSignal.current && cancelSignal.current.abort());
+ }, [limit.value]);
+
+ return (<>
+ <div className='table-header-section'>
+ <div className='table-filter-section'>
+ <SingleSelect
+ options={LIMIT_OPTIONS}
+ defaultValue={limit}
+ placeholder='Limit'
+ onChange={handleLimitChange} />
+ </div>
+ <Search
+ disabled={(data?.length ?? 0) < 1}
+ searchInput={searchTerm}
+ onSearchChange={
+ (e: React.ChangeEvent<HTMLInputElement>) =>
setSearchTerm(e.target.value)
+ }
+ onChange={() => { }} />
+ </div>
+ <Table
+ loading={loading}
+ dataSource={filterData(data)}
+ columns={COLUMNS}
+ pagination={paginationConfig}
+ rowKey='key'
+ locale={{ filterTitle: '' }}
+ scroll={{ x: 'max-content' }} />
+ </>)
+}
+
+export default DeletePendingDirTable;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingKeysTable.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingKeysTable.tsx
new file mode 100644
index 0000000000..65ada49564
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingKeysTable.tsx
@@ -0,0 +1,194 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import React from 'react';
+import { AxiosError } from 'axios';
+import Table, {
+ ColumnsType,
+ TablePaginationConfig
+} from 'antd/es/table';
+import { ValueType } from 'react-select';
+
+import Search from '@/v2/components/search/search';
+import SingleSelect, { Option } from '@/v2/components/select/singleSelect';
+import ExpandedPendingKeysTable from
'@/v2/components/tables/insights/expandedPendingKeysTable';
+import { AxiosGetHelper } from '@/utils/axiosRequestHelper';
+import { byteToSize, showDataFetchError } from '@/utils/common';
+import { useDebounce } from '@/v2/hooks/debounce.hook';
+import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants';
+
+import {
+ DeletePendingKey,
+ DeletePendingKeysResponse
+} from '@/v2/types/insights.types';
+
+//-----Types------
+type DeletePendingKeysTableProps = {
+ paginationConfig: TablePaginationConfig
+ limit: Option;
+ handleLimitChange: (arg0: ValueType<Option, false>) => void;
+}
+
+type DeletePendingKeysColumns = {
+ fileName: string;
+ keyName: string;
+ dataSize: number;
+ keyCount: number;
+}
+
+type ExpandedDeletePendingKeys = {
+ omKeyInfoList: DeletePendingKey[]
+}
+
+//------Constants------
+const COLUMNS: ColumnsType<DeletePendingKeysColumns> = [
+ {
+ title: 'Key Name',
+ dataIndex: 'fileName',
+ key: 'fileName'
+ },
+ {
+ title: 'Path',
+ dataIndex: 'keyName',
+ key: 'keyName',
+ },
+ {
+ title: 'Total Data Size',
+ dataIndex: 'dataSize',
+ key: 'dataSize',
+ render: (dataSize: number) => byteToSize(dataSize, 1)
+ },
+ {
+ title: 'Total Key Count',
+ dataIndex: 'keyCount',
+ key: 'keyCount',
+ }
+];
+
+let expandedDeletePendingKeys: ExpandedDeletePendingKeys[] = [];
+
+//-----Components------
+const DeletePendingKeysTable: React.FC<DeletePendingKeysTableProps> = ({
+ paginationConfig,
+ limit,
+ handleLimitChange
+}) => {
+ const [loading, setLoading] = React.useState<boolean>(false);
+ const [data, setData] = React.useState<DeletePendingKeysColumns[]>();
+ const [searchTerm, setSearchTerm] = React.useState<string>('');
+
+ const cancelSignal = React.useRef<AbortController>();
+ const debouncedSearch = useDebounce(searchTerm, 300);
+
+ function filterData(data: DeletePendingKeysColumns[] | undefined) {
+ return data?.filter(
+ (data: DeletePendingKeysColumns) =>
data.keyName.includes(debouncedSearch)
+ );
+ }
+
+ function expandedRowRender(record: DeletePendingKeysColumns) {
+ const filteredData = expandedDeletePendingKeys?.flatMap((info) => (
+ info.omKeyInfoList?.filter((key) => key.keyName === record.keyName)
+ ));
+ return (
+ <ExpandedPendingKeysTable
+ data={filteredData}
+ paginationConfig={paginationConfig} />
+ )
+ }
+
+ function fetchDeletePendingKeys() {
+ setLoading(true);
+ const { request, controller } = AxiosGetHelper(
+ `/api/v1/keys/deletePending?limit=${limit.value}`,
+ cancelSignal.current
+ );
+ cancelSignal.current = controller;
+
+ request.then(response => {
+ const deletePendingKeys: DeletePendingKeysResponse = response?.data;
+ let deletedKeyData = [];
+ // Sum up the data size and organize related key information
+ deletedKeyData = deletePendingKeys?.deletedKeyInfo?.flatMap((keyInfo) =>
{
+ expandedDeletePendingKeys.push(keyInfo);
+ let count = 0;
+ let item: DeletePendingKey = keyInfo.omKeyInfoList?.reduce((obj, curr)
=> {
+ count += 1;
+ return { ...curr, dataSize: obj.dataSize + curr.dataSize };
+ }, { ...keyInfo.omKeyInfoList[0], dataSize: 0 });
+
+ return {
+ dataSize: item.dataSize,
+ fileName: item.fileName,
+ keyName: item.keyName,
+ path: item.path,
+ keyCount: count
+ }
+ });
+ setData(deletedKeyData);
+ setLoading(false);
+ }).catch(error => {
+ setLoading(false);
+ showDataFetchError((error as AxiosError).toString());
+ })
+ }
+
+ React.useEffect(() => {
+ fetchDeletePendingKeys();
+ expandedDeletePendingKeys = [];
+
+ return (() => {
+ cancelSignal.current && cancelSignal.current.abort();
+ })
+ }, [limit.value]);
+
+ return (
+ <>
+ <div className='table-header-section'>
+ <div className='table-filter-section'>
+ <SingleSelect
+ options={LIMIT_OPTIONS}
+ defaultValue={limit}
+ placeholder='Limit'
+ onChange={handleLimitChange} />
+ </div>
+ <Search
+ disabled={(data?.length ?? 0) < 1}
+ searchInput={searchTerm}
+ onSearchChange={
+ (e: React.ChangeEvent<HTMLInputElement>) =>
setSearchTerm(e.target.value)
+ }
+ onChange={() => { }} />
+ </div>
+ <Table
+ expandable={{
+ expandRowByClick: true,
+ expandedRowRender: expandedRowRender
+ }}
+ dataSource={filterData(data)}
+ columns={COLUMNS}
+ loading={loading}
+ pagination={paginationConfig}
+ rowKey='keyName'
+ locale={{ filterTitle: '' }}
+ scroll={{ x: 'max-content' }} />
+ </>
+ )
+}
+
+export default DeletePendingKeysTable;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletedContainerKeysTable.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletedContainerKeysTable.tsx
new file mode 100644
index 0000000000..9aaf62a63d
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletedContainerKeysTable.tsx
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { AxiosError } from 'axios';
+import Table, {
+ ColumnsType,
+ TablePaginationConfig
+} from 'antd/es/table';
+import { ValueType } from 'react-select';
+
+import Search from '@/v2/components/search/search';
+import SingleSelect, { Option } from '@/v2/components/select/singleSelect';
+import { AxiosGetHelper } from '@/utils/axiosRequestHelper';
+import { showDataFetchError } from '@/utils/common';
+import { useDebounce } from '@/v2/hooks/debounce.hook';
+import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants';
+
+import {
+ Container,
+ DeletedContainerKeysResponse,
+ Pipelines
+} from '@/v2/types/insights.types';
+
+//------Types-------
+type DeletedContainerKeysTableProps = {
+ paginationConfig: TablePaginationConfig;
+ limit: Option;
+ handleLimitChange: (arg0: ValueType<Option, false>) => void;
+ onRowExpand: (arg0: boolean, arg1: any) => void;
+ expandedRowRender: (arg0: any) => JSX.Element;
+}
+
+//------Constants------
+const COLUMNS: ColumnsType<Container> = [
+ {
+ title: 'Container ID',
+ dataIndex: 'containerId',
+ key: 'containerId',
+ width: '20%'
+ },
+ {
+ title: 'Count Of Keys',
+ dataIndex: 'numberOfKeys',
+ key: 'numberOfKeys',
+ sorter: (a: Container, b: Container) => a.numberOfKeys - b.numberOfKeys
+ },
+ {
+ title: 'Pipelines',
+ dataIndex: 'pipelines',
+ key: 'pipelines',
+ render: (pipelines: Pipelines[]) => (
+ <div>
+ {pipelines && pipelines.map((pipeline: any) => (
+ <div key={pipeline.id.id}>
+ {pipeline.id.id}
+ </div>
+ ))}
+ </div>
+ )
+ }
+];
+
+//-----Components------
+const DeletedContainerKeysTable: React.FC<DeletedContainerKeysTableProps> = ({
+ limit,
+ paginationConfig,
+ handleLimitChange,
+ onRowExpand,
+ expandedRowRender
+}) => {
+
+ const [loading, setLoading] = React.useState<boolean>(false);
+ const [data, setData] = React.useState<Container[]>();
+ const [searchTerm, setSearchTerm] = React.useState<string>('');
+
+ const cancelSignal = React.useRef<AbortController>();
+ const debouncedSearch = useDebounce(searchTerm, 300);
+
+ function filterData(data: Container[] | undefined) {
+ return data?.filter(
+ (data: Container) =>
data.containerId.toString().includes(debouncedSearch)
+ );
+ }
+
+ function fetchDeletedKeys() {
+ const { request, controller } = AxiosGetHelper(
+ `/api/v1/containers/mismatch/deleted?limit=${limit.value}`,
+ cancelSignal.current
+ )
+ cancelSignal.current = controller;
+
+ request.then(response => {
+ setLoading(true);
+ const deletedContainerKeys: DeletedContainerKeysResponse =
response?.data;
+ setData(deletedContainerKeys?.containers ?? []);
+ setLoading(false);
+ }).catch(error => {
+ setLoading(false);
+ showDataFetchError((error as AxiosError).toString());
+ });
+ }
+
+ React.useEffect(() => {
+ fetchDeletedKeys();
+
+ return (() => {
+ cancelSignal.current && cancelSignal.current.abort();
+ })
+ }, [limit.value]);
+
+
+ return (
+ <>
+ <div className='table-header-section'>
+ <div className='table-filter-section'>
+ <SingleSelect
+ options={LIMIT_OPTIONS}
+ defaultValue={limit}
+ placeholder='Limit'
+ onChange={handleLimitChange} />
+ </div>
+ <Search
+ disabled={(data?.length ?? 0) < 1}
+ searchInput={searchTerm}
+ onSearchChange={
+ (e: React.ChangeEvent<HTMLInputElement>) =>
setSearchTerm(e.target.value)
+ }
+ onChange={() => { }} />
+ </div>
+ <Table
+ expandable={{
+ expandRowByClick: true,
+ expandedRowRender: expandedRowRender,
+ onExpand: onRowExpand
+ }}
+ dataSource={filterData(data)}
+ columns={COLUMNS}
+ loading={loading}
+ pagination={paginationConfig}
+ rowKey='containerId'
+ locale={{ filterTitle: '' }}
+ scroll={{ x: 'max-content' }} />
+ </>
+ )
+}
+
+export default DeletedContainerKeysTable;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/expandedKeyTable.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/expandedKeyTable.tsx
new file mode 100644
index 0000000000..8b54937e47
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/expandedKeyTable.tsx
@@ -0,0 +1,93 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import React from 'react';
+import moment from 'moment';
+import filesize from 'filesize';
+import Table, {
+ ColumnsType,
+ TablePaginationConfig
+} from 'antd/es/table';
+
+import { MismatchKeys } from '@/v2/types/insights.types';
+
+
+const size = filesize.partial({ standard: 'iec' });
+
+//-----Types------
+type ExpandedKeyTableProps = {
+ loading: boolean;
+ data: MismatchKeys[];
+ paginationConfig: TablePaginationConfig;
+}
+
+//-----Constants-----
+const COLUMNS: ColumnsType<MismatchKeys> = [
+ {
+ title: 'Volume',
+ dataIndex: 'Volume',
+ key: 'Volume'
+ },
+ {
+ title: 'Bucket',
+ dataIndex: 'Bucket',
+ key: 'Bucket'
+ },
+ {
+ title: 'Key',
+ dataIndex: 'Key',
+ key: 'Key'
+ },
+ {
+ title: 'Size',
+ dataIndex: 'DataSize',
+ key: 'DataSize',
+ render: (dataSize: number) => <div>{size(dataSize)}</div>
+ },
+ {
+ title: 'Date Created',
+ dataIndex: 'CreationTime',
+ key: 'CreationTime',
+ render: (date: string) => moment(date).format('lll')
+ },
+ {
+ title: 'Date Modified',
+ dataIndex: 'ModificationTime',
+ key: 'ModificationTime',
+ render: (date: string) => moment(date).format('lll')
+ }
+];
+
+//-----Components------
+const ExpandedKeyTable: React.FC<ExpandedKeyTableProps> = ({
+ loading,
+ data,
+ paginationConfig
+}) => {
+ return (
+ <Table
+ loading={loading}
+ dataSource={data}
+ columns={COLUMNS}
+ pagination={paginationConfig}
+ rowKey='uid'
+ locale={{ filterTitle: '' }} />
+ )
+}
+
+export default ExpandedKeyTable;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/expandedPendingKeysTable.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/expandedPendingKeysTable.tsx
new file mode 100644
index 0000000000..accb390303
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/expandedPendingKeysTable.tsx
@@ -0,0 +1,81 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import React from 'react';
+import Table, {
+ ColumnsType,
+ TablePaginationConfig
+} from 'antd/es/table';
+
+import { byteToSize } from '@/utils/common';
+import { getFormattedTime } from '@/v2/utils/momentUtils';
+
+import { DeletePendingKey } from '@/v2/types/insights.types';
+
+//--------Types--------
+type ExpandedPendingKeysTableProps = {
+ data: DeletePendingKey[];
+ paginationConfig: TablePaginationConfig;
+}
+
+//--------Constants--------
+const COLUMNS: ColumnsType<DeletePendingKey> = [{
+ title: 'Data Size',
+ dataIndex: 'dataSize',
+ key: 'dataSize',
+ render: (dataSize: any) => dataSize = dataSize > 0 ? byteToSize(dataSize, 1)
: dataSize
+},
+{
+ title: 'Replicated Data Size',
+ dataIndex: 'replicatedSize',
+ key: 'replicatedSize',
+ render: (replicatedSize: any) => replicatedSize = replicatedSize > 0 ?
byteToSize(replicatedSize, 1) : replicatedSize
+},
+{
+ title: 'Creation Time',
+ dataIndex: 'creationTime',
+ key: 'creationTime',
+ render: (creationTime: number) => {
+ return getFormattedTime(creationTime, 'll LTS');
+ }
+},
+{
+ title: 'Modification Time',
+ dataIndex: 'modificationTime',
+ key: 'modificationTime',
+ render: (modificationTime: number) => {
+ return getFormattedTime(modificationTime, 'll LTS');
+ }
+}]
+
+//--------Component--------
+const ExpandedPendingKeysTable: React.FC<ExpandedPendingKeysTableProps> = ({
+ data,
+ paginationConfig
+}) => {
+ return (
+ <Table
+ dataSource={data}
+ columns={COLUMNS}
+ pagination={paginationConfig}
+ rowKey='dataSize'
+ locale={{ filterTitle: '' }} />
+ )
+}
+
+export default ExpandedPendingKeysTable;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/openKeysTable.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/openKeysTable.tsx
new file mode 100644
index 0000000000..02c73c7752
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/openKeysTable.tsx
@@ -0,0 +1,213 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import React from 'react';
+import { AxiosError } from 'axios';
+import {
+ Dropdown,
+ Menu,
+ Table
+} from 'antd';
+import {
+ ColumnsType,
+ TablePaginationConfig
+} from 'antd/es/table';
+import { MenuProps } from 'antd/es/menu';
+import { FilterFilled } from '@ant-design/icons';
+import { ValueType } from 'react-select';
+
+import Search from '@/v2/components/search/search';
+import SingleSelect, { Option } from '@/v2/components/select/singleSelect';
+import { AxiosGetHelper } from '@/utils/axiosRequestHelper';
+import { byteToSize, showDataFetchError } from '@/utils/common';
+import { getFormattedTime } from '@/v2/utils/momentUtils';
+import { useDebounce } from '@/v2/hooks/debounce.hook';
+import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants';
+
+import { OpenKeys, OpenKeysResponse } from '@/v2/types/insights.types';
+
+
+//--------Types--------
+type OpenKeysTableProps = {
+ limit: Option;
+ paginationConfig: TablePaginationConfig;
+ handleLimitChange: (arg0: ValueType<Option, false>) => void;
+}
+
+//-----Components------
+const OpenKeysTable: React.FC<OpenKeysTableProps> = ({
+ limit,
+ paginationConfig,
+ handleLimitChange
+}) => {
+ const [loading, setLoading] = React.useState<boolean>(false);
+ const [data, setData] = React.useState<OpenKeys[]>();
+ const [searchTerm, setSearchTerm] = React.useState<string>('');
+
+ const cancelSignal = React.useRef<AbortController>();
+ const debouncedSearch = useDebounce(searchTerm, 300);
+
+ function filterData(data: OpenKeys[] | undefined) {
+ return data?.filter(
+ (data: OpenKeys) => data.path.includes(debouncedSearch)
+ );
+ }
+
+ function fetchOpenKeys(isFso: boolean) {
+ setLoading(true);
+
+ const { request, controller } = AxiosGetHelper(
+
`/api/v1/keys/open?includeFso=${isFso}&includeNonFso=${!isFso}&limit=${limit.value}`,
+ cancelSignal.current
+ );
+ cancelSignal.current = controller;
+
+ request.then(response => {
+ const openKeys: OpenKeysResponse = response?.data ?? { 'fso': [] };
+ let allOpenKeys: OpenKeys[];
+ if (isFso) {
+ allOpenKeys = openKeys['fso']?.map((key: OpenKeys) => ({
+ ...key,
+ type: 'FSO'
+ })) ?? [];
+ } else {
+ allOpenKeys = openKeys['nonFSO']?.map((key: OpenKeys) => ({
+ ...key,
+ type: 'Non FSO'
+ })) ?? [];
+ }
+
+ setData(allOpenKeys);
+ setLoading(false);
+ }).catch(error => {
+ setLoading(false);
+ showDataFetchError((error as AxiosError).toString());
+ });
+ }
+
+ const handleKeyTypeChange: MenuProps['onClick'] = (e) => {
+ if (e.key === 'fso') {
+ fetchOpenKeys(true);
+ } else {
+ fetchOpenKeys(false);
+ }
+ }
+
+ const COLUMNS: ColumnsType<OpenKeys> = [{
+ title: 'Key Name',
+ dataIndex: 'path',
+ key: 'path'
+ },
+ {
+ title: 'Size',
+ dataIndex: 'size',
+ key: 'size',
+ render: (size: any) => size = byteToSize(size, 1)
+ },
+ {
+ title: 'Path',
+ dataIndex: 'key',
+ key: 'key',
+ width: '270px'
+ },
+ {
+ title: 'In state since',
+ dataIndex: 'inStateSince',
+ key: 'inStateSince',
+ render: (inStateSince: number) => {
+ return getFormattedTime(inStateSince, 'll LTS');
+ }
+ },
+ {
+ title: 'Replication Factor',
+ dataIndex: 'replicationInfo',
+ key: 'replicationfactor',
+ render: (replicationInfo: any) => (
+ <div>
+ {Object.values(replicationInfo)[0]}
+ </div>
+ )
+ },
+ {
+ title: 'Replication Type',
+ dataIndex: 'replicationInfo',
+ key: 'replicationtype',
+ render: (replicationInfo: any) => (
+ <div>
+ {
+ <div >
+ {Object.values(replicationInfo)[2]}
+ </div>
+ }
+ </div>
+ )
+ }, {
+ title: <>
+ <Dropdown
+ overlay={
+ <Menu onClick={handleKeyTypeChange}>
+ <Menu.Item key='fso'>FSO</Menu.Item>
+ <Menu.Item key='nonFSO'> Non-FSO</Menu.Item>
+ </Menu>
+ }>
+ <label> Key Type <FilterFilled /></label>
+ </Dropdown>
+ </>,
+ dataIndex: 'type',
+ key: 'type',
+ render: (type: string) => <div key={type}>{type}</div>
+ }];
+
+ React.useEffect(() => {
+ // Fetch FSO open keys by default
+ fetchOpenKeys(true);
+
+ return (() => cancelSignal.current && cancelSignal.current.abort());
+ }, [limit.value]);
+
+ return (
+ <>
+ <div className='table-header-section'>
+ <div className='table-filter-section'>
+ <SingleSelect
+ options={LIMIT_OPTIONS}
+ defaultValue={limit}
+ placeholder='Limit'
+ onChange={handleLimitChange} />
+ </div>
+ <Search
+ disabled={(data?.length ?? 0) < 1}
+ searchInput={searchTerm}
+ onSearchChange={
+ (e: React.ChangeEvent<HTMLInputElement>) =>
setSearchTerm(e.target.value)
+ }
+ onChange={() => { }} />
+ </div>
+ <Table
+ dataSource={filterData(data)}
+ columns={COLUMNS}
+ loading={loading}
+ rowKey='key'
+ pagination={paginationConfig}
+ locale={{ filterTitle: '' }}
+ scroll={{ x: 'max-content' }} />
+ </>
+ );
+}
+
+export default OpenKeysTable;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/volumesTable.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/volumesTable.tsx
index 4de0d713fc..ecfbf730a2 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/volumesTable.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/volumesTable.tsx
@@ -96,7 +96,6 @@ const VolumesTable: React.FC<VolumesTableProps> = ({
React.useEffect(() => {
// On table mount add the actions column
- console.log("Adding new column");
const actionsColumn: ColumnType<Volume> = {
title: 'Actions',
key: 'actions',
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/constants/breadcrumbs.constants.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/breadcrumbs.constants.tsx
similarity index 88%
copy from
hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/constants/breadcrumbs.constants.tsx
copy to
hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/breadcrumbs.constants.tsx
index bc81e86dcb..807a68cc8d 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/constants/breadcrumbs.constants.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/breadcrumbs.constants.tsx
@@ -16,20 +16,19 @@
* limitations under the License.
*/
-interface IBreadcrumbNameMap {
+type BreadcrumbNameMap = {
[path: string]: string;
}
-export const breadcrumbNameMap: IBreadcrumbNameMap = {
+export const breadcrumbNameMap: BreadcrumbNameMap = {
'/Overview': 'Overview',
'/Volumes': 'Volumes',
'/Buckets': 'Buckets',
'/Datanodes': 'Datanodes',
'/Pipelines': 'Pipelines',
- '/MissingContainers': 'Missing Containers',
'/Containers': 'Containers',
'/Insights': 'Insights',
'/DiskUsage': 'Disk Usage',
'/Heatmap': 'Heatmap',
- '/Om': 'Om',
+ '/Om': 'OM DB Insights'
};
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/constants/breadcrumbs.constants.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/limit.constants.tsx
similarity index 65%
copy from
hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/constants/breadcrumbs.constants.tsx
copy to
hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/limit.constants.tsx
index bc81e86dcb..b76c51c896 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/constants/breadcrumbs.constants.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/limit.constants.tsx
@@ -15,21 +15,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import { Option } from '@/v2/components/select/singleSelect';
-interface IBreadcrumbNameMap {
- [path: string]: string;
-}
-
-export const breadcrumbNameMap: IBreadcrumbNameMap = {
- '/Overview': 'Overview',
- '/Volumes': 'Volumes',
- '/Buckets': 'Buckets',
- '/Datanodes': 'Datanodes',
- '/Pipelines': 'Pipelines',
- '/MissingContainers': 'Missing Containers',
- '/Containers': 'Containers',
- '/Insights': 'Insights',
- '/DiskUsage': 'Disk Usage',
- '/Heatmap': 'Heatmap',
- '/Om': 'Om',
-};
+export const LIMIT_OPTIONS: Option[] = [
+ {
+ label: '1000',
+ value: '1000'
+ },
+ {
+ label: '5000',
+ value: '5000'
+ },
+ {
+ label: '10000',
+ value: '10000'
+ },
+ {
+ label: '20000',
+ value: '20000'
+ }
+];
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx
index add571cec0..1c039f4270 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx
@@ -31,6 +31,7 @@ import BucketsTable, { COLUMNS } from
'@/v2/components/tables/bucketsTable';
import { AutoReloadHelper } from '@/utils/autoReloadHelper';
import { AxiosGetHelper, cancelRequests } from "@/utils/axiosRequestHelper";
import { showDataFetchError } from '@/utils/common';
+import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants';
import { useDebounce } from '@/v2/hooks/debounce.hook';
import {
@@ -41,26 +42,6 @@ import {
import './buckets.less';
-
-const LIMIT_OPTIONS: Option[] = [
- {
- label: '1000',
- value: '1000'
- },
- {
- label: '5000',
- value: '5000'
- },
- {
- label: '10000',
- value: '10000'
- },
- {
- label: '20000',
- value: '20000'
- }
-]
-
const SearchableColumnOpts = [{
label: 'Bucket',
value: 'name'
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/diskUsage/diskUsage.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/diskUsage/diskUsage.tsx
index 41da4565ac..57d7a612c3 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/diskUsage/diskUsage.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/diskUsage/diskUsage.tsx
@@ -27,7 +27,7 @@ import {
import { ValueType } from 'react-select';
import DUMetadata from '@/v2/components/duMetadata/duMetadata';
-import DUPieChart from '@/v2/components/duPieChart/duPieChart';
+import DUPieChart from '@/v2/components/plots/duPieChart';
import SingleSelect, { Option } from '@/v2/components/select/singleSelect';
import DUBreadcrumbNav from '@/v2/components/duBreadcrumbNav/duBreadcrumbNav';
import { showDataFetchError } from '@/utils/common';
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.less
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.less
new file mode 100644
index 0000000000..dfc0ef3143
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.less
@@ -0,0 +1,36 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+.content-div {
+ min-height: unset;
+
+ .table-header-section {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .table-filter-section {
+ font-size: 14px;
+ font-weight: normal;
+ display: flex;
+ column-gap: 8px;
+ padding: 16px 8px;
+ align-items: center;
+ }
+ }
+}
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.tsx
new file mode 100644
index 0000000000..f2a2c3e3f7
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.tsx
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState } from 'react';
+import axios, {
+ CanceledError,
+ AxiosError
+} from 'axios';
+import { Row, Col, Card, Result } from 'antd';
+
+import { showDataFetchError } from '@/utils/common';
+import { PromiseAllSettledGetHelper } from '@/utils/axiosRequestHelper';
+
+import { Option } from '@/v2/components/select/multiSelect';
+import FileSizeDistribution from '@/v2/components/plots/insightsFilePlot';
+import ContainerSizeDistribution from
'@/v2/components/plots/insightsContainerPlot';
+
+import {
+ FileCountResponse,
+ InsightsState,
+ PlotResponse,
+} from '@/v2/types/insights.types';
+
+const Insights: React.FC<{}> = () => {
+
+ const [loading, setLoading] = useState<boolean>(false);
+ const [state, setState] = useState<InsightsState>({
+ volumeBucketMap: new Map<string, Set<string>>(),
+ volumeOptions: [],
+ fileCountError: undefined,
+ containerSizeError: undefined
+ });
+ const [plotResponse, setPlotResponse] = useState<PlotResponse>({
+ fileCountResponse: [{
+ volume: '',
+ bucket: '',
+ fileSize: 0,
+ count: 0
+ }],
+ containerCountResponse: [{
+ containerSize: 0,
+ count: 0
+ }]
+ });
+
+ const cancelInsightSignal = React.useRef<AbortController>();
+
+ function loadData() {
+ setLoading(true);
+ const { requests, controller } = PromiseAllSettledGetHelper([
+ '/api/v1/utilization/fileCount',
+ '/api/v1/utilization/containerCount'
+ ], cancelInsightSignal.current);
+
+ cancelInsightSignal.current = controller;
+ requests.then(axios.spread((
+ fileCountResponse: Awaited<Promise<any>>,
+ containerCountResponse: Awaited<Promise<any>>
+ ) => {
+ let fileAPIError;
+ let containerAPIError;
+ let responseError = [
+ fileCountResponse,
+ containerCountResponse
+ ].filter((resp) => resp.status === 'rejected');
+
+ if (responseError.length !== 0) {
+ responseError.forEach((err) => {
+ if (err.reason.toString().includes('CancelledError')) {
+ throw new CanceledError('canceled', 'ERR_CANCELED');
+ } else {
+ if (err.reason.config.url.includes("fileCount")) {
+ fileAPIError = err.reason.toString();
+ } else {
+ containerAPIError = err.reason.toString();
+ }
+ }
+ });
+ }
+
+ // Construct volume -> bucket[] map for populating filters
+ // Ex: vol1 -> [bucket1, bucket2], vol2 -> [bucket1]
+ const volumeBucketMap: Map<string, Set<string>> =
fileCountResponse.value?.data?.reduce(
+ (map: Map<string, Set<string>>, current: FileCountResponse) => {
+ const volume = current.volume;
+ const bucket = current.bucket;
+ if (map.has(volume)) {
+ const buckets = Array.from(map.get(volume)!);
+ map.set(volume, new Set<string>([...buckets, bucket]));
+ } else {
+ map.set(volume, new Set<string>().add(bucket));
+ }
+ return map;
+ },
+ new Map<string, Set<string>>()
+ );
+ const volumeOptions: Option[] = Array.from(volumeBucketMap.keys()).map(k
=> ({
+ label: k,
+ value: k
+ }));
+
+ setState({
+ ...state,
+ volumeBucketMap: volumeBucketMap,
+ volumeOptions: volumeOptions,
+ fileCountError: fileAPIError,
+ containerSizeError: containerAPIError
+ });
+ setPlotResponse({
+ fileCountResponse: fileCountResponse.value?.data ?? [{
+ volume: '',
+ bucket: '',
+ fileSize: 0,
+ count: 0
+ }],
+ containerCountResponse: containerCountResponse.value?.data ?? [{
+ containerSize: 0,
+ count: 0
+ }]
+ });
+ setLoading(false);
+ })).catch(error => {
+ setLoading(false);
+ showDataFetchError((error as AxiosError).toString());
+ })
+ }
+
+ React.useEffect(() => {
+ loadData();
+
+ return (() => {
+ cancelInsightSignal.current && cancelInsightSignal.current.abort();
+ })
+ }, []);
+
+ return (
+ <>
+ <div className='page-header-v2'>
+ Insights
+ </div>
+ <div style={{ padding: '24px' }}>
+ {
+ loading
+ ? <Result title='Charts are being loaded' />
+ : <>
+ <Row gutter={20}>
+ <Col xs={24} xl={12}>
+ <Card title='File Size Distribution' size='small'>
+ {plotResponse.fileCountResponse?.length > 0
+ ? <FileSizeDistribution
+ volumeOptions={state.volumeOptions}
+ volumeBucketMap={state.volumeBucketMap}
+ fileCountError={state.fileCountError}
+ fileCountResponse={plotResponse.fileCountResponse} />
+ : <Result
+ title='No Data'
+ subTitle='Add files to Ozone to see a visualization on
file size distribution.' />}
+
+ </Card>
+ </Col>
+ <Col xs={24} xl={12}>
+ <Card title='Container Size Distribution' size='small'>
+ {plotResponse.containerCountResponse?.length > 0
+ ? <ContainerSizeDistribution
+
containerCountResponse={plotResponse.containerCountResponse}
+ containerSizeError={state.containerSizeError} />
+ : <Result
+ title='No Data'
+ subTitle='Add files to Ozone to see a visualization on
container size distribution.' />}
+ </Card>
+ </Col>
+ </Row>
+ </>
+ }
+ </div>
+ </>
+ )
+
+}
+
+export default Insights;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/omInsights.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/omInsights.tsx
new file mode 100644
index 0000000000..732af0aa00
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/omInsights.tsx
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { AxiosError } from 'axios';
+import { ValueType } from 'react-select';
+import { Tabs, Tooltip } from 'antd';
+import { TablePaginationConfig } from 'antd/es/table';
+import { InfoCircleOutlined } from '@ant-design/icons';
+
+import { Option } from '@/v2/components/select/singleSelect';
+import ContainerMismatchTable from
'@/v2/components/tables/insights/containerMismatchTable';
+import DeletedContainerKeysTable from
'@/v2/components/tables/insights/deletedContainerKeysTable';
+import DeletePendingDirTable from
'@/v2/components/tables/insights/deletePendingDirsTable';
+import DeletePendingKeysTable from
'@/v2/components/tables/insights/deletePendingKeysTable';
+import ExpandedKeyTable from
'@/v2/components/tables/insights/expandedKeyTable';
+import OpenKeysTable from '@/v2/components/tables/insights/openKeysTable';
+import { showDataFetchError } from '@/utils/common';
+import { AxiosGetHelper } from '@/utils/axiosRequestHelper';
+
+import {
+ Container,
+ ExpandedRow,
+ ExpandedRowState,
+ MismatchKeysResponse
+} from '@/v2/types/insights.types';
+
+import './insights.less';
+import { useLocation } from 'react-router-dom';
+
+
+const OMDBInsights: React.FC<{}> = () => {
+
+ const [loading, setLoading] = React.useState<boolean>(false);
+ const [expandedRowData, setExpandedRowData] =
React.useState<ExpandedRow>({});
+ const [selectedLimit, setSelectedLimit] = React.useState<Option>({
+ label: '1000',
+ value: '1000'
+ });
+
+ const rowExpandSignal = React.useRef<AbortController>();
+ const {state: { activeTab } = {}} = useLocation<Record<string, any>>();
+
+ const paginationConfig: TablePaginationConfig = {
+ showTotal: (total: number, range) => `${range[0]}-${range[1]} of ${total}`
+ };
+
+ function onRowExpandClick(expanded: boolean, record: Container) {
+ if (expanded) {
+ setLoading(true);
+
+ const { request, controller } = AxiosGetHelper(
+ `/api/v1/containers/${record.containerId}/keys`,
+ rowExpandSignal.current
+ );
+ rowExpandSignal.current = controller;
+
+ request.then(response => {
+ const containerKeysResponse: MismatchKeysResponse = response?.data;
+ const expandedRowState: ExpandedRowState = Object.assign(
+ {},
+ expandedRowData[record.containerId],
+ {
+ dataSource: containerKeysResponse?.keys ?? [],
+ totalCount: containerKeysResponse?.totalCount ?? 0
+ }
+ );
+ setExpandedRowData(Object.assign(
+ {},
+ expandedRowData,
+ { [record.containerId]: expandedRowState }
+ ));
+ setLoading(false);
+ }).catch(error => {
+ showDataFetchError((error as AxiosError).toString());
+ });
+ }
+ }
+
+ function expandedRowRender(record: Container) {
+ const containerId = record.containerId;
+ if (expandedRowData[containerId]) {
+ const containerKeys: ExpandedRowState = expandedRowData[containerId];
+ const data = containerKeys?.dataSource?.map(record => (
+ { ...record, uid: `${record.Volume}/${record.Bucket}/${record.Key}` }
+ ));
+
+ return (
+ <ExpandedKeyTable
+ loading={loading}
+ data={data} paginationConfig={paginationConfig} />
+ )
+ }
+ return <div>Loading...</div>
+ }
+
+ function handleLimitChange(selected: ValueType<Option, false>) {
+ setSelectedLimit(selected as Option);
+ }
+
+ return (<>
+ <div className='page-header-v2'>
+ OM DB Insights
+ </div>
+ <div style={{ padding: '24px' }}>
+ <div className='content-div'>
+ <Tabs defaultActiveKey={activeTab ?? '1'}>
+ <Tabs.TabPane key='1' tab={
+ <label>
+ Container Mismatch Info
+ <Tooltip
+ placement='right'
+ title='Containers which are present in OM and missing in SCM
or vice-versa'>
+ <InfoCircleOutlined style={{ marginLeft: '10px' }} />
+ </Tooltip>
+ </label>
+ }>
+ <ContainerMismatchTable
+ limit={selectedLimit}
+ paginationConfig={paginationConfig}
+ expandedRowRender={expandedRowRender}
+ onRowExpand={onRowExpandClick}
+ handleLimitChange={handleLimitChange} />
+ </Tabs.TabPane>
+ <Tabs.TabPane key='2' tab={
+ <label>
+ Open Keys
+ <Tooltip
+ placement='right'
+ title='Keys which are not yet committed but currently being
written'>
+ <InfoCircleOutlined style={{ marginLeft: '10px' }} />
+ </Tooltip>
+ </label>
+ }>
+ <OpenKeysTable
+ limit={selectedLimit}
+ paginationConfig={paginationConfig}
+ handleLimitChange={handleLimitChange} />
+ </Tabs.TabPane>
+ <Tabs.TabPane key='3' tab={
+ <label>
+ Keys Pending for Deletion
+ <Tooltip
+ placement='right'
+ title='Keys that are pending for deletion'>
+ <InfoCircleOutlined style={{ marginLeft: '10px' }} />
+ </Tooltip>
+ </label>
+ }>
+ <DeletePendingKeysTable
+ limit={selectedLimit}
+ paginationConfig={paginationConfig}
+ handleLimitChange={handleLimitChange} />
+ </Tabs.TabPane>
+ <Tabs.TabPane key='4' tab={
+ <label>
+ Deleted Container Keys
+ <Tooltip
+ placement='right'
+ title='Keys mapped to Containers in DELETED state SCM.'>
+ <InfoCircleOutlined style={{ marginLeft: '10px' }} />
+ </Tooltip>
+ </label>
+ }>
+ <DeletedContainerKeysTable
+ limit={selectedLimit}
+ paginationConfig={paginationConfig}
+ handleLimitChange={handleLimitChange}
+ onRowExpand={onRowExpandClick}
+ expandedRowRender={expandedRowRender} />
+ </Tabs.TabPane>
+ <Tabs.TabPane key='5' tab={
+ <label>
+ Directories Pending for Deletion
+ <Tooltip
+ placement='right'
+ title='Directories that are pending for deletion.'>
+ <InfoCircleOutlined style={{ marginLeft: '10px' }} />
+ </Tooltip>
+ </label>
+ }>
+ <DeletePendingDirTable
+ limit={selectedLimit}
+ paginationConfig={paginationConfig}
+ handleLimitChange={handleLimitChange} />
+ </Tabs.TabPane>
+ </Tabs>
+ </div>
+ </div>
+ </>)
+}
+
+export default OMDBInsights;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.tsx
index 458c363d6d..c7f5a4b95d 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.tsx
@@ -485,7 +485,8 @@ const Overview: React.FC<{}> = () => {
)
}
]}
- linkToUrl='/Om' />
+ linkToUrl='/Om'
+ state={{activeTab: '2'}} />
</Col>
<Col xs={24} sm={24} md={24} lg={12} xl={12}>
<OverviewSummaryCard
@@ -524,7 +525,8 @@ const Overview: React.FC<{}> = () => {
)
}
]}
- linkToUrl='/Om' />
+ linkToUrl='/Om'
+ state={{activeTab: '3'}} />
</Col>
</Row>
<span style={{ paddingLeft: '8px' }}>
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx
index 0b35a676d1..b4614d387f 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx
@@ -30,6 +30,7 @@ import Search from '@/v2/components/search/search';
import { showDataFetchError } from '@/utils/common';
import { AutoReloadHelper } from '@/utils/autoReloadHelper';
import { AxiosGetHelper, cancelRequests } from "@/utils/axiosRequestHelper";
+import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants';
import { useDebounce } from '@/v2/hooks/debounce.hook';
import {
@@ -55,13 +56,6 @@ const SearchableColumnOpts = [
}
]
-const LIMIT_OPTIONS: Option[] = [
- { label: '1000', value: '1000' },
- { label: '5000', value: "5000" },
- { label: '10000', value: "10000" },
- { label: '20000', value: "20000" }
-]
-
const Volumes: React.FC<{}> = () => {
const cancelSignal = useRef<AbortController>();
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/routes-v2.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/routes-v2.tsx
index 942ad16dcd..9f27b7cae7 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/routes-v2.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/routes-v2.tsx
@@ -24,6 +24,8 @@ const Datanodes = lazy(() =>
import('@/v2/pages/datanodes/datanodes'));
const Pipelines = lazy(() => import('@/v2/pages/pipelines/pipelines'));
const DiskUsage = lazy(() => import('@/v2/pages/diskUsage/diskUsage'));
const Containers = lazy(() => import('@/v2/pages/containers/containers'));
+const Insights = lazy(() => import('@/v2/pages/insights/insights'));
+const OMDBInsights = lazy(() => import('@/v2/pages/insights/omInsights'));
export const routesV2 = [
{
@@ -53,5 +55,13 @@ export const routesV2 = [
{
path: '/Containers',
component: Containers
+ },
+ {
+ path: '/Insights',
+ component: Insights
+ },
+ {
+ path: '/Om',
+ component: OMDBInsights
}
];
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/insights.types.ts
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/insights.types.ts
new file mode 100644
index 0000000000..d608a2bc8d
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/insights.types.ts
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Option } from "@/v2/components/select/multiSelect";
+
+export type FileCountResponse = {
+ volume: string;
+ bucket: string;
+ fileSize: number;
+ count: number;
+}
+
+export type ContainerCountResponse = {
+ containerSize: number;
+ count: number;
+}
+
+export type PlotResponse = {
+ fileCountResponse: FileCountResponse[],
+ containerCountResponse: ContainerCountResponse[]
+}
+
+export type FilePlotData = {
+ fileCountValues: string[];
+ fileCountMap: Map<number, number>;
+}
+
+export type ContainerPlotData = {
+ containerCountValues: string[];
+ containerCountMap: Map<number, number>;
+}
+
+export type InsightsState = {
+ volumeBucketMap: Map<string, Set<string>>;
+ volumeOptions: Option[];
+ fileCountError: string | undefined;
+ containerSizeError: string | undefined;
+}
+
+//-------------------------//
+//---OM DB Insights types---
+//-------------------------//
+type ReplicationConfig = {
+ replicationFactor: string;
+ requiredNodes: number;
+ replicationType: string;
+}
+
+export type Pipelines = {
+ id: {
+ id: string;
+ },
+ replicationConfig: ReplicationConfig;
+ healthy: boolean;
+}
+
+// Container Mismatch Info
+export type Container = {
+ containerId: number;
+ numberOfKeys: number;
+ pipelines: Pipelines[];
+ existsAt: 'OM' | 'SCM';
+}
+
+export type MismatchContainersResponse = {
+ containerDiscrepancyInfo: Container[];
+}
+
+// Deleted Container Keys
+export type DeletedContainerKeysResponse = {
+ containers: Container[];
+}
+
+export type MismatchKeys = {
+ Volume: string;
+ Bucket: string;
+ Key: string;
+ DataSize: number;
+ Versions: number[];
+ Blocks: Record<string, []>
+ CreationTime: string;
+ ModificationTime: string;
+}
+
+export type MismatchKeysResponse = {
+ totalCount: number;
+ keys: MismatchKeys[];
+}
+
+// Open Keys
+export type OpenKeys = {
+ key: string;
+ path: string;
+ inStateSince: number;
+ size: number;
+ replicatedSize: number;
+ replicationInfo: {
+ data: number;
+ parity: number;
+ ecChunkSize: number;
+ codec: string;
+ replicationType: string;
+ requiredNodes: number;
+ }
+ creationTime: number;
+ modificationTime: number;
+ isKey: boolean;
+}
+
+export type OpenKeysResponse = {
+ lastKey: string;
+ replicatedDataSize: number;
+ unreplicatedDataSize: number;
+ fso?: OpenKeys[];
+ nonFSO?: OpenKeys[];
+}
+
+//Keys pending deletion
+export type DeletePendingKey = {
+ objectID: number;
+ updateID: number;
+ parentObjectID: number;
+ volumeName: string;
+ bucketName: string;
+ keyName: string;
+ dataSize: number;
+ creationTime: number;
+ modificationTime: number;
+ replicationConfig: ReplicationConfig;
+ fileChecksum: number | null;
+ fileName: string;
+ file: boolean;
+ path: string;
+ hsync: boolean;
+ replicatedSize: number;
+ fileEncryptionInfo: string | null;
+ objectInfo: string;
+ updateIDSet: boolean;
+}
+
+export type DeletePendingKeysResponse = {
+ lastKey: string;
+ keysSummary: {
+ totalUnreplicatedDataSize: number,
+ totalReplicatedDataSize: number,
+ totalDeletedKeys: number
+ },
+ replicatedDataSize: number;
+ unreplicatedDataSize: number;
+ deletedKeyInfo: {
+ omKeyInfoList: DeletePendingKey[]
+ }[];
+}
+
+//Directories Pending for Deletion
+export type DeletedDirInfo = {
+ key: string;
+ path: string;
+ inStateSince: number;
+ size: number;
+ replicatedSize: number;
+ replicationInfo: ReplicationConfig;
+ creationTime: number;
+ modificationTime: number;
+ isKey: boolean;
+}
+
+export type DeletedDirReponse = {
+ lastKey: string;
+ replicatedDataSize: number;
+ unreplicatedDataSize: number;
+ deletedDirInfo: DeletedDirInfo[];
+ status: string;
+}
+
+export type ExpandedRow = {
+ [key: number]: ExpandedRowState;
+}
+
+export type ExpandedRowState = {
+ containerId: number;
+ dataSource: MismatchKeys[];
+ totalCount: number;
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]