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 c6202027676e..784ee8302e35 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 78954ebb5a54..ceb633f603e7 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, IAppState> {
- + {(enableNewUI) ? : } New UI
} 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 bc81e86dcb3c..a9f7a53eda44 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 000000000000..8e1c34decb7a --- /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 ( + + + {breadcrumbNameMap[url]} + + + ) + }); + + const breadcrumbItems = [( + + + + )].concat(extraBreadcrumbItems); + + return ( + + {breadcrumbItems} + + ); +} + +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 e383512f20e8..8736b3e0d290 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; } // ------------- Styles -------------- // @@ -63,15 +64,18 @@ const OverviewSummaryCard: React.FC = ({ columns = [], tableData = [], linkToUrl = '', - showHeader = false + showHeader = false, + state }) => { - const titleElement = (linkToUrl) ? (
{title} View Insights 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 000000000000..851c355e765c --- /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 = ({ + containerCountResponse, + containerSizeError +}) => { + + const [containerPlotData, setContainerPlotData] = React.useState({ + containerCountValues: [], + containerCountMap: new Map() + }); + + function updatePlotData() { + const containerCountMap: Map = containerCountResponse.reduce( + (map: Map, current) => { + const containerSize = current.containerSize; + const oldCount = map.get(containerSize) ?? 0; + map.set(containerSize, oldCount + current.count); + return map; + }, + new Map() + ); + + 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: ${data.name}
Count: ${data.value}` + } + }, + 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 (<> + + ) +} + +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 000000000000..bb6453ed7c19 --- /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>; + 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 = ({ + volumeOptions = [], + volumeBucketMap, + fileCountResponse, + fileCountError +}) => { + + const [bucketOptions, setBucketOptions] = React.useState([]); + const [selectedBuckets, setSelectedBuckets] = React.useState([]); + const [selectedVolumes, setSelectedVolumes] = React.useState([]); + const [isBucketSelectionEnabled, setBucketSelectionEnabled] = React.useState(false); + + const [filePlotData, setFilePlotData] = React.useState({ + fileCountValues: [], + fileCountMap: new Map() + }); + + function handleVolumeChange(selectedVolumes: ValueType) { + + // 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) { + 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 = filteredData.reduce( + (map: Map, current) => { + const fileSize = current.fileSize; + const oldCount = map.get(fileSize) ?? 0; + map.set(fileSize, oldCount + current.count); + return map; + }, + new Map + ); + + // 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}
Count: ${value}` + } + }, + 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 (<> +
+ { }} + fixedColumn='' + columnLength={volumeOptions.length} + style={{ + control: (baseStyles, state) => ({ + ...baseStyles, + minWidth: 345 + }) + }} /> + { }} + fixedColumn='' + columnLength={bucketOptions.length} + isDisabled={!isBucketSelectionEnabled} + style={{ + control: (baseStyles, state) => ({ + ...baseStyles, + minWidth: 345 + }) + }} /> +
+ + ) +} + +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 07b3f9eafa15..3dfe19f9b456 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 { placeholder: string; fixedColumn: string; columnLength: number; + style?: StylesConfig; onChange: (arg0: ValueType) => void; onTagClose: (arg0: string) => void; } @@ -72,9 +74,11 @@ const MultiSelect: React.FC = ({ 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 = ({ ? child : null )} - {placeholder}: {selected.length} selected + {isDisabled + ? placeholder + : `${placeholder}: ${selected.length} selected` +} ); }; + const finalStyles = {...selectStyles, ...style ?? {}} + return ( option.value === fixedColumn} - onChange={(selected: ValueType) => { - 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) => { + 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 000000000000..818eca37f8ef --- /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) => void; + expandedRowRender: (arg0: any) => JSX.Element; + onRowExpand: (arg0: boolean, arg1: any) => void; +} + +//-----Components------ +const ContainerMismatchTable: React.FC = ({ + paginationConfig, + limit, + onRowExpand, + expandedRowRender, + handleLimitChange +}) => { + + const [loading, setLoading] = React.useState(false); + const [data, setData] = React.useState(); + const [searchTerm, setSearchTerm] = React.useState(''); + + const cancelSignal = React.useRef(); + 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 = [ + { + 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 => ( +
+ {pipeline.id.id} +
+ )); + } + return ( + + {pipelines.length} pipelines + + ) + } + }, + { + title: <> + + OM + SCM + + }> + + + + SCM: Container exists at SCM but missing at OM.
+ OM: Container exist at OM but missing at SCM. + }> + +
+ , + 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 ( + <> +
+
+ +
+ ) => setSearchTerm(e.target.value) + } + onChange={() => { }} /> +
+ + + ) +} + +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 000000000000..f0c6fc8161e4 --- /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) => void; +} + +//-----Constants------ +const COLUMNS: ColumnsType = [{ + 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 = ({ + limit, + paginationConfig, + handleLimitChange +}) => { + + const [loading, setLoading] = React.useState(false); + const [data, setData] = React.useState(); + const [searchTerm, setSearchTerm] = React.useState(''); + + const cancelSignal = React.useRef(); + 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 (<> +
+
+ +
+ ) => setSearchTerm(e.target.value) + } + onChange={() => { }} /> +
+
+ ) +} + +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 000000000000..65ada4956411 --- /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) => void; +} + +type DeletePendingKeysColumns = { + fileName: string; + keyName: string; + dataSize: number; + keyCount: number; +} + +type ExpandedDeletePendingKeys = { + omKeyInfoList: DeletePendingKey[] +} + +//------Constants------ +const COLUMNS: ColumnsType = [ + { + 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 = ({ + paginationConfig, + limit, + handleLimitChange +}) => { + const [loading, setLoading] = React.useState(false); + const [data, setData] = React.useState(); + const [searchTerm, setSearchTerm] = React.useState(''); + + const cancelSignal = React.useRef(); + 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 ( + + ) + } + + 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 ( + <> +
+
+ +
+ ) => setSearchTerm(e.target.value) + } + onChange={() => { }} /> +
+
+ + ) +} + +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 000000000000..9aaf62a63d6f --- /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) => void; + onRowExpand: (arg0: boolean, arg1: any) => void; + expandedRowRender: (arg0: any) => JSX.Element; +} + +//------Constants------ +const COLUMNS: ColumnsType = [ + { + 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[]) => ( +
+ {pipelines && pipelines.map((pipeline: any) => ( +
+ {pipeline.id.id} +
+ ))} +
+ ) + } +]; + +//-----Components------ +const DeletedContainerKeysTable: React.FC = ({ + limit, + paginationConfig, + handleLimitChange, + onRowExpand, + expandedRowRender +}) => { + + const [loading, setLoading] = React.useState(false); + const [data, setData] = React.useState(); + const [searchTerm, setSearchTerm] = React.useState(''); + + const cancelSignal = React.useRef(); + 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 ( + <> +
+
+ +
+ ) => setSearchTerm(e.target.value) + } + onChange={() => { }} /> +
+
+ + ) +} + +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 000000000000..8b54937e4732 --- /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 = [ + { + 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) =>
{size(dataSize)}
+ }, + { + 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 = ({ + loading, + data, + paginationConfig +}) => { + return ( +
+ ) +} + +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 000000000000..accb390303be --- /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 = [{ + 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 = ({ + data, + paginationConfig +}) => { + return ( +
+ ) +} + +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 000000000000..02c73c77528d --- /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) => void; +} + +//-----Components------ +const OpenKeysTable: React.FC = ({ + limit, + paginationConfig, + handleLimitChange +}) => { + const [loading, setLoading] = React.useState(false); + const [data, setData] = React.useState(); + const [searchTerm, setSearchTerm] = React.useState(''); + + const cancelSignal = React.useRef(); + 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 = [{ + 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) => ( +
+ {Object.values(replicationInfo)[0]} +
+ ) + }, + { + title: 'Replication Type', + dataIndex: 'replicationInfo', + key: 'replicationtype', + render: (replicationInfo: any) => ( +
+ { +
+ {Object.values(replicationInfo)[2]} +
+ } +
+ ) + }, { + title: <> + + FSO + Non-FSO + + }> + + + , + dataIndex: 'type', + key: 'type', + render: (type: string) =>
{type}
+ }]; + + React.useEffect(() => { + // Fetch FSO open keys by default + fetchOpenKeys(true); + + return (() => cancelSignal.current && cancelSignal.current.abort()); + }, [limit.value]); + + return ( + <> +
+
+ +
+ ) => setSearchTerm(e.target.value) + } + onChange={() => { }} /> +
+
+ + ); +} + +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 4de0d713fce6..ecfbf730a2a9 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 = ({ React.useEffect(() => { // On table mount add the actions column - console.log("Adding new column"); const actionsColumn: ColumnType = { title: 'Actions', key: 'actions', diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/breadcrumbs.constants.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/breadcrumbs.constants.tsx new file mode 100644 index 000000000000..807a68cc8d25 --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/breadcrumbs.constants.tsx @@ -0,0 +1,34 @@ +/* + * 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. + */ + +type BreadcrumbNameMap = { + [path: string]: string; +} + +export const breadcrumbNameMap: BreadcrumbNameMap = { + '/Overview': 'Overview', + '/Volumes': 'Volumes', + '/Buckets': 'Buckets', + '/Datanodes': 'Datanodes', + '/Pipelines': 'Pipelines', + '/Containers': 'Containers', + '/Insights': 'Insights', + '/DiskUsage': 'Disk Usage', + '/Heatmap': 'Heatmap', + '/Om': 'OM DB Insights' +}; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/limit.constants.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/limit.constants.tsx new file mode 100644 index 000000000000..b76c51c89603 --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/limit.constants.tsx @@ -0,0 +1,37 @@ +/* + * 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/singleSelect'; + +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 1590f36a4aaa..de79f78edb72 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 1e92780619b1..92bc0a0d3523 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 000000000000..dfc0ef31438d --- /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 000000000000..f2a2c3e3f7d1 --- /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(false); + const [state, setState] = useState({ + volumeBucketMap: new Map>(), + volumeOptions: [], + fileCountError: undefined, + containerSizeError: undefined + }); + const [plotResponse, setPlotResponse] = useState({ + fileCountResponse: [{ + volume: '', + bucket: '', + fileSize: 0, + count: 0 + }], + containerCountResponse: [{ + containerSize: 0, + count: 0 + }] + }); + + const cancelInsightSignal = React.useRef(); + + 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>, + containerCountResponse: Awaited> + ) => { + 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> = fileCountResponse.value?.data?.reduce( + (map: Map>, 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([...buckets, bucket])); + } else { + map.set(volume, new Set().add(bucket)); + } + return map; + }, + new Map>() + ); + 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 ( + <> +
+ Insights +
+
+ { + loading + ? + : <> + +
+ + {plotResponse.fileCountResponse?.length > 0 + ? + : } + + + + + + {plotResponse.containerCountResponse?.length > 0 + ? + : } + + + + + } + + + ) + +} + +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 000000000000..732af0aa00e7 --- /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(false); + const [expandedRowData, setExpandedRowData] = React.useState({}); + const [selectedLimit, setSelectedLimit] = React.useState = () => { ) } ]} - linkToUrl='/Om' /> + linkToUrl='/Om' + state={{activeTab: '3'}} /> 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 cb25cedbcec7..d0fe7f46eaa8 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(); 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 942ad16dcd1a..9f27b7cae765 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 000000000000..d608a2bc8d12 --- /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; +} + +export type ContainerPlotData = { + containerCountValues: string[]; + containerCountMap: Map; +} + +export type InsightsState = { + volumeBucketMap: Map>; + 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 + 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; +}