From 49290dc2605ebfb5a28f2d0829e07a43ee6ef48a Mon Sep 17 00:00:00 2001 From: Abhishek Pal Date: Fri, 9 Aug 2024 12:35:39 +0530 Subject: [PATCH 1/8] Initial buckets page commit --- .../src/v2/pages/buckets/buckets.tsx | 18 +++++ .../ozone-recon-web/src/v2/routes-v2.tsx | 11 ++- .../ozone-recon-web/src/v2/types/acl.types.ts | 47 +++++++++++ .../src/v2/types/bucket.types.ts | 80 +++++++++++++++++++ 4 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx create mode 100644 hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/acl.types.ts create mode 100644 hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/bucket.types.ts 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 new file mode 100644 index 00000000000..e5cdfe96e7b --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx @@ -0,0 +1,18 @@ +/* + * 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. + */ + 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 4cdd700d502..981a1306c4b 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 @@ -16,11 +16,18 @@ * limitations under the License. */ -import Overview from '@/v2/pages/overview/overview'; +import { lazy } from 'react'; -export const routesV2: IRoute[] = [ +const Overview = lazy(() => import('@/v2/pages/overview/overview')); +const Buckets = lazy(() => import('@/v2/pages/buckets/buckets')) + +export const routesV2 = [ { path: '/Overview', component: Overview + }, + { + path: '/Buckets', + component: Buckets } ]; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/acl.types.ts b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/acl.types.ts new file mode 100644 index 00000000000..33cd047d18e --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/acl.types.ts @@ -0,0 +1,47 @@ +/* + * 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. + */ + +export const ACLIdentityTypeList = [ + 'USER', + 'GROUP', + 'WORLD', + 'ANONYMOUS', + 'CLIENT_IP' +] as const; +export type ACLIdentity = typeof ACLIdentityTypeList[number]; + +export const ACLRightList = [ + 'READ', + 'WRITE', + 'CREATE', + 'LIST', + 'DELETE', + 'READ_ACL', + 'WRITE_ACL', + 'ALL', + 'NONE' +] as const; +export type ACLRight = typeof ACLRightList[number]; + + +export type Acl = { + type: string; + name: string; + scope: string; + aclList: string[]; +} diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/bucket.types.ts b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/bucket.types.ts new file mode 100644 index 00000000000..11a4d007d8b --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/bucket.types.ts @@ -0,0 +1,80 @@ +/* + * 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 { Acl } from "@/v2/types/acl.types"; + +// Corresponds to OzoneManagerProtocolProtos.StorageTypeProto +export const BucketStorageTypeList = [ + 'RAM_DISK', + 'SSD', + 'DISK', + 'ARCHIVE' +] as const; +export type BucketStorage = typeof BucketStorageTypeList[number]; + +// Corresponds to OzoneManagerProtocolProtos.BucketLayoutProto +export const BucketLayoutTypeList = [ + 'FILE_SYSTEM_OPTIMIZED', + 'OBJECT_STORE', + 'LEGACY' +] as const; +export type BucketLayout = typeof BucketLayoutTypeList[number]; + + +export type BucketResponse = { + volumeName: string; + bucketName: string; + isVersionEnabled: boolean; + storageType: BucketStorage; + creationTime: number; + modificationTime: number; + sourceVolume?: string; + sourceBucket?: string; + usedBytes: number; + usedNamespace: number; + quotaInBytes: number; + quotaInNamespace: number; + owner: string; + acls?: Acl[]; +} + +export type Bucket = { + bucketLayout: BucketLayout; +} & BucketResponse; + +export type BucketsResponse = { + totalCount: number; + buckets: BucketResponse[]; +} + +export type BucketsState = { + loading: boolean; + totalCount: number; + lastUpdated: number; + selectedColumns: Option; + columnOptions: IOption[]; + volumeBucketMap: Map>; + selectedVolumes: IOption[]; + selectedBuckets: Bucket[]; + volumeOptions: Bucket[]; + currentRow?: Bucket; + showPanel: boolean; + selectedLimit: Bucket; +} + + From a9a47c63e8cddf7cef8e91d22a70e8ac4a5e61ce Mon Sep 17 00:00:00 2001 From: Abhishek Pal Date: Tue, 20 Aug 2024 18:58:28 +0530 Subject: [PATCH 2/8] Added bucket table --- .../src/v2/components/select/multiSelect.tsx | 1 + .../src/v2/pages/buckets/buckets.less | 41 ++ .../src/v2/pages/buckets/buckets.tsx | 528 ++++++++++++++++++ .../src/v2/pages/volumes/volumes.tsx | 3 +- .../ozone-recon-web/src/v2/routes-v2.tsx | 5 + .../src/v2/types/bucket.types.ts | 21 +- 6 files changed, 595 insertions(+), 4 deletions(-) create mode 100644 hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.less 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 7a6b494aaeb..cdc6e1bce0d 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 @@ -93,6 +93,7 @@ const MultiSelect: React.FC = ({ }} placeholder={placeholder} value={selected} + isOptionDisabled={(option) => option.value === fixedColumn} onChange={(selected: ValueType) => { if (selected?.length === options.length) return onChange!(options); return onChange!(selected); diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.less b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.less new file mode 100644 index 00000000000..8f4c8ffaf9f --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.less @@ -0,0 +1,41 @@ +/* +* 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; + } + } + + .tag-block { + display: flex; + column-gap: 8px; + padding: 0px 8px 16px 8px; + } +} 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 e5cdfe96e7b..0eb0e4c3cf8 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 @@ -16,3 +16,531 @@ * limitations under the License. */ +import React, { useEffect, useState } from 'react'; +import moment from 'moment'; +import { Table, Tag } from 'antd'; +import { + ColumnProps, + ColumnsType, + TablePaginationConfig +} from 'antd/es/table'; +import { + CheckCircleOutlined, + CloseCircleOutlined, + CloudServerOutlined, + FileUnknownOutlined, + HddOutlined, + LaptopOutlined, + SaveOutlined +} from '@ant-design/icons'; +import { ValueType } from 'react-select'; +import { useLocation } from 'react-router-dom'; + +import QuotaBar from '@/components/quotaBar/quotaBar'; +import AutoReloadPanel from '@/components/autoReloadPanel/autoReloadPanel'; +import AclPanel from '@/v2/components/aclDrawer/aclDrawer'; +import Search from '@/v2/components/search/search'; +import MultiSelect from '@/v2/components/select/multiSelect'; +import SingleSelect, { Option } from '@/v2/components/select/singleSelect'; + +import { AutoReloadHelper } from '@/utils/autoReloadHelper'; +import { AxiosGetHelper } from "@/utils/axiosRequestHelper"; +import { nullAwareLocaleCompare, showDataFetchError } from '@/utils/common'; +import { useDebounce } from '@/v2/hooks/debounce.hook'; + +import { + Bucket, + BucketLayout, + BucketLayoutTypeList, + BucketResponse, + BucketsState, + BucketStorage, + BucketStorageTypeList +} from '@/v2/types/bucket.types'; + +import './buckets.less'; + + +const LIMIT_OPTIONS: Option[] = [ + { + label: '1000', + value: '1000' + }, + { + label: '5000', + value: '5000' + }, + { + label: '10000', + value: '10000' + }, + { + label: '20000', + value: '20000' + } +] + +const renderIsVersionEnabled = (isVersionEnabled: boolean) => { + return isVersionEnabled + ? + : +}; + +const renderStorageType = (bucketStorage: BucketStorage) => { + const bucketStorageIconMap: Record = { + RAM_DISK: , + SSD: , + DISK: , + ARCHIVE: + }; + const icon = bucketStorage in bucketStorageIconMap + ? bucketStorageIconMap[bucketStorage] + : ; + return {icon} {bucketStorage}; +}; + +const renderBucketLayout = (bucketLayout: BucketLayout) => { + const bucketLayoutColorMap = { + FILE_SYSTEM_OPTIMIZED: 'green', + OBJECT_STORE: 'blue', + LEGACY: 'gray' + }; + const color = bucketLayout in bucketLayoutColorMap ? + bucketLayoutColorMap[bucketLayout] : ''; + return {bucketLayout}; +}; + +const SearchableColumnOpts = [{ + label: 'Bucket', + value: 'name' +}, { + label: 'Volume', + value: 'volumeName' +}] + +const COLUMNS: ColumnsType = [ + { + title: 'Bucket', + dataIndex: 'name', + key: 'name', + sorter: (a: Bucket, b: Bucket) => a.name.localeCompare(b.name), + defaultSortOrder: 'ascend' as const + }, + { + title: 'Volume', + dataIndex: 'volumeName', + key: 'volumeName', + sorter: (a: Bucket, b: Bucket) => a.volumeName.localeCompare(b.volumeName), + defaultSortOrder: 'ascend' as const + }, + { + title: 'Owner', + dataIndex: 'owner', + key: 'owner', + sorter: (a: Bucket, b: Bucket) => nullAwareLocaleCompare(a.owner, b.owner) + }, + { + title: 'Versioning', + dataIndex: 'versioning', + key: 'isVersionEnabled', + render: (isVersionEnabled: boolean) => renderIsVersionEnabled(isVersionEnabled) + }, + { + title: 'Storage Type', + dataIndex: 'storageType', + key: 'storageType', + filterMultiple: true, + filters: BucketStorageTypeList.map(state => ({ text: state, value: state })), + onFilter: (value, record: Bucket) => record.storageType === value, + sorter: (a: Bucket, b: Bucket) => a.storageType.localeCompare(b.storageType), + render: (storageType: BucketStorage) => renderStorageType(storageType) + }, + { + title: 'Bucket Layout', + dataIndex: 'bucketLayout', + key: 'bucketLayout', + filterMultiple: true, + filters: BucketLayoutTypeList.map(state => ({ text: state, value: state })), + onFilter: (value, record: Bucket) => record.bucketLayout === value, + sorter: (a: Bucket, b: Bucket) => a.bucketLayout.localeCompare(b.bucketLayout), + render: (bucketLayout: BucketLayout) => renderBucketLayout(bucketLayout) + }, + { + title: 'Creation Time', + dataIndex: 'creationTime', + key: 'creationTime', + sorter: (a: Bucket, b: Bucket) => a.creationTime - b.creationTime, + render: (creationTime: number) => { + return creationTime > 0 ? moment(creationTime).format('ll LTS') : 'NA'; + } + }, + { + title: 'Modification Time', + dataIndex: 'modificationTime', + key: 'modificationTime', + sorter: (a: Bucket, b: Bucket) => a.modificationTime - b.modificationTime, + render: (modificationTime: number) => { + return modificationTime > 0 ? moment(modificationTime).format('ll LTS') : 'NA'; + } + }, + { + title: 'Storage Capacity', + key: 'quotaCapacityBytes', + sorter: (a: Bucket, b: Bucket) => a.usedBytes - b.usedBytes, + render: (text: string, record: Bucket) => ( + + ) + }, + { + title: 'Namespace Capacity', + key: 'namespaceCapacity', + sorter: (a: Bucket, b: Bucket) => a.usedNamespace - b.usedNamespace, + render: (text: string, record: Bucket) => ( + + ) + }, + { + title: 'Source Volume', + dataIndex: 'sourceVolume', + key: 'sourceVolume', + render: (sourceVolume: string) => { + return sourceVolume ? sourceVolume : 'NA'; + } + }, + { + title: 'Source Bucket', + dataIndex: 'sourceBucket', + key: 'sourceBucket', + render: (sourceBucket: string) => { + return sourceBucket ? sourceBucket : 'NA'; + } + } +]; + +const defaultColumns = COLUMNS.map(column => ({ + label: column.title as string, + value: column.key as string +})); + +const Buckets: React.FC<{}> = () => { + + let cancelSignal: AbortController; + + const [state, setState] = useState({ + totalCount: 0, + lastUpdated: 0, + columnOptions: defaultColumns, + volumeBucketMap: new Map>(), + bucketsUnderVolume: [], + volumeOptions: [], + currentRow: {} + }); + const [loading, setLoading] = useState(false); + const [selectedColumns, setSelectedColumns] = useState(defaultColumns); + const [selectedLimit, setSelectedLimit] = useState