1+ /*
2+ * Licensed to the Apache Software Foundation (ASF) under one
3+ * or more contributor license agreements. See the NOTICE file
4+ * distributed with this work for additional information
5+ * regarding copyright ownership. The ASF licenses this file
6+ * to you under the Apache License, Version 2.0 (the
7+ * "License"); you may not use this file except in compliance
8+ * with the License. You may obtain a copy of the License at
9+ *
10+ * http://www.apache.org/licenses/LICENSE-2.0
11+ *
12+ * Unless required by applicable law or agreed to in writing, software
13+ * distributed under the License is distributed on an "AS IS" BASIS,
14+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+ * See the License for the specific language governing permissions and
16+ * limitations under the License.
17+ */
18+
19+ import React from 'react' ;
20+
21+ import moment from 'moment' ;
22+ import Table , {
23+ ColumnProps ,
24+ ColumnsType ,
25+ TablePaginationConfig
26+ } from 'antd/es/table' ;
27+ import Tag from 'antd/es/tag' ;
28+ import {
29+ CheckCircleOutlined ,
30+ CloseCircleOutlined ,
31+ CloudServerOutlined ,
32+ FileUnknownOutlined ,
33+ HddOutlined ,
34+ LaptopOutlined ,
35+ SaveOutlined
36+ } from '@ant-design/icons' ;
37+
38+ import QuotaBar from '@/components/quotaBar/quotaBar' ;
39+ import { nullAwareLocaleCompare } from '@/utils/common' ;
40+ import {
41+ Bucket ,
42+ BucketLayout ,
43+ BucketLayoutTypeList ,
44+ BucketsTableProps ,
45+ BucketStorage ,
46+ BucketStorageTypeList
47+ } from '@/v2/types/bucket.types' ;
48+
49+ function renderIsVersionEnabled ( isVersionEnabled : boolean ) {
50+ return isVersionEnabled
51+ ? < CheckCircleOutlined
52+ style = { { color : '#1da57a' } }
53+ className = 'icon-success' />
54+ : < CloseCircleOutlined className = 'icon-neutral' />
55+ } ;
56+
57+ function renderStorageType ( bucketStorage : BucketStorage ) {
58+ const bucketStorageIconMap : Record < BucketStorage , React . ReactElement > = {
59+ RAM_DISK : < LaptopOutlined /> ,
60+ SSD : < SaveOutlined /> ,
61+ DISK : < HddOutlined /> ,
62+ ARCHIVE : < CloudServerOutlined />
63+ } ;
64+ const icon = bucketStorage in bucketStorageIconMap
65+ ? bucketStorageIconMap [ bucketStorage ]
66+ : < FileUnknownOutlined /> ;
67+ return < span > { icon } { bucketStorage } </ span > ;
68+ } ;
69+
70+ function renderBucketLayout ( bucketLayout : BucketLayout ) {
71+ const bucketLayoutColorMap = {
72+ FILE_SYSTEM_OPTIMIZED : 'green' ,
73+ OBJECT_STORE : 'orange' ,
74+ LEGACY : 'blue'
75+ } ;
76+ const color = bucketLayout in bucketLayoutColorMap ?
77+ bucketLayoutColorMap [ bucketLayout ] : '' ;
78+ return < Tag color = { color } > { bucketLayout } </ Tag > ;
79+ } ;
80+
81+ export const COLUMNS : ColumnsType < Bucket > = [
82+ {
83+ title : 'Bucket' ,
84+ dataIndex : 'name' ,
85+ key : 'name' ,
86+ sorter : ( a : Bucket , b : Bucket ) => a . name . localeCompare ( b . name ) ,
87+ defaultSortOrder : 'ascend' as const
88+ } ,
89+ {
90+ title : 'Volume' ,
91+ dataIndex : 'volumeName' ,
92+ key : 'volumeName' ,
93+ sorter : ( a : Bucket , b : Bucket ) => a . volumeName . localeCompare ( b . volumeName ) ,
94+ defaultSortOrder : 'ascend' as const
95+ } ,
96+ {
97+ title : 'Owner' ,
98+ dataIndex : 'owner' ,
99+ key : 'owner' ,
100+ sorter : ( a : Bucket , b : Bucket ) => nullAwareLocaleCompare ( a . owner , b . owner )
101+ } ,
102+ {
103+ title : 'Versioning' ,
104+ dataIndex : 'versioning' ,
105+ key : 'isVersionEnabled' ,
106+ render : ( isVersionEnabled : boolean ) => renderIsVersionEnabled ( isVersionEnabled )
107+ } ,
108+ {
109+ title : 'Storage Type' ,
110+ dataIndex : 'storageType' ,
111+ key : 'storageType' ,
112+ filterMultiple : true ,
113+ filters : BucketStorageTypeList . map ( state => ( { text : state , value : state } ) ) ,
114+ onFilter : ( value , record : Bucket ) => record . storageType === value ,
115+ sorter : ( a : Bucket , b : Bucket ) => a . storageType . localeCompare ( b . storageType ) ,
116+ render : ( storageType : BucketStorage ) => renderStorageType ( storageType )
117+ } ,
118+ {
119+ title : 'Bucket Layout' ,
120+ dataIndex : 'bucketLayout' ,
121+ key : 'bucketLayout' ,
122+ filterMultiple : true ,
123+ filters : BucketLayoutTypeList . map ( state => ( { text : state , value : state } ) ) ,
124+ onFilter : ( value , record : Bucket ) => record . bucketLayout === value ,
125+ sorter : ( a : Bucket , b : Bucket ) => a . bucketLayout . localeCompare ( b . bucketLayout ) ,
126+ render : ( bucketLayout : BucketLayout ) => renderBucketLayout ( bucketLayout )
127+ } ,
128+ {
129+ title : 'Creation Time' ,
130+ dataIndex : 'creationTime' ,
131+ key : 'creationTime' ,
132+ sorter : ( a : Bucket , b : Bucket ) => a . creationTime - b . creationTime ,
133+ render : ( creationTime : number ) => {
134+ return creationTime > 0 ? moment ( creationTime ) . format ( 'll LTS' ) : 'NA' ;
135+ }
136+ } ,
137+ {
138+ title : 'Modification Time' ,
139+ dataIndex : 'modificationTime' ,
140+ key : 'modificationTime' ,
141+ sorter : ( a : Bucket , b : Bucket ) => a . modificationTime - b . modificationTime ,
142+ render : ( modificationTime : number ) => {
143+ return modificationTime > 0 ? moment ( modificationTime ) . format ( 'll LTS' ) : 'NA' ;
144+ }
145+ } ,
146+ {
147+ title : 'Storage Capacity' ,
148+ key : 'quotaCapacityBytes' ,
149+ sorter : ( a : Bucket , b : Bucket ) => a . usedBytes - b . usedBytes ,
150+ render : ( text : string , record : Bucket ) => (
151+ < QuotaBar
152+ quota = { record . quotaInBytes }
153+ used = { record . usedBytes }
154+ quotaType = 'size'
155+ />
156+ )
157+ } ,
158+ {
159+ title : 'Namespace Capacity' ,
160+ key : 'namespaceCapacity' ,
161+ sorter : ( a : Bucket , b : Bucket ) => a . usedNamespace - b . usedNamespace ,
162+ render : ( text : string , record : Bucket ) => (
163+ < QuotaBar
164+ quota = { record . quotaInNamespace }
165+ used = { record . usedNamespace }
166+ quotaType = 'namespace'
167+ />
168+ )
169+ } ,
170+ {
171+ title : 'Source Volume' ,
172+ dataIndex : 'sourceVolume' ,
173+ key : 'sourceVolume' ,
174+ render : ( sourceVolume : string ) => {
175+ return sourceVolume ? sourceVolume : 'NA' ;
176+ }
177+ } ,
178+ {
179+ title : 'Source Bucket' ,
180+ dataIndex : 'sourceBucket' ,
181+ key : 'sourceBucket' ,
182+ render : ( sourceBucket : string ) => {
183+ return sourceBucket ? sourceBucket : 'NA' ;
184+ }
185+ }
186+ ] ;
187+
188+ const BucketsTable : React . FC < BucketsTableProps > = ( {
189+ loading = false ,
190+ data,
191+ handleAclClick,
192+ selectedColumns,
193+ searchColumn = 'name' ,
194+ searchTerm = ''
195+ } ) => {
196+
197+ React . useEffect ( ( ) => {
198+ const aclColumn : ColumnProps < Bucket > = {
199+ title : 'ACLs' ,
200+ dataIndex : 'acls' ,
201+ key : 'acls' ,
202+ render : ( _ : any , record : Bucket ) => {
203+ return (
204+ < a
205+ key = 'acl'
206+ onClick = { ( ) => {
207+ handleAclClick ( record ) ;
208+ } }
209+ >
210+ Show ACL
211+ </ a >
212+ ) ;
213+ }
214+ } ;
215+
216+ if ( COLUMNS . length > 0 && COLUMNS [ COLUMNS . length - 1 ] . key !== 'acls' ) {
217+ // Push the ACL column for initial load
218+ COLUMNS . push ( aclColumn ) ;
219+ selectedColumns . push ( {
220+ label : aclColumn . title as string ,
221+ value : aclColumn . key as string
222+ } ) ;
223+ } else {
224+ // Replace old ACL column with new ACL column with correct reference
225+ // e.g. After page is reloaded / redirect from other page
226+ COLUMNS [ COLUMNS . length - 1 ] = aclColumn ;
227+ selectedColumns [ selectedColumns . length - 1 ] = {
228+ label : aclColumn . title as string ,
229+ value : aclColumn . key as string
230+ }
231+ }
232+ } , [ ] ) ;
233+
234+ function filterSelectedColumns ( ) {
235+ const columnKeys = selectedColumns . map ( ( column ) => column . value ) ;
236+ return COLUMNS . filter (
237+ ( column ) => columnKeys . indexOf ( column . key as string ) >= 0
238+ )
239+ }
240+
241+ function getFilteredData ( data : Bucket [ ] ) {
242+ return data . filter (
243+ ( bucket : Bucket ) => bucket [ searchColumn ] . includes ( searchTerm )
244+ ) ;
245+ }
246+
247+ const paginationConfig : TablePaginationConfig = {
248+ showTotal : ( total : number , range ) => `${ range [ 0 ] } -${ range [ 1 ] } of ${ total } buckets` ,
249+ showSizeChanger : true
250+ } ;
251+
252+ return (
253+ < div >
254+ < Table
255+ dataSource = { getFilteredData ( data ) }
256+ columns = { filterSelectedColumns ( ) }
257+ loading = { loading }
258+ rowKey = 'volume'
259+ pagination = { paginationConfig }
260+ scroll = { { x : 'max-content' , scrollToFirstRowOnChange : true } }
261+ locale = { { filterTitle : '' } }
262+ />
263+ </ div >
264+ )
265+ }
266+
267+ export default BucketsTable ;
0 commit comments