github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/ListBuckets/BucketListItem.tsx (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 import React, { Fragment, useState } from "react"; 17 import get from "lodash/get"; 18 import styled from "styled-components"; 19 import { Link, useNavigate } from "react-router-dom"; 20 import { 21 Box, 22 breakPoints, 23 BucketsIcon, 24 Checkbox, 25 Grid, 26 HelpTip, 27 ReportedUsageIcon, 28 TotalObjectsIcon, 29 } from "mds"; 30 import { 31 calculateBytes, 32 niceBytes, 33 prettyNumber, 34 } from "../../../../common/utils"; 35 import { 36 IAM_PERMISSIONS, 37 IAM_ROLES, 38 } from "../../../../common/SecureComponent/permissions"; 39 import { hasPermission } from "../../../../common/SecureComponent"; 40 import { Bucket } from "../../../../api/consoleApi"; 41 import { usageClarifyingContent } from "screens/Console/Dashboard/BasicDashboard/ReportedUsage"; 42 43 const BucketItemMain = styled.div(({ theme }) => ({ 44 border: `${get(theme, "borderColor", "#eaeaea")} 1px solid`, 45 borderRadius: 3, 46 padding: 15, 47 cursor: "pointer", 48 "&.disabled": { 49 backgroundColor: get(theme, "signalColors.danger", "red"), 50 }, 51 "&:hover": { 52 backgroundColor: get(theme, "boxBackground", "#FBFAFA"), 53 }, 54 "& .bucketTitle": { 55 display: "flex", 56 alignItems: "center", 57 justifyContent: "flex-start", 58 gap: 10, 59 "& h1": { 60 padding: 0, 61 margin: 0, 62 marginBottom: 5, 63 fontSize: 22, 64 color: get(theme, "screenTitle.iconColor", "#07193E"), 65 [`@media (max-width: ${breakPoints.md}px)`]: { 66 marginBottom: 0, 67 }, 68 }, 69 }, 70 "& .bucketDetails": { 71 display: "flex", 72 gap: 40, 73 "& span": { 74 fontSize: 14, 75 }, 76 [`@media (max-width: ${breakPoints.md}px)`]: { 77 flexFlow: "column-reverse", 78 gap: 5, 79 }, 80 }, 81 "& .bucketMetrics": { 82 display: "flex", 83 alignItems: "center", 84 marginTop: 20, 85 gap: 25, 86 borderTop: `${get(theme, "borderColor", "#E2E2E2")} 1px solid`, 87 paddingTop: 20, 88 "& svg.bucketIcon": { 89 color: get(theme, "screenTitle.iconColor", "#07193E"), 90 fill: get(theme, "screenTitle.iconColor", "#07193E"), 91 }, 92 "& .metric": { 93 "& .min-icon": { 94 color: get(theme, "fontColor", "#000"), 95 width: 13, 96 marginRight: 5, 97 }, 98 }, 99 "& .metricLabel": { 100 fontSize: 14, 101 fontWeight: "bold", 102 color: get(theme, "fontColor", "#000"), 103 }, 104 "& .metricText": { 105 fontSize: 24, 106 fontWeight: "bold", 107 }, 108 "& .unit": { 109 fontSize: 12, 110 fontWeight: "normal", 111 }, 112 [`@media (max-width: ${breakPoints.md}px)`]: { 113 marginTop: 8, 114 paddingTop: 8, 115 }, 116 }, 117 })); 118 119 interface IBucketListItem { 120 bucket: Bucket; 121 onSelect: (e: React.ChangeEvent<HTMLInputElement>) => void; 122 selected: boolean; 123 bulkSelect: boolean; 124 } 125 126 const BucketListItem = ({ 127 bucket, 128 onSelect, 129 selected, 130 bulkSelect, 131 }: IBucketListItem) => { 132 const navigate = useNavigate(); 133 134 const [clickOverride, setClickOverride] = useState<boolean>(false); 135 136 const usage = niceBytes(`${bucket.size}` || "0"); 137 const usageScalar = usage.split(" ")[0]; 138 const usageUnit = usage.split(" ")[1]; 139 140 const quota = get(bucket, "details.quota.quota", "0"); 141 const quotaForString = calculateBytes(quota, true, false); 142 143 const manageAllowed = 144 hasPermission(bucket.name, IAM_PERMISSIONS[IAM_ROLES.BUCKET_ADMIN]) && 145 false; 146 147 const accessToStr = (bucket: Bucket): string => { 148 if (bucket.rw_access?.read && !bucket.rw_access?.write) { 149 return "R"; 150 } else if (!bucket.rw_access?.read && bucket.rw_access?.write) { 151 return "W"; 152 } else if (bucket.rw_access?.read && bucket.rw_access?.write) { 153 return "R/W"; 154 } 155 return ""; 156 }; 157 const onCheckboxClick = (e: React.ChangeEvent<HTMLInputElement>) => { 158 onSelect(e); 159 }; 160 161 return ( 162 <BucketItemMain 163 onClick={() => { 164 !clickOverride && navigate(`/buckets/${bucket.name}/admin`); 165 }} 166 id={`manageBucket-${bucket.name}`} 167 className={`bucket-item ${manageAllowed ? "disabled" : ""}`} 168 > 169 <Box className={"bucketTitle"}> 170 {bulkSelect && ( 171 <Box 172 onClick={(e) => { 173 e.stopPropagation(); 174 }} 175 > 176 <Checkbox 177 checked={selected} 178 id={`select-${bucket.name}`} 179 label={""} 180 name={`select-${bucket.name}`} 181 onChange={onCheckboxClick} 182 value={bucket.name} 183 /> 184 </Box> 185 )} 186 <h1> 187 {bucket.name} {manageAllowed} 188 </h1> 189 </Box> 190 <Box className={"bucketDetails"}> 191 <span id={`created-${bucket.name}`}> 192 <strong>Created:</strong>{" "} 193 {bucket.creation_date 194 ? new Date(bucket.creation_date).toString() 195 : "n/a"} 196 </span> 197 <span id={`access-${bucket.name}`}> 198 <strong>Access:</strong> {accessToStr(bucket)} 199 </span> 200 </Box> 201 <Box className={"bucketMetrics"}> 202 <Link to={`/buckets/${bucket.name}/admin`}> 203 <BucketsIcon 204 className={"bucketIcon"} 205 style={{ 206 height: 48, 207 width: 48, 208 }} 209 /> 210 </Link> 211 212 <Grid 213 item 214 className={"metric"} 215 onMouseEnter={() => 216 bucket.details?.versioning && setClickOverride(true) 217 } 218 onMouseLeave={() => 219 bucket.details?.versioning && setClickOverride(false) 220 } 221 > 222 {bucket.details?.versioning && ( 223 <HelpTip content={usageClarifyingContent} placement="top"> 224 <ReportedUsageIcon />{" "} 225 </HelpTip> 226 )} 227 {!bucket.details?.versioning && <ReportedUsageIcon />} 228 <span className={"metricLabel"}>Usage</span> 229 <div className={"metricText"}> 230 {usageScalar} 231 <span className={"unit"}>{usageUnit}</span> 232 {quota !== "0" && ( 233 <Fragment> 234 {" "} 235 / {quotaForString.total} 236 <span className={"unit"}>{quotaForString.unit}</span> 237 </Fragment> 238 )} 239 </div> 240 </Grid> 241 242 <Grid item className={"metric"}> 243 <TotalObjectsIcon /> 244 <span className={"metricLabel"}>Objects</span> 245 <div className={"metricText"}> 246 {bucket.objects ? prettyNumber(bucket.objects) : 0} 247 </div> 248 </Grid> 249 </Box> 250 </BucketItemMain> 251 ); 252 }; 253 254 export default BucketListItem;