@backstage/plugin-kubernetes 0.4.20 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -4,7 +4,7 @@ import React__default, { Fragment, useState, useEffect, useContext } from 'react
4
4
  import { useEntity } from '@backstage/plugin-catalog-react';
5
5
  import { Routes, Route } from 'react-router-dom';
6
6
  import { Typography, Chip, Grid, makeStyles, createStyles, Button, Drawer, IconButton, FormControlLabel, Switch, Accordion, AccordionSummary, AccordionDetails, Divider, Stepper, Step, StepLabel } from '@material-ui/core';
7
- import { WarningPanel, InfoCard, Table, SubvalueCell, StatusError, StatusOK, StatusAborted, Button as Button$1, CodeSnippet, StructuredMetadataTable, Page, Content, Progress, MissingAnnotationEmptyState } from '@backstage/core-components';
7
+ import { WarningPanel, InfoCard, Table, StatusOK, SubvalueCell, StatusError, StatusAborted, Button as Button$1, CodeSnippet, StructuredMetadataTable, StatusPending, Page, Content, Progress, MissingAnnotationEmptyState } from '@backstage/core-components';
8
8
  import EmptyStateImage from './assets/emptystate.svg';
9
9
  import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
10
10
  import Close from '@material-ui/icons/Close';
@@ -12,6 +12,7 @@ import OpenInNewIcon from '@material-ui/icons/OpenInNew';
12
12
  import { withStyles } from '@material-ui/core/styles';
13
13
  import jsYaml from 'js-yaml';
14
14
  import { useInterval } from 'react-use';
15
+ import cronstrue from 'cronstrue';
15
16
  import lodash from 'lodash';
16
17
  import PauseIcon from '@material-ui/icons/Pause';
17
18
  import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline';
@@ -44,7 +45,7 @@ class KubernetesBackendClient {
44
45
  method: "POST",
45
46
  headers: {
46
47
  "Content-Type": "application/json",
47
- ...idToken && {Authorization: `Bearer ${idToken}`}
48
+ ...idToken && { Authorization: `Bearer ${idToken}` }
48
49
  },
49
50
  body: JSON.stringify(requestBody)
50
51
  });
@@ -59,7 +60,7 @@ class KubernetesBackendClient {
59
60
  const response = await fetch(url, {
60
61
  method: "GET",
61
62
  headers: {
62
- ...idToken && {Authorization: `Bearer ${idToken}`}
63
+ ...idToken && { Authorization: `Bearer ${idToken}` }
63
64
  }
64
65
  });
65
66
  return (await this.handleResponse(response)).items;
@@ -85,7 +86,7 @@ class GoogleKubernetesAuthProvider {
85
86
  if ("auth" in requestBody) {
86
87
  requestBody.auth.google = googleAuthToken;
87
88
  } else {
88
- requestBody.auth = {google: googleAuthToken};
89
+ requestBody.auth = { google: googleAuthToken };
89
90
  }
90
91
  return requestBody;
91
92
  }
@@ -105,7 +106,7 @@ class AwsKubernetesAuthProvider {
105
106
 
106
107
  class KubernetesAuthProviders {
107
108
  constructor(options) {
108
- this.kubernetesAuthProviderMap = new Map();
109
+ this.kubernetesAuthProviderMap = /* @__PURE__ */ new Map();
109
110
  this.kubernetesAuthProviderMap.set("google", new GoogleKubernetesAuthProvider(options.googleAuthApi));
110
111
  this.kubernetesAuthProviderMap.set("serviceAccount", new ServiceAccountKubernetesAuthProvider());
111
112
  this.kubernetesAuthProviderMap.set("aws", new AwsKubernetesAuthProvider());
@@ -131,13 +132,13 @@ const kubernetesPlugin = createPlugin({
131
132
  discoveryApi: discoveryApiRef,
132
133
  identityApi: identityApiRef
133
134
  },
134
- factory: ({discoveryApi, identityApi}) => new KubernetesBackendClient({discoveryApi, identityApi})
135
+ factory: ({ discoveryApi, identityApi }) => new KubernetesBackendClient({ discoveryApi, identityApi })
135
136
  }),
136
137
  createApiFactory({
137
138
  api: kubernetesAuthProvidersApiRef,
138
- deps: {googleAuthApi: googleAuthApiRef},
139
- factory: ({googleAuthApi}) => {
140
- return new KubernetesAuthProviders({googleAuthApi});
139
+ deps: { googleAuthApi: googleAuthApiRef },
140
+ factory: ({ googleAuthApi }) => {
141
+ return new KubernetesAuthProviders({ googleAuthApi });
141
142
  }
142
143
  })
143
144
  ],
@@ -176,7 +177,7 @@ const ErrorPanel$1 = ({
176
177
  variant: "body2"
177
178
  }, "Errors: ", errorMessage));
178
179
 
179
- const columns$1 = [
180
+ const columns = [
180
181
  {
181
182
  title: "cluster",
182
183
  width: "15%",
@@ -240,15 +241,15 @@ const ErrorEmptyState = () => {
240
241
  "data-testid": "emptyStateImg"
241
242
  })));
242
243
  };
243
- const ErrorReporting = ({detectedErrors}) => {
244
+ const ErrorReporting = ({ detectedErrors }) => {
244
245
  const errors = Array.from(detectedErrors.values()).flat().sort(sortBySeverity);
245
246
  return /* @__PURE__ */ React.createElement(React.Fragment, null, errors.length === 0 ? /* @__PURE__ */ React.createElement(InfoCard, {
246
247
  title: "Error Reporting"
247
248
  }, /* @__PURE__ */ React.createElement(ErrorEmptyState, null)) : /* @__PURE__ */ React.createElement(Table, {
248
249
  title: "Error Reporting",
249
250
  data: errors,
250
- columns: columns$1,
251
- options: {paging: true, search: false}
251
+ columns,
252
+ options: { paging: true, search: false }
252
253
  }));
253
254
  };
254
255
 
@@ -276,6 +277,12 @@ const groupResponses = (fetchResponse) => {
276
277
  case "ingresses":
277
278
  prev.ingresses.push(...next.resources);
278
279
  break;
280
+ case "jobs":
281
+ prev.jobs.push(...next.resources);
282
+ break;
283
+ case "cronjobs":
284
+ prev.cronJobs.push(...next.resources);
285
+ break;
279
286
  case "customresources":
280
287
  prev.customResources.push(...next.resources);
281
288
  break;
@@ -289,6 +296,8 @@ const groupResponses = (fetchResponse) => {
289
296
  configMaps: [],
290
297
  horizontalPodAutoscalers: [],
291
298
  ingresses: [],
299
+ jobs: [],
300
+ cronJobs: [],
292
301
  customResources: []
293
302
  });
294
303
  };
@@ -330,7 +339,7 @@ const containerStatuses = (pod) => {
330
339
  return /* @__PURE__ */ React__default.createElement(Fragment, {
331
340
  key: `${(_a2 = pod.metadata) == null ? void 0 : _a2.name}-${next.name}`
332
341
  }, /* @__PURE__ */ React__default.createElement(SubvalueCell, {
333
- value: /* @__PURE__ */ React__default.createElement(StatusError, null, "Container: ", next.name),
342
+ value: reason === "Completed" ? /* @__PURE__ */ React__default.createElement(StatusOK, null, "Container: ", next.name) : /* @__PURE__ */ React__default.createElement(StatusError, null, "Container: ", next.name),
334
343
  subvalue: reason
335
344
  }), /* @__PURE__ */ React__default.createElement("br", null));
336
345
  };
@@ -363,10 +372,36 @@ const renderCondition = (condition) => {
363
372
  }
364
373
  return [condition.type, /* @__PURE__ */ React__default.createElement(StatusAborted, null)];
365
374
  };
375
+ const currentToDeclaredResourceToPerc = (current, resource) => {
376
+ if (typeof current === "number" && typeof resource === "number") {
377
+ return `${Math.round(current / resource * 100)}%`;
378
+ }
379
+ const numerator = BigInt(current);
380
+ const denominator = BigInt(resource);
381
+ return `${numerator * BigInt(100) / denominator}%`;
382
+ };
383
+ const podStatusToCpuUtil = (podStatus) => {
384
+ const cpuUtil = podStatus.cpu;
385
+ let currentUsage = cpuUtil.currentUsage;
386
+ if (typeof cpuUtil.currentUsage === "number") {
387
+ currentUsage = cpuUtil.currentUsage / 10;
388
+ }
389
+ return /* @__PURE__ */ React__default.createElement(SubvalueCell, {
390
+ value: `requests: ${currentToDeclaredResourceToPerc(currentUsage, cpuUtil.requestTotal)}`,
391
+ subvalue: `limits: ${currentToDeclaredResourceToPerc(currentUsage, cpuUtil.limitTotal)}`
392
+ });
393
+ };
394
+ const podStatusToMemoryUtil = (podStatus) => {
395
+ const memUtil = podStatus.memory;
396
+ return /* @__PURE__ */ React__default.createElement(SubvalueCell, {
397
+ value: `requests: ${currentToDeclaredResourceToPerc(memUtil.currentUsage, memUtil.requestTotal)}`,
398
+ subvalue: `limits: ${currentToDeclaredResourceToPerc(memUtil.currentUsage, memUtil.limitTotal)}`
399
+ });
400
+ };
366
401
 
367
402
  const detectErrorsInObjects = (objects, kind, clusterName, errorMappers) => {
368
403
  var _a, _b;
369
- const errors = new Map();
404
+ const errors = /* @__PURE__ */ new Map();
370
405
  for (const object of objects) {
371
406
  for (const errorMapper of errorMappers) {
372
407
  if (errorMapper.errorExists(object)) {
@@ -514,7 +549,7 @@ const hpaErrorMappers = [
514
549
  const detectErrorsInHpa = (hpas, clusterName) => detectErrorsInObjects(hpas, "HorizontalPodAutoscaler", clusterName, hpaErrorMappers);
515
550
 
516
551
  const detectErrors = (objects) => {
517
- const errors = new Map();
552
+ const errors = /* @__PURE__ */ new Map();
518
553
  for (const clusterResponse of objects.items) {
519
554
  let clusterErrors = [];
520
555
  const groupedResponses = groupResponses(clusterResponse.resources);
@@ -572,7 +607,7 @@ const useKubernetesObjects = (entity, intervalMs = 1e4) => {
572
607
  };
573
608
  };
574
609
 
575
- const PodNamesWithErrorsContext = React__default.createContext(new Set());
610
+ const PodNamesWithErrorsContext = React__default.createContext(/* @__PURE__ */ new Set());
576
611
 
577
612
  const GroupedResponsesContext = React__default.createContext({
578
613
  pods: [],
@@ -582,6 +617,8 @@ const GroupedResponsesContext = React__default.createContext({
582
617
  configMaps: [],
583
618
  horizontalPodAutoscalers: [],
584
619
  ingresses: [],
620
+ jobs: [],
621
+ cronJobs: [],
585
622
  customResources: []
586
623
  });
587
624
 
@@ -589,51 +626,86 @@ const ClusterContext = React__default.createContext({
589
626
  name: ""
590
627
  });
591
628
 
592
- const kindMappings$1 = {
629
+ const kindMappings$2 = {
593
630
  deployment: "deployment",
594
631
  ingress: "ingress",
595
632
  service: "service",
596
633
  horizontalpodautoscaler: "deployment"
597
634
  };
598
635
  function standardFormatter(options) {
599
- var _a, _b;
636
+ var _a, _b, _c, _d;
600
637
  const result = new URL(options.dashboardUrl.href);
601
- const name = (_a = options.object.metadata) == null ? void 0 : _a.name;
602
- const namespace = (_b = options.object.metadata) == null ? void 0 : _b.namespace;
603
- const validKind = kindMappings$1[options.kind.toLocaleLowerCase("en-US")];
604
- if (namespace) {
605
- result.searchParams.set("namespace", namespace);
638
+ const name = encodeURIComponent((_b = (_a = options.object.metadata) == null ? void 0 : _a.name) != null ? _b : "");
639
+ const namespace = encodeURIComponent((_d = (_c = options.object.metadata) == null ? void 0 : _c.namespace) != null ? _d : "");
640
+ const validKind = kindMappings$2[options.kind.toLocaleLowerCase("en-US")];
641
+ if (!result.pathname.endsWith("/")) {
642
+ result.pathname += "/";
606
643
  }
607
644
  if (validKind && name && namespace) {
608
645
  result.hash = `/${validKind}/${namespace}/${name}`;
609
646
  } else if (namespace) {
610
647
  result.hash = "/workloads";
611
648
  }
649
+ if (namespace) {
650
+ result.hash += `?namespace=${namespace}`;
651
+ }
612
652
  return result;
613
653
  }
614
654
 
615
- const kindMappings = {
655
+ const kindMappings$1 = {
616
656
  deployment: "apps.deployment",
617
657
  ingress: "networking.k8s.io.ingress",
618
658
  service: "service",
619
659
  horizontalpodautoscaler: "autoscaling.horizontalpodautoscaler"
620
660
  };
621
661
  function rancherFormatter(options) {
622
- var _a, _b;
623
- const result = new URL(options.dashboardUrl.href);
624
- const name = (_a = options.object.metadata) == null ? void 0 : _a.name;
625
- const namespace = (_b = options.object.metadata) == null ? void 0 : _b.namespace;
626
- const validKind = kindMappings[options.kind.toLocaleLowerCase("en-US")];
662
+ var _a, _b, _c, _d;
663
+ const basePath = new URL(options.dashboardUrl.href);
664
+ const name = encodeURIComponent((_b = (_a = options.object.metadata) == null ? void 0 : _a.name) != null ? _b : "");
665
+ const namespace = encodeURIComponent((_d = (_c = options.object.metadata) == null ? void 0 : _c.namespace) != null ? _d : "");
666
+ const validKind = kindMappings$1[options.kind.toLocaleLowerCase("en-US")];
667
+ if (!basePath.pathname.endsWith("/")) {
668
+ basePath.pathname += "/";
669
+ }
670
+ let path = "";
627
671
  if (validKind && name && namespace) {
628
- result.pathname += `explorer/${validKind}/${namespace}/${name}`;
672
+ path = `explorer/${validKind}/${namespace}/${name}`;
629
673
  } else if (namespace) {
630
- result.pathname += "explorer/workload";
674
+ path = "explorer/workload";
631
675
  }
632
- return result;
676
+ return new URL(path, basePath);
633
677
  }
634
678
 
635
- function openshiftFormatter(_options) {
636
- throw new Error("OpenShift formatter is not yet implemented. Please, contribute!");
679
+ const kindMappings = {
680
+ deployment: "deployments",
681
+ ingress: "ingresses",
682
+ service: "services",
683
+ horizontalpodautoscaler: "horizontalpodautoscalers",
684
+ persistentvolume: "persistentvolumes"
685
+ };
686
+ function openshiftFormatter(options) {
687
+ var _a, _b, _c, _d;
688
+ const basePath = new URL(options.dashboardUrl.href);
689
+ const name = encodeURIComponent((_b = (_a = options.object.metadata) == null ? void 0 : _a.name) != null ? _b : "");
690
+ const namespace = encodeURIComponent((_d = (_c = options.object.metadata) == null ? void 0 : _c.namespace) != null ? _d : "");
691
+ const validKind = kindMappings[options.kind.toLocaleLowerCase("en-US")];
692
+ if (!basePath.pathname.endsWith("/")) {
693
+ basePath.pathname += "/";
694
+ }
695
+ let path = "";
696
+ if (namespace) {
697
+ if (name && validKind) {
698
+ path = `k8s/ns/${namespace}/${validKind}/${name}`;
699
+ } else {
700
+ path = `k8s/cluster/projects/${namespace}`;
701
+ }
702
+ } else if (validKind) {
703
+ path = `k8s/cluster/${validKind}`;
704
+ if (name) {
705
+ path += `/${name}`;
706
+ }
707
+ }
708
+ return new URL(path, basePath);
637
709
  }
638
710
 
639
711
  function aksFormatter(_options) {
@@ -675,7 +747,7 @@ function formatClusterLink(options) {
675
747
  object: options.object,
676
748
  kind: options.kind
677
749
  });
678
- return `${url.origin}${url.pathname}${url.hash}${url.search}`;
750
+ return url.toString();
679
751
  }
680
752
 
681
753
  const useDrawerStyles = makeStyles((theme) => createStyles({
@@ -715,7 +787,7 @@ const PodDrawerButton = withStyles({
715
787
  textTransform: "none"
716
788
  }
717
789
  })(Button);
718
- const ErrorPanel = ({cluster, errorMessage}) => /* @__PURE__ */ React__default.createElement(WarningPanel, {
790
+ const ErrorPanel = ({ cluster, errorMessage }) => /* @__PURE__ */ React__default.createElement(WarningPanel, {
719
791
  title: "There was a problem formatting the link to the Kubernetes dashboard",
720
792
  message: `Could not format the link to the dashboard of your cluster named '${cluster.name}'. Its dashboardApp property has been set to '${cluster.dashboardApp || "standard"}.'`
721
793
  }, errorMessage && /* @__PURE__ */ React__default.createElement(Typography, {
@@ -748,7 +820,7 @@ const KubernetesDrawerContent = ({
748
820
  const [isYaml, setIsYaml] = useState(false);
749
821
  const classes = useDrawerContentStyles();
750
822
  const cluster = useContext(ClusterContext);
751
- const {clusterLink, errorMessage} = tryFormatClusterLink({
823
+ const { clusterLink, errorMessage } = tryFormatClusterLink({
752
824
  dashboardUrl: cluster.dashboardUrl,
753
825
  dashboardApp: cluster.dashboardApp,
754
826
  object,
@@ -877,7 +949,11 @@ const PodDrawer = ({
877
949
  });
878
950
  };
879
951
 
880
- const columns = [
952
+ const PodNamesWithMetricsContext = React__default.createContext(/* @__PURE__ */ new Map());
953
+
954
+ const READY_COLUMNS = "READY";
955
+ const RESOURCE_COLUMNS = "RESOURCE";
956
+ const DEFAULT_COLUMNS = [
881
957
  {
882
958
  title: "name",
883
959
  highlight: true,
@@ -892,6 +968,12 @@ const columns = [
892
968
  return (_b = (_a = pod.status) == null ? void 0 : _a.phase) != null ? _b : "unknown";
893
969
  }
894
970
  },
971
+ {
972
+ title: "status",
973
+ render: containerStatuses
974
+ }
975
+ ];
976
+ const READY = [
895
977
  {
896
978
  title: "containers ready",
897
979
  align: "center",
@@ -902,13 +984,41 @@ const columns = [
902
984
  align: "center",
903
985
  render: totalRestarts,
904
986
  type: "numeric"
905
- },
906
- {
907
- title: "status",
908
- render: containerStatuses
909
987
  }
910
988
  ];
911
- const PodsTable = ({pods}) => {
989
+ const PodsTable = ({ pods, extraColumns = [] }) => {
990
+ const podNamesWithMetrics = useContext(PodNamesWithMetricsContext);
991
+ const columns = [...DEFAULT_COLUMNS];
992
+ if (extraColumns.includes(READY_COLUMNS)) {
993
+ columns.push(...READY);
994
+ }
995
+ if (extraColumns.includes(RESOURCE_COLUMNS)) {
996
+ const resourceColumns = [
997
+ {
998
+ title: "CPU usage %",
999
+ render: (pod) => {
1000
+ var _a, _b;
1001
+ const metrics = podNamesWithMetrics.get((_b = (_a = pod.metadata) == null ? void 0 : _a.name) != null ? _b : "");
1002
+ if (!metrics) {
1003
+ return "unknown";
1004
+ }
1005
+ return podStatusToCpuUtil(metrics);
1006
+ }
1007
+ },
1008
+ {
1009
+ title: "Memory usage %",
1010
+ render: (pod) => {
1011
+ var _a, _b;
1012
+ const metrics = podNamesWithMetrics.get((_b = (_a = pod.metadata) == null ? void 0 : _a.name) != null ? _b : "");
1013
+ if (!metrics) {
1014
+ return "unknown";
1015
+ }
1016
+ return podStatusToMemoryUtil(metrics);
1017
+ }
1018
+ }
1019
+ ];
1020
+ columns.push(...resourceColumns);
1021
+ }
912
1022
  const tableStyle = {
913
1023
  minWidth: "0",
914
1024
  width: "100%"
@@ -916,7 +1026,7 @@ const PodsTable = ({pods}) => {
916
1026
  return /* @__PURE__ */ React__default.createElement("div", {
917
1027
  style: tableStyle
918
1028
  }, /* @__PURE__ */ React__default.createElement(Table, {
919
- options: {paging: true, search: false},
1029
+ options: { paging: true, search: false },
920
1030
  data: pods,
921
1031
  columns
922
1032
  }));
@@ -1033,7 +1143,7 @@ const DeploymentSummary = ({
1033
1143
  item: true,
1034
1144
  xs: 1
1035
1145
  }, /* @__PURE__ */ React__default.createElement(Divider, {
1036
- style: {height: "5em"},
1146
+ style: { height: "5em" },
1037
1147
  orientation: "vertical"
1038
1148
  })), hpa && /* @__PURE__ */ React__default.createElement(Grid, {
1039
1149
  item: true,
@@ -1083,7 +1193,7 @@ const DeploymentAccordion = ({
1083
1193
  return podNamesWithErrors.has((_b = (_a = p.metadata) == null ? void 0 : _a.name) != null ? _b : "");
1084
1194
  });
1085
1195
  return /* @__PURE__ */ React__default.createElement(Accordion, {
1086
- TransitionProps: {unmountOnExit: true}
1196
+ TransitionProps: { unmountOnExit: true }
1087
1197
  }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1088
1198
  expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1089
1199
  }, /* @__PURE__ */ React__default.createElement(DeploymentSummary, {
@@ -1092,7 +1202,8 @@ const DeploymentAccordion = ({
1092
1202
  numberOfPodsWithErrors: podsWithErrors.length,
1093
1203
  hpa: matchingHpa
1094
1204
  })), /* @__PURE__ */ React__default.createElement(AccordionDetails, null, /* @__PURE__ */ React__default.createElement(PodsTable, {
1095
- pods: ownedPods
1205
+ pods: ownedPods,
1206
+ extraColumns: [READY_COLUMNS, RESOURCE_COLUMNS]
1096
1207
  })));
1097
1208
  };
1098
1209
  const DeploymentsAccordions = ({}) => {
@@ -1150,7 +1261,7 @@ const IngressDrawer = ({
1150
1261
  }, "Ingress"))));
1151
1262
  };
1152
1263
 
1153
- const IngressSummary = ({ingress}) => {
1264
+ const IngressSummary = ({ ingress }) => {
1154
1265
  return /* @__PURE__ */ React__default.createElement(Grid, {
1155
1266
  container: true,
1156
1267
  direction: "row",
@@ -1165,20 +1276,20 @@ const IngressSummary = ({ingress}) => {
1165
1276
  item: true,
1166
1277
  xs: 1
1167
1278
  }, /* @__PURE__ */ React__default.createElement(Divider, {
1168
- style: {height: "5em"},
1279
+ style: { height: "5em" },
1169
1280
  orientation: "vertical"
1170
1281
  })));
1171
1282
  };
1172
- const IngressCard = ({ingress}) => {
1283
+ const IngressCard = ({ ingress }) => {
1173
1284
  return /* @__PURE__ */ React__default.createElement(StructuredMetadataTable, {
1174
1285
  metadata: {
1175
1286
  ...ingress.spec
1176
1287
  }
1177
1288
  });
1178
1289
  };
1179
- const IngressAccordion = ({ingress}) => {
1290
+ const IngressAccordion = ({ ingress }) => {
1180
1291
  return /* @__PURE__ */ React__default.createElement(Accordion, {
1181
- TransitionProps: {unmountOnExit: true}
1292
+ TransitionProps: { unmountOnExit: true }
1182
1293
  }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1183
1294
  expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1184
1295
  }, /* @__PURE__ */ React__default.createElement(IngressSummary, {
@@ -1233,7 +1344,7 @@ const ServiceDrawer = ({
1233
1344
  }, "Service"))));
1234
1345
  };
1235
1346
 
1236
- const ServiceSummary = ({service}) => {
1347
+ const ServiceSummary = ({ service }) => {
1237
1348
  var _a, _b;
1238
1349
  return /* @__PURE__ */ React__default.createElement(Grid, {
1239
1350
  container: true,
@@ -1249,7 +1360,7 @@ const ServiceSummary = ({service}) => {
1249
1360
  item: true,
1250
1361
  xs: 1
1251
1362
  }, /* @__PURE__ */ React__default.createElement(Divider, {
1252
- style: {height: "5em"},
1363
+ style: { height: "5em" },
1253
1364
  orientation: "vertical"
1254
1365
  })), /* @__PURE__ */ React__default.createElement(Grid, {
1255
1366
  item: true
@@ -1257,7 +1368,7 @@ const ServiceSummary = ({service}) => {
1257
1368
  variant: "subtitle2"
1258
1369
  }, "Type: ", (_b = (_a = service.spec) == null ? void 0 : _a.type) != null ? _b : "?")));
1259
1370
  };
1260
- const ServiceCard = ({service}) => {
1371
+ const ServiceCard = ({ service }) => {
1261
1372
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1262
1373
  const metadata = {};
1263
1374
  if ((_d = (_c = (_b = (_a = service.status) == null ? void 0 : _a.loadBalancer) == null ? void 0 : _b.ingress) == null ? void 0 : _c.length) != null ? _d : -1 > 0) {
@@ -1277,9 +1388,9 @@ const ServiceCard = ({service}) => {
1277
1388
  }
1278
1389
  });
1279
1390
  };
1280
- const ServiceAccordion = ({service}) => {
1391
+ const ServiceAccordion = ({ service }) => {
1281
1392
  return /* @__PURE__ */ React__default.createElement(Accordion, {
1282
- TransitionProps: {unmountOnExit: true}
1393
+ TransitionProps: { unmountOnExit: true }
1283
1394
  }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1284
1395
  expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1285
1396
  }, /* @__PURE__ */ React__default.createElement(ServiceSummary, {
@@ -1304,6 +1415,214 @@ const ServicesAccordions = ({}) => {
1304
1415
  }))));
1305
1416
  };
1306
1417
 
1418
+ const JobDrawer = ({
1419
+ job,
1420
+ expanded
1421
+ }) => {
1422
+ var _a, _b;
1423
+ return /* @__PURE__ */ React__default.createElement(KubernetesDrawer, {
1424
+ object: job,
1425
+ expanded,
1426
+ kind: "Job",
1427
+ renderObject: (jobObj) => {
1428
+ var _a2, _b2, _c, _d, _e, _f, _g, _h;
1429
+ return {
1430
+ parallelism: (_b2 = (_a2 = jobObj.spec) == null ? void 0 : _a2.parallelism) != null ? _b2 : "???",
1431
+ completions: (_d = (_c = jobObj.spec) == null ? void 0 : _c.completions) != null ? _d : "???",
1432
+ backoffLimit: (_f = (_e = jobObj.spec) == null ? void 0 : _e.backoffLimit) != null ? _f : "???",
1433
+ startTime: (_h = (_g = jobObj.status) == null ? void 0 : _g.startTime) != null ? _h : "???"
1434
+ };
1435
+ }
1436
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1437
+ container: true,
1438
+ direction: "column",
1439
+ justifyContent: "flex-start",
1440
+ alignItems: "flex-start",
1441
+ spacing: 0
1442
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1443
+ item: true
1444
+ }, /* @__PURE__ */ React__default.createElement(Typography, {
1445
+ variant: "h6"
1446
+ }, (_b = (_a = job.metadata) == null ? void 0 : _a.name) != null ? _b : "unknown object")), /* @__PURE__ */ React__default.createElement(Grid, {
1447
+ item: true
1448
+ }, /* @__PURE__ */ React__default.createElement(Typography, {
1449
+ color: "textSecondary",
1450
+ variant: "body1"
1451
+ }, "Job"))));
1452
+ };
1453
+
1454
+ const JobSummary = ({ job }) => {
1455
+ var _a, _b, _c, _d, _e, _f;
1456
+ return /* @__PURE__ */ React__default.createElement(Grid, {
1457
+ container: true,
1458
+ direction: "row",
1459
+ justifyContent: "flex-start",
1460
+ alignItems: "center"
1461
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1462
+ xs: 3,
1463
+ item: true
1464
+ }, /* @__PURE__ */ React__default.createElement(JobDrawer, {
1465
+ job
1466
+ })), /* @__PURE__ */ React__default.createElement(Grid, {
1467
+ item: true,
1468
+ xs: 1
1469
+ }, /* @__PURE__ */ React__default.createElement(Divider, {
1470
+ style: { height: "5em" },
1471
+ orientation: "vertical"
1472
+ })), /* @__PURE__ */ React__default.createElement(Grid, {
1473
+ item: true,
1474
+ container: true,
1475
+ xs: 8,
1476
+ direction: "column",
1477
+ justifyContent: "flex-start",
1478
+ alignItems: "flex-start"
1479
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1480
+ item: true
1481
+ }, ((_a = job.status) == null ? void 0 : _a.succeeded) && /* @__PURE__ */ React__default.createElement(StatusOK, null, "Succeeded"), ((_b = job.status) == null ? void 0 : _b.active) && /* @__PURE__ */ React__default.createElement(StatusPending, null, "Running"), ((_c = job.status) == null ? void 0 : _c.failed) && /* @__PURE__ */ React__default.createElement(StatusError, null, "Failed")), /* @__PURE__ */ React__default.createElement(Grid, {
1482
+ item: true
1483
+ }, "Start time: ", (_e = (_d = job.status) == null ? void 0 : _d.startTime) == null ? void 0 : _e.toString()), ((_f = job.status) == null ? void 0 : _f.completionTime) && /* @__PURE__ */ React__default.createElement(Grid, {
1484
+ item: true
1485
+ }, "Completion time: ", job.status.completionTime.toString())));
1486
+ };
1487
+ const JobAccordion = ({ job, ownedPods }) => {
1488
+ return /* @__PURE__ */ React__default.createElement(Accordion, {
1489
+ TransitionProps: { unmountOnExit: true }
1490
+ }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1491
+ expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1492
+ }, /* @__PURE__ */ React__default.createElement(JobSummary, {
1493
+ job
1494
+ })), /* @__PURE__ */ React__default.createElement(AccordionDetails, null, /* @__PURE__ */ React__default.createElement(PodsTable, {
1495
+ pods: ownedPods
1496
+ })));
1497
+ };
1498
+ const JobsAccordions = ({ jobs }) => {
1499
+ const groupedResponses = useContext(GroupedResponsesContext);
1500
+ return /* @__PURE__ */ React__default.createElement(Grid, {
1501
+ container: true,
1502
+ direction: "column",
1503
+ justifyContent: "flex-start",
1504
+ alignItems: "flex-start"
1505
+ }, jobs.map((job, i) => /* @__PURE__ */ React__default.createElement(Grid, {
1506
+ container: true,
1507
+ item: true,
1508
+ key: i,
1509
+ xs: true
1510
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1511
+ item: true,
1512
+ xs: true
1513
+ }, /* @__PURE__ */ React__default.createElement(JobAccordion, {
1514
+ ownedPods: getOwnedResources(job, groupedResponses.pods),
1515
+ job
1516
+ })))));
1517
+ };
1518
+
1519
+ const CronJobDrawer = ({
1520
+ cronJob,
1521
+ expanded
1522
+ }) => {
1523
+ var _a, _b, _c;
1524
+ const namespace = (_a = cronJob.metadata) == null ? void 0 : _a.namespace;
1525
+ return /* @__PURE__ */ React__default.createElement(KubernetesDrawer, {
1526
+ object: cronJob,
1527
+ expanded,
1528
+ kind: "CronJob",
1529
+ renderObject: (cronJobObj) => {
1530
+ var _a2, _b2, _c2, _d, _e, _f, _g, _h;
1531
+ return {
1532
+ schedule: (_b2 = (_a2 = cronJobObj.spec) == null ? void 0 : _a2.schedule) != null ? _b2 : "???",
1533
+ startingDeadlineSeconds: (_d = (_c2 = cronJobObj.spec) == null ? void 0 : _c2.startingDeadlineSeconds) != null ? _d : "???",
1534
+ concurrencyPolicy: (_f = (_e = cronJobObj.spec) == null ? void 0 : _e.concurrencyPolicy) != null ? _f : "???",
1535
+ lastScheduleTime: (_h = (_g = cronJobObj.status) == null ? void 0 : _g.lastScheduleTime) != null ? _h : "???"
1536
+ };
1537
+ }
1538
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1539
+ container: true,
1540
+ direction: "column",
1541
+ justifyContent: "flex-start",
1542
+ alignItems: "flex-start",
1543
+ spacing: 0
1544
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1545
+ item: true
1546
+ }, /* @__PURE__ */ React__default.createElement(Typography, {
1547
+ variant: "h5"
1548
+ }, (_c = (_b = cronJob.metadata) == null ? void 0 : _b.name) != null ? _c : "unknown object")), /* @__PURE__ */ React__default.createElement(Grid, {
1549
+ item: true
1550
+ }, /* @__PURE__ */ React__default.createElement(Typography, {
1551
+ color: "textSecondary",
1552
+ variant: "body1"
1553
+ }, "CronJob")), namespace && /* @__PURE__ */ React__default.createElement(Grid, {
1554
+ item: true
1555
+ }, /* @__PURE__ */ React__default.createElement(Chip, {
1556
+ size: "small",
1557
+ label: `namespace: ${namespace}`
1558
+ }))));
1559
+ };
1560
+
1561
+ const CronJobSummary = ({ cronJob }) => {
1562
+ var _a, _b;
1563
+ return /* @__PURE__ */ React__default.createElement(Grid, {
1564
+ container: true,
1565
+ direction: "row",
1566
+ justifyContent: "flex-start",
1567
+ alignItems: "center"
1568
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1569
+ xs: 3,
1570
+ item: true
1571
+ }, /* @__PURE__ */ React__default.createElement(CronJobDrawer, {
1572
+ cronJob
1573
+ })), /* @__PURE__ */ React__default.createElement(Grid, {
1574
+ item: true,
1575
+ xs: 1
1576
+ }, /* @__PURE__ */ React__default.createElement(Divider, {
1577
+ style: { height: "5em" },
1578
+ orientation: "vertical"
1579
+ })), /* @__PURE__ */ React__default.createElement(Grid, {
1580
+ item: true,
1581
+ container: true,
1582
+ xs: 5,
1583
+ direction: "column",
1584
+ justifyContent: "flex-start",
1585
+ alignItems: "flex-start"
1586
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1587
+ item: true
1588
+ }, ((_a = cronJob.spec) == null ? void 0 : _a.suspend) ? /* @__PURE__ */ React__default.createElement(StatusError, null, "Suspended") : /* @__PURE__ */ React__default.createElement(StatusOK, null, "Active")), /* @__PURE__ */ React__default.createElement(Grid, {
1589
+ item: true
1590
+ }, /* @__PURE__ */ React__default.createElement(Typography, {
1591
+ variant: "body1"
1592
+ }, "Schedule:", " ", ((_b = cronJob.spec) == null ? void 0 : _b.schedule) ? `${cronJob.spec.schedule} (${cronstrue.toString(cronJob.spec.schedule)})` : "N/A"))));
1593
+ };
1594
+ const CronJobAccordion = ({ cronJob, ownedJobs }) => {
1595
+ return /* @__PURE__ */ React__default.createElement(Accordion, {
1596
+ TransitionProps: { unmountOnExit: true }
1597
+ }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1598
+ expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1599
+ }, /* @__PURE__ */ React__default.createElement(CronJobSummary, {
1600
+ cronJob
1601
+ })), /* @__PURE__ */ React__default.createElement(AccordionDetails, null, /* @__PURE__ */ React__default.createElement(JobsAccordions, {
1602
+ jobs: ownedJobs.reverse()
1603
+ })));
1604
+ };
1605
+ const CronJobsAccordions = ({}) => {
1606
+ const groupedResponses = useContext(GroupedResponsesContext);
1607
+ return /* @__PURE__ */ React__default.createElement(Grid, {
1608
+ container: true,
1609
+ direction: "column",
1610
+ justifyContent: "flex-start",
1611
+ alignItems: "flex-start"
1612
+ }, groupedResponses.cronJobs.map((cronJob, i) => /* @__PURE__ */ React__default.createElement(Grid, {
1613
+ container: true,
1614
+ item: true,
1615
+ key: i,
1616
+ xs: true
1617
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1618
+ item: true,
1619
+ xs: true
1620
+ }, /* @__PURE__ */ React__default.createElement(CronJobAccordion, {
1621
+ ownedJobs: getOwnedResources(cronJob, groupedResponses.jobs),
1622
+ cronJob
1623
+ })))));
1624
+ };
1625
+
1307
1626
  const RolloutDrawer = ({
1308
1627
  rollout,
1309
1628
  expanded
@@ -1403,7 +1722,7 @@ const RolloutSummary = ({
1403
1722
  item: true,
1404
1723
  xs: 1
1405
1724
  }, /* @__PURE__ */ React__default.createElement(Divider, {
1406
- style: {height: "5em"},
1725
+ style: { height: "5em" },
1407
1726
  orientation: "vertical"
1408
1727
  })), hpa && /* @__PURE__ */ React__default.createElement(Grid, {
1409
1728
  item: true,
@@ -1451,7 +1770,7 @@ const RolloutSummary = ({
1451
1770
  }
1452
1771
  }, /* @__PURE__ */ React__default.createElement(PauseIcon, null), /* @__PURE__ */ React__default.createElement(Typography, {
1453
1772
  variant: "subtitle1"
1454
- }, "Paused (", DateTime.fromISO(pauseTime).toRelative({locale: "en"}), ")"))), abortedMessage && /* @__PURE__ */ React__default.createElement(Grid, {
1773
+ }, "Paused (", DateTime.fromISO(pauseTime).toRelative({ locale: "en" }), ")"))), abortedMessage && /* @__PURE__ */ React__default.createElement(Grid, {
1455
1774
  item: true,
1456
1775
  xs: 3
1457
1776
  }, AbortedTitle));
@@ -1472,7 +1791,7 @@ const RolloutAccordion = ({
1472
1791
  const abortedMessage = findAbortedMessage(rollout);
1473
1792
  return /* @__PURE__ */ React__default.createElement(Accordion, {
1474
1793
  defaultExpanded,
1475
- TransitionProps: {unmountOnExit: true}
1794
+ TransitionProps: { unmountOnExit: true }
1476
1795
  }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1477
1796
  expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1478
1797
  }, /* @__PURE__ */ React__default.createElement(RolloutSummary, {
@@ -1481,11 +1800,11 @@ const RolloutAccordion = ({
1481
1800
  numberOfPodsWithErrors: podsWithErrors.length,
1482
1801
  hpa: matchingHpa
1483
1802
  })), /* @__PURE__ */ React__default.createElement(AccordionDetails, null, /* @__PURE__ */ React__default.createElement("div", {
1484
- style: {width: "100%"}
1803
+ style: { width: "100%" }
1485
1804
  }, /* @__PURE__ */ React__default.createElement("div", null, /* @__PURE__ */ React__default.createElement(Typography, {
1486
1805
  variant: "h6"
1487
1806
  }, "Rollout status")), /* @__PURE__ */ React__default.createElement("div", {
1488
- style: {margin: "1rem"}
1807
+ style: { margin: "1rem" }
1489
1808
  }, abortedMessage && /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, AbortedTitle, /* @__PURE__ */ React__default.createElement(Typography, {
1490
1809
  variant: "subtitle2"
1491
1810
  }, abortedMessage)), /* @__PURE__ */ React__default.createElement(StepsProgress, {
@@ -1493,7 +1812,8 @@ const RolloutAccordion = ({
1493
1812
  steps: (_f = (_e = (_d = (_c = rollout.spec) == null ? void 0 : _c.strategy) == null ? void 0 : _d.canary) == null ? void 0 : _e.steps) != null ? _f : [],
1494
1813
  currentStepIndex
1495
1814
  })), /* @__PURE__ */ React__default.createElement("div", null, /* @__PURE__ */ React__default.createElement(PodsTable, {
1496
- pods: ownedPods
1815
+ pods: ownedPods,
1816
+ extraColumns: [READY_COLUMNS, RESOURCE_COLUMNS]
1497
1817
  })))));
1498
1818
  };
1499
1819
  const RolloutAccordions = ({
@@ -1575,7 +1895,7 @@ const DefaultCustomResourceSummary = ({
1575
1895
  item: true,
1576
1896
  xs: 1
1577
1897
  }, /* @__PURE__ */ React__default.createElement(Divider, {
1578
- style: {height: "5em"},
1898
+ style: { height: "5em" },
1579
1899
  orientation: "vertical"
1580
1900
  })));
1581
1901
  };
@@ -1586,7 +1906,7 @@ const DefaultCustomResourceAccordion = ({
1586
1906
  }) => {
1587
1907
  return /* @__PURE__ */ React__default.createElement(Accordion, {
1588
1908
  defaultExpanded,
1589
- TransitionProps: {unmountOnExit: true}
1909
+ TransitionProps: { unmountOnExit: true }
1590
1910
  }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1591
1911
  expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1592
1912
  }, /* @__PURE__ */ React__default.createElement(DefaultCustomResourceSummary, {
@@ -1676,7 +1996,7 @@ const ClusterSummary = ({
1676
1996
  item: true,
1677
1997
  xs: 1
1678
1998
  }, /* @__PURE__ */ React__default.createElement(Divider, {
1679
- style: {height: "4em"},
1999
+ style: { height: "4em" },
1680
2000
  orientation: "vertical"
1681
2001
  })), /* @__PURE__ */ React__default.createElement(Grid, {
1682
2002
  item: true,
@@ -1691,16 +2011,26 @@ const ClusterSummary = ({
1691
2011
  item: true
1692
2012
  }, numberOfPodsWithErrors > 0 ? /* @__PURE__ */ React__default.createElement(StatusError, null, numberOfPodsWithErrors, " pods with errors") : /* @__PURE__ */ React__default.createElement(StatusOK, null, "No pods with errors"))));
1693
2013
  };
1694
- const Cluster = ({clusterObjects, podsWithErrors}) => {
2014
+ const Cluster = ({ clusterObjects, podsWithErrors }) => {
1695
2015
  const groupedResponses = groupResponses(clusterObjects.resources);
2016
+ const podNameToMetrics = clusterObjects.podMetrics.flat().reduce((accum, next) => {
2017
+ var _a;
2018
+ const name = (_a = next.pod.metadata) == null ? void 0 : _a.name;
2019
+ if (name !== void 0) {
2020
+ accum.set(name, next);
2021
+ }
2022
+ return accum;
2023
+ }, /* @__PURE__ */ new Map());
1696
2024
  return /* @__PURE__ */ React__default.createElement(ClusterContext.Provider, {
1697
2025
  value: clusterObjects.cluster
1698
2026
  }, /* @__PURE__ */ React__default.createElement(GroupedResponsesContext.Provider, {
1699
2027
  value: groupedResponses
2028
+ }, /* @__PURE__ */ React__default.createElement(PodNamesWithMetricsContext.Provider, {
2029
+ value: podNameToMetrics
1700
2030
  }, /* @__PURE__ */ React__default.createElement(PodNamesWithErrorsContext.Provider, {
1701
2031
  value: podsWithErrors
1702
2032
  }, /* @__PURE__ */ React__default.createElement(Accordion, {
1703
- TransitionProps: {unmountOnExit: true}
2033
+ TransitionProps: { unmountOnExit: true }
1704
2034
  }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1705
2035
  expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1706
2036
  }, /* @__PURE__ */ React__default.createElement(ClusterSummary, {
@@ -1718,14 +2048,16 @@ const Cluster = ({clusterObjects, podsWithErrors}) => {
1718
2048
  item: true
1719
2049
  }, /* @__PURE__ */ React__default.createElement(IngressesAccordions, null)), /* @__PURE__ */ React__default.createElement(Grid, {
1720
2050
  item: true
1721
- }, /* @__PURE__ */ React__default.createElement(ServicesAccordions, null))))))));
2051
+ }, /* @__PURE__ */ React__default.createElement(ServicesAccordions, null)), /* @__PURE__ */ React__default.createElement(Grid, {
2052
+ item: true
2053
+ }, /* @__PURE__ */ React__default.createElement(CronJobsAccordions, null)))))))));
1722
2054
  };
1723
2055
 
1724
- const KubernetesContent = ({entity}) => {
2056
+ const KubernetesContent = ({ entity }) => {
1725
2057
  var _a;
1726
- const {kubernetesObjects, error} = useKubernetesObjects(entity);
2058
+ const { kubernetesObjects, error } = useKubernetesObjects(entity);
1727
2059
  const clustersWithErrors = (_a = kubernetesObjects == null ? void 0 : kubernetesObjects.items.filter((r) => r.errors.length > 0)) != null ? _a : [];
1728
- const detectedErrors = kubernetesObjects !== void 0 ? detectErrors(kubernetesObjects) : new Map();
2060
+ const detectedErrors = kubernetesObjects !== void 0 ? detectErrors(kubernetesObjects) : /* @__PURE__ */ new Map();
1729
2061
  return /* @__PURE__ */ React__default.createElement(Page, {
1730
2062
  themeId: "tool"
1731
2063
  }, /* @__PURE__ */ React__default.createElement(Content, null, kubernetesObjects === void 0 && error === void 0 && /* @__PURE__ */ React__default.createElement(Progress, null), clustersWithErrors.length > 0 && /* @__PURE__ */ React__default.createElement(Grid, {
@@ -1803,7 +2135,7 @@ const isKubernetesAvailable = (entity) => {
1803
2135
  };
1804
2136
  const Router = (_props) => {
1805
2137
  var _a, _b;
1806
- const {entity} = useEntity();
2138
+ const { entity } = useEntity();
1807
2139
  const kubernetesAnnotationValue = (_a = entity.metadata.annotations) == null ? void 0 : _a[KUBERNETES_ANNOTATION];
1808
2140
  const kubernetesLabelSelectorQueryAnnotationValue = (_b = entity.metadata.annotations) == null ? void 0 : _b[KUBERNETES_LABEL_SELECTOR_QUERY_ANNOTATION];
1809
2141
  if (kubernetesAnnotationValue || kubernetesLabelSelectorQueryAnnotationValue) {