@backstage/plugin-kubernetes 0.4.19 → 0.5.0

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());
@@ -120,8 +121,7 @@ class KubernetesAuthProviders {
120
121
  }
121
122
 
122
123
  const rootCatalogKubernetesRouteRef = createRouteRef({
123
- path: "*",
124
- title: "Kubernetes"
124
+ id: "kubernetes"
125
125
  });
126
126
  const kubernetesPlugin = createPlugin({
127
127
  id: "kubernetes",
@@ -132,13 +132,13 @@ const kubernetesPlugin = createPlugin({
132
132
  discoveryApi: discoveryApiRef,
133
133
  identityApi: identityApiRef
134
134
  },
135
- factory: ({discoveryApi, identityApi}) => new KubernetesBackendClient({discoveryApi, identityApi})
135
+ factory: ({ discoveryApi, identityApi }) => new KubernetesBackendClient({ discoveryApi, identityApi })
136
136
  }),
137
137
  createApiFactory({
138
138
  api: kubernetesAuthProvidersApiRef,
139
- deps: {googleAuthApi: googleAuthApiRef},
140
- factory: ({googleAuthApi}) => {
141
- return new KubernetesAuthProviders({googleAuthApi});
139
+ deps: { googleAuthApi: googleAuthApiRef },
140
+ factory: ({ googleAuthApi }) => {
141
+ return new KubernetesAuthProviders({ googleAuthApi });
142
142
  }
143
143
  })
144
144
  ],
@@ -177,7 +177,7 @@ const ErrorPanel$1 = ({
177
177
  variant: "body2"
178
178
  }, "Errors: ", errorMessage));
179
179
 
180
- const columns$1 = [
180
+ const columns = [
181
181
  {
182
182
  title: "cluster",
183
183
  width: "15%",
@@ -241,15 +241,15 @@ const ErrorEmptyState = () => {
241
241
  "data-testid": "emptyStateImg"
242
242
  })));
243
243
  };
244
- const ErrorReporting = ({detectedErrors}) => {
244
+ const ErrorReporting = ({ detectedErrors }) => {
245
245
  const errors = Array.from(detectedErrors.values()).flat().sort(sortBySeverity);
246
246
  return /* @__PURE__ */ React.createElement(React.Fragment, null, errors.length === 0 ? /* @__PURE__ */ React.createElement(InfoCard, {
247
247
  title: "Error Reporting"
248
248
  }, /* @__PURE__ */ React.createElement(ErrorEmptyState, null)) : /* @__PURE__ */ React.createElement(Table, {
249
249
  title: "Error Reporting",
250
250
  data: errors,
251
- columns: columns$1,
252
- options: {paging: true, search: false}
251
+ columns,
252
+ options: { paging: true, search: false }
253
253
  }));
254
254
  };
255
255
 
@@ -277,6 +277,12 @@ const groupResponses = (fetchResponse) => {
277
277
  case "ingresses":
278
278
  prev.ingresses.push(...next.resources);
279
279
  break;
280
+ case "jobs":
281
+ prev.jobs.push(...next.resources);
282
+ break;
283
+ case "cronjobs":
284
+ prev.cronJobs.push(...next.resources);
285
+ break;
280
286
  case "customresources":
281
287
  prev.customResources.push(...next.resources);
282
288
  break;
@@ -290,6 +296,8 @@ const groupResponses = (fetchResponse) => {
290
296
  configMaps: [],
291
297
  horizontalPodAutoscalers: [],
292
298
  ingresses: [],
299
+ jobs: [],
300
+ cronJobs: [],
293
301
  customResources: []
294
302
  });
295
303
  };
@@ -364,10 +372,36 @@ const renderCondition = (condition) => {
364
372
  }
365
373
  return [condition.type, /* @__PURE__ */ React__default.createElement(StatusAborted, null)];
366
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
+ };
367
401
 
368
402
  const detectErrorsInObjects = (objects, kind, clusterName, errorMappers) => {
369
403
  var _a, _b;
370
- const errors = new Map();
404
+ const errors = /* @__PURE__ */ new Map();
371
405
  for (const object of objects) {
372
406
  for (const errorMapper of errorMappers) {
373
407
  if (errorMapper.errorExists(object)) {
@@ -515,7 +549,7 @@ const hpaErrorMappers = [
515
549
  const detectErrorsInHpa = (hpas, clusterName) => detectErrorsInObjects(hpas, "HorizontalPodAutoscaler", clusterName, hpaErrorMappers);
516
550
 
517
551
  const detectErrors = (objects) => {
518
- const errors = new Map();
552
+ const errors = /* @__PURE__ */ new Map();
519
553
  for (const clusterResponse of objects.items) {
520
554
  let clusterErrors = [];
521
555
  const groupedResponses = groupResponses(clusterResponse.resources);
@@ -573,7 +607,7 @@ const useKubernetesObjects = (entity, intervalMs = 1e4) => {
573
607
  };
574
608
  };
575
609
 
576
- const PodNamesWithErrorsContext = React__default.createContext(new Set());
610
+ const PodNamesWithErrorsContext = React__default.createContext(/* @__PURE__ */ new Set());
577
611
 
578
612
  const GroupedResponsesContext = React__default.createContext({
579
613
  pods: [],
@@ -583,6 +617,8 @@ const GroupedResponsesContext = React__default.createContext({
583
617
  configMaps: [],
584
618
  horizontalPodAutoscalers: [],
585
619
  ingresses: [],
620
+ jobs: [],
621
+ cronJobs: [],
586
622
  customResources: []
587
623
  });
588
624
 
@@ -590,51 +626,86 @@ const ClusterContext = React__default.createContext({
590
626
  name: ""
591
627
  });
592
628
 
593
- const kindMappings$1 = {
629
+ const kindMappings$2 = {
594
630
  deployment: "deployment",
595
631
  ingress: "ingress",
596
632
  service: "service",
597
633
  horizontalpodautoscaler: "deployment"
598
634
  };
599
635
  function standardFormatter(options) {
600
- var _a, _b;
636
+ var _a, _b, _c, _d;
601
637
  const result = new URL(options.dashboardUrl.href);
602
- const name = (_a = options.object.metadata) == null ? void 0 : _a.name;
603
- const namespace = (_b = options.object.metadata) == null ? void 0 : _b.namespace;
604
- const validKind = kindMappings$1[options.kind.toLocaleLowerCase("en-US")];
605
- if (namespace) {
606
- 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 += "/";
607
643
  }
608
644
  if (validKind && name && namespace) {
609
645
  result.hash = `/${validKind}/${namespace}/${name}`;
610
646
  } else if (namespace) {
611
647
  result.hash = "/workloads";
612
648
  }
649
+ if (namespace) {
650
+ result.hash += `?namespace=${namespace}`;
651
+ }
613
652
  return result;
614
653
  }
615
654
 
616
- const kindMappings = {
655
+ const kindMappings$1 = {
617
656
  deployment: "apps.deployment",
618
657
  ingress: "networking.k8s.io.ingress",
619
658
  service: "service",
620
659
  horizontalpodautoscaler: "autoscaling.horizontalpodautoscaler"
621
660
  };
622
661
  function rancherFormatter(options) {
623
- var _a, _b;
624
- const result = new URL(options.dashboardUrl.href);
625
- const name = (_a = options.object.metadata) == null ? void 0 : _a.name;
626
- const namespace = (_b = options.object.metadata) == null ? void 0 : _b.namespace;
627
- 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 = "";
628
671
  if (validKind && name && namespace) {
629
- result.pathname += `explorer/${validKind}/${namespace}/${name}`;
672
+ path = `explorer/${validKind}/${namespace}/${name}`;
630
673
  } else if (namespace) {
631
- result.pathname += "explorer/workload";
674
+ path = "explorer/workload";
632
675
  }
633
- return result;
676
+ return new URL(path, basePath);
634
677
  }
635
678
 
636
- function openshiftFormatter(_options) {
637
- 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);
638
709
  }
639
710
 
640
711
  function aksFormatter(_options) {
@@ -676,7 +747,7 @@ function formatClusterLink(options) {
676
747
  object: options.object,
677
748
  kind: options.kind
678
749
  });
679
- return `${url.origin}${url.pathname}${url.hash}${url.search}`;
750
+ return url.toString();
680
751
  }
681
752
 
682
753
  const useDrawerStyles = makeStyles((theme) => createStyles({
@@ -716,7 +787,7 @@ const PodDrawerButton = withStyles({
716
787
  textTransform: "none"
717
788
  }
718
789
  })(Button);
719
- const ErrorPanel = ({cluster, errorMessage}) => /* @__PURE__ */ React__default.createElement(WarningPanel, {
790
+ const ErrorPanel = ({ cluster, errorMessage }) => /* @__PURE__ */ React__default.createElement(WarningPanel, {
720
791
  title: "There was a problem formatting the link to the Kubernetes dashboard",
721
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"}.'`
722
793
  }, errorMessage && /* @__PURE__ */ React__default.createElement(Typography, {
@@ -749,7 +820,7 @@ const KubernetesDrawerContent = ({
749
820
  const [isYaml, setIsYaml] = useState(false);
750
821
  const classes = useDrawerContentStyles();
751
822
  const cluster = useContext(ClusterContext);
752
- const {clusterLink, errorMessage} = tryFormatClusterLink({
823
+ const { clusterLink, errorMessage } = tryFormatClusterLink({
753
824
  dashboardUrl: cluster.dashboardUrl,
754
825
  dashboardApp: cluster.dashboardApp,
755
826
  object,
@@ -878,7 +949,11 @@ const PodDrawer = ({
878
949
  });
879
950
  };
880
951
 
881
- 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 = [
882
957
  {
883
958
  title: "name",
884
959
  highlight: true,
@@ -893,6 +968,12 @@ const columns = [
893
968
  return (_b = (_a = pod.status) == null ? void 0 : _a.phase) != null ? _b : "unknown";
894
969
  }
895
970
  },
971
+ {
972
+ title: "status",
973
+ render: containerStatuses
974
+ }
975
+ ];
976
+ const READY = [
896
977
  {
897
978
  title: "containers ready",
898
979
  align: "center",
@@ -903,13 +984,41 @@ const columns = [
903
984
  align: "center",
904
985
  render: totalRestarts,
905
986
  type: "numeric"
906
- },
907
- {
908
- title: "status",
909
- render: containerStatuses
910
987
  }
911
988
  ];
912
- 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
+ }
913
1022
  const tableStyle = {
914
1023
  minWidth: "0",
915
1024
  width: "100%"
@@ -917,7 +1026,7 @@ const PodsTable = ({pods}) => {
917
1026
  return /* @__PURE__ */ React__default.createElement("div", {
918
1027
  style: tableStyle
919
1028
  }, /* @__PURE__ */ React__default.createElement(Table, {
920
- options: {paging: true, search: false},
1029
+ options: { paging: true, search: false },
921
1030
  data: pods,
922
1031
  columns
923
1032
  }));
@@ -1034,7 +1143,7 @@ const DeploymentSummary = ({
1034
1143
  item: true,
1035
1144
  xs: 1
1036
1145
  }, /* @__PURE__ */ React__default.createElement(Divider, {
1037
- style: {height: "5em"},
1146
+ style: { height: "5em" },
1038
1147
  orientation: "vertical"
1039
1148
  })), hpa && /* @__PURE__ */ React__default.createElement(Grid, {
1040
1149
  item: true,
@@ -1084,7 +1193,7 @@ const DeploymentAccordion = ({
1084
1193
  return podNamesWithErrors.has((_b = (_a = p.metadata) == null ? void 0 : _a.name) != null ? _b : "");
1085
1194
  });
1086
1195
  return /* @__PURE__ */ React__default.createElement(Accordion, {
1087
- TransitionProps: {unmountOnExit: true}
1196
+ TransitionProps: { unmountOnExit: true }
1088
1197
  }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1089
1198
  expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1090
1199
  }, /* @__PURE__ */ React__default.createElement(DeploymentSummary, {
@@ -1093,7 +1202,8 @@ const DeploymentAccordion = ({
1093
1202
  numberOfPodsWithErrors: podsWithErrors.length,
1094
1203
  hpa: matchingHpa
1095
1204
  })), /* @__PURE__ */ React__default.createElement(AccordionDetails, null, /* @__PURE__ */ React__default.createElement(PodsTable, {
1096
- pods: ownedPods
1205
+ pods: ownedPods,
1206
+ extraColumns: [READY_COLUMNS, RESOURCE_COLUMNS]
1097
1207
  })));
1098
1208
  };
1099
1209
  const DeploymentsAccordions = ({}) => {
@@ -1151,7 +1261,7 @@ const IngressDrawer = ({
1151
1261
  }, "Ingress"))));
1152
1262
  };
1153
1263
 
1154
- const IngressSummary = ({ingress}) => {
1264
+ const IngressSummary = ({ ingress }) => {
1155
1265
  return /* @__PURE__ */ React__default.createElement(Grid, {
1156
1266
  container: true,
1157
1267
  direction: "row",
@@ -1166,20 +1276,20 @@ const IngressSummary = ({ingress}) => {
1166
1276
  item: true,
1167
1277
  xs: 1
1168
1278
  }, /* @__PURE__ */ React__default.createElement(Divider, {
1169
- style: {height: "5em"},
1279
+ style: { height: "5em" },
1170
1280
  orientation: "vertical"
1171
1281
  })));
1172
1282
  };
1173
- const IngressCard = ({ingress}) => {
1283
+ const IngressCard = ({ ingress }) => {
1174
1284
  return /* @__PURE__ */ React__default.createElement(StructuredMetadataTable, {
1175
1285
  metadata: {
1176
1286
  ...ingress.spec
1177
1287
  }
1178
1288
  });
1179
1289
  };
1180
- const IngressAccordion = ({ingress}) => {
1290
+ const IngressAccordion = ({ ingress }) => {
1181
1291
  return /* @__PURE__ */ React__default.createElement(Accordion, {
1182
- TransitionProps: {unmountOnExit: true}
1292
+ TransitionProps: { unmountOnExit: true }
1183
1293
  }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1184
1294
  expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1185
1295
  }, /* @__PURE__ */ React__default.createElement(IngressSummary, {
@@ -1234,7 +1344,7 @@ const ServiceDrawer = ({
1234
1344
  }, "Service"))));
1235
1345
  };
1236
1346
 
1237
- const ServiceSummary = ({service}) => {
1347
+ const ServiceSummary = ({ service }) => {
1238
1348
  var _a, _b;
1239
1349
  return /* @__PURE__ */ React__default.createElement(Grid, {
1240
1350
  container: true,
@@ -1250,7 +1360,7 @@ const ServiceSummary = ({service}) => {
1250
1360
  item: true,
1251
1361
  xs: 1
1252
1362
  }, /* @__PURE__ */ React__default.createElement(Divider, {
1253
- style: {height: "5em"},
1363
+ style: { height: "5em" },
1254
1364
  orientation: "vertical"
1255
1365
  })), /* @__PURE__ */ React__default.createElement(Grid, {
1256
1366
  item: true
@@ -1258,7 +1368,7 @@ const ServiceSummary = ({service}) => {
1258
1368
  variant: "subtitle2"
1259
1369
  }, "Type: ", (_b = (_a = service.spec) == null ? void 0 : _a.type) != null ? _b : "?")));
1260
1370
  };
1261
- const ServiceCard = ({service}) => {
1371
+ const ServiceCard = ({ service }) => {
1262
1372
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1263
1373
  const metadata = {};
1264
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) {
@@ -1278,9 +1388,9 @@ const ServiceCard = ({service}) => {
1278
1388
  }
1279
1389
  });
1280
1390
  };
1281
- const ServiceAccordion = ({service}) => {
1391
+ const ServiceAccordion = ({ service }) => {
1282
1392
  return /* @__PURE__ */ React__default.createElement(Accordion, {
1283
- TransitionProps: {unmountOnExit: true}
1393
+ TransitionProps: { unmountOnExit: true }
1284
1394
  }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1285
1395
  expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1286
1396
  }, /* @__PURE__ */ React__default.createElement(ServiceSummary, {
@@ -1305,6 +1415,214 @@ const ServicesAccordions = ({}) => {
1305
1415
  }))));
1306
1416
  };
1307
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
+
1308
1626
  const RolloutDrawer = ({
1309
1627
  rollout,
1310
1628
  expanded
@@ -1404,7 +1722,7 @@ const RolloutSummary = ({
1404
1722
  item: true,
1405
1723
  xs: 1
1406
1724
  }, /* @__PURE__ */ React__default.createElement(Divider, {
1407
- style: {height: "5em"},
1725
+ style: { height: "5em" },
1408
1726
  orientation: "vertical"
1409
1727
  })), hpa && /* @__PURE__ */ React__default.createElement(Grid, {
1410
1728
  item: true,
@@ -1452,7 +1770,7 @@ const RolloutSummary = ({
1452
1770
  }
1453
1771
  }, /* @__PURE__ */ React__default.createElement(PauseIcon, null), /* @__PURE__ */ React__default.createElement(Typography, {
1454
1772
  variant: "subtitle1"
1455
- }, "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, {
1456
1774
  item: true,
1457
1775
  xs: 3
1458
1776
  }, AbortedTitle));
@@ -1473,7 +1791,7 @@ const RolloutAccordion = ({
1473
1791
  const abortedMessage = findAbortedMessage(rollout);
1474
1792
  return /* @__PURE__ */ React__default.createElement(Accordion, {
1475
1793
  defaultExpanded,
1476
- TransitionProps: {unmountOnExit: true}
1794
+ TransitionProps: { unmountOnExit: true }
1477
1795
  }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1478
1796
  expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1479
1797
  }, /* @__PURE__ */ React__default.createElement(RolloutSummary, {
@@ -1482,11 +1800,11 @@ const RolloutAccordion = ({
1482
1800
  numberOfPodsWithErrors: podsWithErrors.length,
1483
1801
  hpa: matchingHpa
1484
1802
  })), /* @__PURE__ */ React__default.createElement(AccordionDetails, null, /* @__PURE__ */ React__default.createElement("div", {
1485
- style: {width: "100%"}
1803
+ style: { width: "100%" }
1486
1804
  }, /* @__PURE__ */ React__default.createElement("div", null, /* @__PURE__ */ React__default.createElement(Typography, {
1487
1805
  variant: "h6"
1488
1806
  }, "Rollout status")), /* @__PURE__ */ React__default.createElement("div", {
1489
- style: {margin: "1rem"}
1807
+ style: { margin: "1rem" }
1490
1808
  }, abortedMessage && /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, AbortedTitle, /* @__PURE__ */ React__default.createElement(Typography, {
1491
1809
  variant: "subtitle2"
1492
1810
  }, abortedMessage)), /* @__PURE__ */ React__default.createElement(StepsProgress, {
@@ -1494,7 +1812,8 @@ const RolloutAccordion = ({
1494
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 : [],
1495
1813
  currentStepIndex
1496
1814
  })), /* @__PURE__ */ React__default.createElement("div", null, /* @__PURE__ */ React__default.createElement(PodsTable, {
1497
- pods: ownedPods
1815
+ pods: ownedPods,
1816
+ extraColumns: [READY_COLUMNS, RESOURCE_COLUMNS]
1498
1817
  })))));
1499
1818
  };
1500
1819
  const RolloutAccordions = ({
@@ -1576,7 +1895,7 @@ const DefaultCustomResourceSummary = ({
1576
1895
  item: true,
1577
1896
  xs: 1
1578
1897
  }, /* @__PURE__ */ React__default.createElement(Divider, {
1579
- style: {height: "5em"},
1898
+ style: { height: "5em" },
1580
1899
  orientation: "vertical"
1581
1900
  })));
1582
1901
  };
@@ -1587,7 +1906,7 @@ const DefaultCustomResourceAccordion = ({
1587
1906
  }) => {
1588
1907
  return /* @__PURE__ */ React__default.createElement(Accordion, {
1589
1908
  defaultExpanded,
1590
- TransitionProps: {unmountOnExit: true}
1909
+ TransitionProps: { unmountOnExit: true }
1591
1910
  }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1592
1911
  expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1593
1912
  }, /* @__PURE__ */ React__default.createElement(DefaultCustomResourceSummary, {
@@ -1677,7 +1996,7 @@ const ClusterSummary = ({
1677
1996
  item: true,
1678
1997
  xs: 1
1679
1998
  }, /* @__PURE__ */ React__default.createElement(Divider, {
1680
- style: {height: "4em"},
1999
+ style: { height: "4em" },
1681
2000
  orientation: "vertical"
1682
2001
  })), /* @__PURE__ */ React__default.createElement(Grid, {
1683
2002
  item: true,
@@ -1692,16 +2011,26 @@ const ClusterSummary = ({
1692
2011
  item: true
1693
2012
  }, numberOfPodsWithErrors > 0 ? /* @__PURE__ */ React__default.createElement(StatusError, null, numberOfPodsWithErrors, " pods with errors") : /* @__PURE__ */ React__default.createElement(StatusOK, null, "No pods with errors"))));
1694
2013
  };
1695
- const Cluster = ({clusterObjects, podsWithErrors}) => {
2014
+ const Cluster = ({ clusterObjects, podsWithErrors }) => {
1696
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());
1697
2024
  return /* @__PURE__ */ React__default.createElement(ClusterContext.Provider, {
1698
2025
  value: clusterObjects.cluster
1699
2026
  }, /* @__PURE__ */ React__default.createElement(GroupedResponsesContext.Provider, {
1700
2027
  value: groupedResponses
2028
+ }, /* @__PURE__ */ React__default.createElement(PodNamesWithMetricsContext.Provider, {
2029
+ value: podNameToMetrics
1701
2030
  }, /* @__PURE__ */ React__default.createElement(PodNamesWithErrorsContext.Provider, {
1702
2031
  value: podsWithErrors
1703
2032
  }, /* @__PURE__ */ React__default.createElement(Accordion, {
1704
- TransitionProps: {unmountOnExit: true}
2033
+ TransitionProps: { unmountOnExit: true }
1705
2034
  }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1706
2035
  expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1707
2036
  }, /* @__PURE__ */ React__default.createElement(ClusterSummary, {
@@ -1719,14 +2048,16 @@ const Cluster = ({clusterObjects, podsWithErrors}) => {
1719
2048
  item: true
1720
2049
  }, /* @__PURE__ */ React__default.createElement(IngressesAccordions, null)), /* @__PURE__ */ React__default.createElement(Grid, {
1721
2050
  item: true
1722
- }, /* @__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))))))));
1723
2054
  };
1724
2055
 
1725
- const KubernetesContent = ({entity}) => {
2056
+ const KubernetesContent = ({ entity }) => {
1726
2057
  var _a;
1727
- const {kubernetesObjects, error} = useKubernetesObjects(entity);
2058
+ const { kubernetesObjects, error } = useKubernetesObjects(entity);
1728
2059
  const clustersWithErrors = (_a = kubernetesObjects == null ? void 0 : kubernetesObjects.items.filter((r) => r.errors.length > 0)) != null ? _a : [];
1729
- const detectedErrors = kubernetesObjects !== void 0 ? detectErrors(kubernetesObjects) : new Map();
2060
+ const detectedErrors = kubernetesObjects !== void 0 ? detectErrors(kubernetesObjects) : /* @__PURE__ */ new Map();
1730
2061
  return /* @__PURE__ */ React__default.createElement(Page, {
1731
2062
  themeId: "tool"
1732
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, {
@@ -1804,12 +2135,12 @@ const isKubernetesAvailable = (entity) => {
1804
2135
  };
1805
2136
  const Router = (_props) => {
1806
2137
  var _a, _b;
1807
- const {entity} = useEntity();
2138
+ const { entity } = useEntity();
1808
2139
  const kubernetesAnnotationValue = (_a = entity.metadata.annotations) == null ? void 0 : _a[KUBERNETES_ANNOTATION];
1809
2140
  const kubernetesLabelSelectorQueryAnnotationValue = (_b = entity.metadata.annotations) == null ? void 0 : _b[KUBERNETES_LABEL_SELECTOR_QUERY_ANNOTATION];
1810
2141
  if (kubernetesAnnotationValue || kubernetesLabelSelectorQueryAnnotationValue) {
1811
2142
  return /* @__PURE__ */ React__default.createElement(Routes, null, /* @__PURE__ */ React__default.createElement(Route, {
1812
- path: `/${rootCatalogKubernetesRouteRef.path}`,
2143
+ path: "/",
1813
2144
  element: /* @__PURE__ */ React__default.createElement(KubernetesContent, {
1814
2145
  entity
1815
2146
  })