github.com/minio/console@v1.4.1/web-app/src/screens/Console/Dashboard/Prometheus/Widgets/CapacityItem.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 17 import React, { useEffect, useState } from "react"; 18 import styled from "styled-components"; 19 import get from "lodash/get"; 20 import { Box, breakPoints, Loader, ReportedUsageIcon } from "mds"; 21 import { Cell, Pie, PieChart } from "recharts"; 22 import { useSelector } from "react-redux"; 23 import api from "../../../../../common/api"; 24 import { IDashboardPanel } from "../types"; 25 import { widgetDetailsToPanel } from "../utils"; 26 import { ErrorResponseHandler } from "../../../../../common/types"; 27 import { 28 calculateBytes, 29 capacityColors, 30 niceBytesInt, 31 } from "../../../../../common/utils"; 32 import { setErrorSnackMessage } from "../../../../../systemSlice"; 33 import { AppState, useAppDispatch } from "../../../../../store"; 34 35 const CapacityItemMain = styled.div(({ theme }) => ({ 36 flex: 1, 37 display: "flex", 38 alignItems: "center", 39 flexFlow: "row", 40 "& .usableLabel": { 41 color: get(theme, "mutedText", "#87888d"), 42 fontSize: "10px", 43 display: "flex", 44 flexFlow: "column", 45 alignItems: "center", 46 textAlign: "center", 47 }, 48 "& .usedLabel": { 49 color: get(theme, "mutedText", "#87888d"), 50 fontWeight: "bold", 51 fontSize: "14px", 52 }, 53 "& .totalUsed": { 54 display: "flex", 55 "& .value": { 56 fontSize: "50px", 57 fontFamily: "Inter", 58 fontWeight: 600, 59 alignSelf: "flex-end", 60 lineHeight: 1, 61 }, 62 "& .unit": { 63 color: get(theme, "mutedText", "#87888d"), 64 fontWeight: "bold", 65 fontSize: "14px", 66 marginLeft: "12px", 67 alignSelf: "flex-end", 68 }, 69 }, 70 "& .ofUsed": { 71 marginTop: "5px", 72 "& .value": { 73 color: get(theme, "mutedText", "#87888d"), 74 fontWeight: "bold", 75 fontSize: "14px", 76 textAlign: "right", 77 }, 78 }, 79 [`@media (max-width: ${breakPoints.sm}px)`]: { 80 flexFlow: "column", 81 }, 82 })); 83 84 const CapacityItem = ({ 85 value, 86 timeStart, 87 timeEnd, 88 apiPrefix, 89 }: { 90 value: IDashboardPanel; 91 timeStart: any; 92 timeEnd: any; 93 apiPrefix: string; 94 }) => { 95 const dispatch = useAppDispatch(); 96 const [loading, setLoading] = useState<boolean>(false); 97 98 const [totalUsableFree, setTotalUsableFree] = useState<number>(0); 99 const [totalUsableFreeRatio, setTotalUsableFreeRatio] = useState<number>(0); 100 const [totalUsed, setTotalUsed] = useState<number>(0); 101 const [totalUsable, setTotalUsable] = useState<number>(0); 102 const widgetVersion = useSelector( 103 (state: AppState) => state.dashboard.widgetLoadVersion, 104 ); 105 106 useEffect(() => { 107 setLoading(true); 108 }, [widgetVersion]); 109 110 useEffect(() => { 111 if (loading) { 112 let stepCalc = 0; 113 if (timeStart !== null && timeEnd !== null) { 114 const secondsInPeriod = 115 timeEnd.toUnixInteger() - timeStart.toUnixInteger(); 116 const periods = Math.floor(secondsInPeriod / 60); 117 118 stepCalc = periods < 1 ? 15 : periods; 119 } 120 121 api 122 .invoke( 123 "GET", 124 `/api/v1/${apiPrefix}/info/widgets/${value.id}/?step=${stepCalc}&${ 125 timeStart !== null ? `&start=${timeStart.toUnixInteger()}` : "" 126 }${timeStart !== null && timeEnd !== null ? "&" : ""}${ 127 timeEnd !== null ? `end=${timeEnd.toUnixInteger()}` : "" 128 }`, 129 ) 130 .then((res: any) => { 131 const widgetsWithValue = widgetDetailsToPanel(res, value); 132 133 let tUsable = 0; 134 let tUsed = 0; 135 let tFree = 0; 136 137 widgetsWithValue.data.forEach((eachArray: any[]) => { 138 eachArray.forEach((itemSum) => { 139 switch (itemSum.legend) { 140 case "Total Usable": 141 tUsable += itemSum.value; 142 break; 143 case "Used Space": 144 tUsed += itemSum.value; 145 break; 146 case "Usable Free": 147 tFree += itemSum.value; 148 break; 149 } 150 }); 151 }); 152 153 const freeRatio = Math.round((tFree / tUsable) * 100); 154 155 setTotalUsableFree(tFree); 156 setTotalUsableFreeRatio(freeRatio); 157 setTotalUsed(tUsed); 158 setTotalUsable(tUsable); 159 160 setLoading(false); 161 }) 162 .catch((err: ErrorResponseHandler) => { 163 dispatch(setErrorSnackMessage(err)); 164 setLoading(false); 165 }); 166 } 167 }, [loading, value, timeEnd, timeStart, dispatch, apiPrefix]); 168 169 const usedConvert = calculateBytes(totalUsed, true, false); 170 171 const plotValues = [ 172 { 173 value: totalUsableFree, 174 color: "#D6D6D6", 175 label: "Usable Available Space", 176 }, 177 { 178 value: totalUsed, 179 color: capacityColors(totalUsed, totalUsable), 180 label: "Used Space", 181 }, 182 ]; 183 return ( 184 <CapacityItemMain> 185 <Box 186 sx={{ 187 fontSize: "16px", 188 fontWeight: 600, 189 [`@media (max-width: ${breakPoints.sm}px)`]: { 190 alignSelf: "flex-start", 191 }, 192 }} 193 > 194 Capacity 195 </Box> 196 <Box 197 sx={{ 198 position: "relative", 199 width: 110, 200 height: 110, 201 marginLeft: "auto", 202 [`@media (max-width: ${breakPoints.sm}px)`]: { 203 marginLeft: "", 204 }, 205 }} 206 > 207 <Box 208 sx={{ 209 position: "absolute", 210 display: "flex", 211 flexFlow: "column", 212 alignItems: "center", 213 top: "50%", 214 left: "50%", 215 transform: "translate(-50%, -50%)", 216 fontWeight: "bold", 217 fontSize: 12, 218 }} 219 > 220 {`${totalUsableFreeRatio}%`} 221 <br /> 222 <Box className={"usableLabel"}>Free</Box> 223 </Box> 224 <PieChart width={110} height={110}> 225 <Pie 226 data={plotValues} 227 cx={"50%"} 228 cy={"50%"} 229 dataKey="value" 230 outerRadius={50} 231 innerRadius={40} 232 startAngle={-70} 233 endAngle={360} 234 animationDuration={1} 235 > 236 {plotValues.map((entry, index) => ( 237 <Cell key={`cellCapacity-${index}`} fill={entry.color} /> 238 ))} 239 </Pie> 240 </PieChart> 241 </Box> 242 <Box 243 sx={{ 244 display: "flex", 245 alignItems: "center", 246 marginLeft: "auto", 247 [`@media (max-width: ${breakPoints.sm}px)`]: { 248 marginLeft: "", 249 }, 250 }} 251 > 252 <Box> 253 <Box className={"usedLabel"}>Used:</Box> 254 <Box className={"totalUsed"}> 255 <div className="value">{usedConvert.total}</div> 256 <div className="unit">{usedConvert.unit}</div> 257 </Box> 258 <Box className={"ofUsed"}> 259 <div className="value">Of: {niceBytesInt(totalUsable)}</div> 260 </Box> 261 </Box> 262 263 <Box 264 sx={{ 265 marginLeft: "15px", 266 height: "100%", 267 display: "flex", 268 alignItems: "flex-start", 269 }} 270 > 271 <Box> 272 {loading ? ( 273 <Loader style={{ width: "26px", height: "26px" }} /> 274 ) : ( 275 <ReportedUsageIcon /> 276 )} 277 </Box> 278 </Box> 279 </Box> 280 </CapacityItemMain> 281 ); 282 }; 283 284 export default CapacityItem;