Details.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import React, { memo, useEffect, useState } from 'react';
  2. import { forEach, get, reverse, round, sortBy } from 'lodash';
  3. import CircularProgress from '@material-ui/core/CircularProgress';
  4. import ClusterIcon from '@material-ui/icons/GroupWork';
  5. import NodeIcon from '@material-ui/icons/Memory';
  6. import List from '@material-ui/core/List';
  7. import ListItem from '@material-ui/core/ListItem';
  8. import ListItemIcon from '@material-ui/core/ListItemIcon';
  9. import ListItemText from '@material-ui/core/ListItemText';
  10. import Table from '@material-ui/core/Table';
  11. import TableBody from '@material-ui/core/TableBody';
  12. import TableCell from '@material-ui/core/TableCell';
  13. import TableContainer from '@material-ui/core/TableContainer';
  14. import TableHead from '@material-ui/core/TableHead';
  15. import TableRow from '@material-ui/core/TableRow';
  16. import Warnings from './Warnings';
  17. import AllocationService from '../services/allocation';
  18. import { bytesToString, toCurrency } from '../util';
  19. const Details = ({
  20. window,
  21. namespace,
  22. controllerKind,
  23. controller,
  24. pod,
  25. currency,
  26. }) => {
  27. const [cluster, setCluster] = useState('')
  28. const [node, setNode] = useState('')
  29. const [fetch, setFetch] = useState(true)
  30. const [loading, setLoading] = useState(false)
  31. const [errors, setErrors] = useState([])
  32. const [rows, setRows] = useState([])
  33. useEffect(() => {
  34. if (fetch) {
  35. setCluster('')
  36. setNode('')
  37. fetchData()
  38. }
  39. }, [fetch])
  40. async function fetchData() {
  41. setLoading(true)
  42. setErrors([])
  43. try {
  44. const filters = []
  45. if (cluster) {
  46. filters.push({
  47. property: "cluster",
  48. value: cluster,
  49. })
  50. }
  51. if (node) {
  52. filters.push({
  53. property: "node",
  54. value: node,
  55. })
  56. }
  57. if (namespace) {
  58. filters.push({
  59. property: "namespace",
  60. value: namespace,
  61. })
  62. }
  63. if (controllerKind) {
  64. filters.push({
  65. property: "controllerKind",
  66. value: controllerKind,
  67. })
  68. }
  69. if (controller) {
  70. filters.push({
  71. property: "controller",
  72. value: controller,
  73. })
  74. }
  75. if (pod) {
  76. filters.push({
  77. property: "pod",
  78. value: pod,
  79. })
  80. }
  81. const resp = await AllocationService.fetchAllocation(window, '', { accumulate: true })
  82. let data = []
  83. forEach(resp.data[0], (datum) => {
  84. if (datum.name === "__idle__") {
  85. return
  86. }
  87. if (!cluster) {
  88. setCluster(get(datum, 'properties.cluster', ''))
  89. }
  90. if (!node) {
  91. setNode(get(datum, 'properties.node', ''))
  92. }
  93. // TODO can we get pod, container back in properties?
  94. const names = datum.name.split("/")
  95. datum.pod = names[names.length-2]
  96. datum.container = names[names.length-1]
  97. datum.hours = round(get(datum, 'minutes', 0.0) / 60.0, 2)
  98. if (datum.hours > 0) {
  99. datum.cpu = round(get(datum, 'cpuCoreHours', 0.0) / datum.hours, 2)
  100. datum.cpuCostPerCoreHr = datum.cpuCost / (datum.cpu * datum.hours)
  101. if (datum.cpu === 0) {
  102. datum.cpuCostPerCoreHr = 0.0
  103. }
  104. datum.ram = round(get(datum, 'ramByteHours', 0.0) / datum.hours, 2)
  105. const ramGiB = datum.ram / 1024 / 1024 / 1024
  106. datum.ramCostPerGiBHr = datum.ramCost / (ramGiB * datum.hours)
  107. if (ramGiB === 0) {
  108. datum.ramCostPerGiBHr = 0.0
  109. }
  110. } else {
  111. datum.cpu = 0.0
  112. datum.cpuCostPerCoreHr = 0.0
  113. datum.ram = 0.0
  114. datum.ramCostPerGiBHr = 0.0
  115. }
  116. data.push(datum)
  117. })
  118. data = reverse(sortBy(data, 'totalCost'))
  119. setRows(data)
  120. } catch (e) {
  121. console.warn(`Error fetching details for (${controllerKind}, ${controller}):`, e)
  122. setErrors([{
  123. primary: "Error fetching details",
  124. secondary: `Tried fetching details for: ${namespace}, ${controllerKind}, ${controller}, ${pod}`,
  125. }])
  126. }
  127. setLoading(false)
  128. setFetch(false)
  129. }
  130. if (loading) {
  131. return (
  132. <div style={{ display: 'flex', justifyContent: 'center' }}>
  133. <div style={{ paddingTop: 100, paddingBottom: 100 }}>
  134. <CircularProgress />
  135. </div>
  136. </div>
  137. )
  138. }
  139. return (
  140. <div>
  141. {!loading && errors.length > 0 && (
  142. <div style={{ marginBottom: 20 }}>
  143. <Warnings warnings={errors} />
  144. </div>
  145. )}
  146. <List>
  147. {cluster && (
  148. <ListItem>
  149. <ListItemIcon>
  150. <ClusterIcon />
  151. </ListItemIcon>
  152. <ListItemText primary={cluster} />
  153. </ListItem>
  154. )}
  155. {node && (
  156. <ListItem>
  157. <ListItemIcon>
  158. <NodeIcon />
  159. </ListItemIcon>
  160. <ListItemText primary={node} />
  161. </ListItem>
  162. )}
  163. </List>
  164. <TableContainer>
  165. <Table>
  166. <TableHead>
  167. <TableRow>
  168. <TableCell align="left" component="th" scope="row" width={200}>Container</TableCell>
  169. <TableCell align="right" component="th" scope="row">Hours</TableCell>
  170. <TableCell align="right" component="th" scope="row">CPU</TableCell>
  171. <TableCell align="right" component="th" scope="row">$/(CPU*Hr)</TableCell>
  172. <TableCell align="right" component="th" scope="row">CPU cost</TableCell>
  173. <TableCell align="right" component="th" scope="row">RAM</TableCell>
  174. <TableCell align="right" component="th" scope="row">$/(GiB*Hr)</TableCell>
  175. <TableCell align="right" component="th" scope="row">RAM cost</TableCell>
  176. <TableCell align="right" component="th" scope="row">Total cost</TableCell>
  177. </TableRow>
  178. </TableHead>
  179. <TableBody>
  180. {rows.map((row, i) => (
  181. <TableRow key={i} hover>
  182. <TableCell align="left" component="th" scope="row" width={200}>{row.container}</TableCell>
  183. <TableCell align="right" component="th" scope="row">{row.hours}</TableCell>
  184. <TableCell align="right" component="th" scope="row">{row.cpu}</TableCell>
  185. <TableCell align="right" component="th" scope="row">{toCurrency(row.cpuCostPerCoreHr, currency, 5)}</TableCell>
  186. <TableCell align="right" component="th" scope="row">{toCurrency(row.cpuCost, currency, 3)}</TableCell>
  187. <TableCell align="right" component="th" scope="row">{bytesToString(row.ram)}</TableCell>
  188. <TableCell align="right" component="th" scope="row">{toCurrency(row.ramCostPerGiBHr, currency, 5)}</TableCell>
  189. <TableCell align="right" component="th" scope="row">{toCurrency(row.ramCost, currency, 3)}</TableCell>
  190. <TableCell align="right" component="th" scope="row">{toCurrency(row.totalCost, currency, 3)}</TableCell>
  191. </TableRow>
  192. ))}
  193. </TableBody>
  194. </Table>
  195. </TableContainer>
  196. </div>
  197. )
  198. }
  199. export default memo(Details)