@backstage/plugin-kubernetes 0.4.14 → 0.4.18

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
@@ -3,14 +3,15 @@ import * as React from 'react';
3
3
  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
- import { Typography, Chip, makeStyles, createStyles, Button, Drawer, Grid, IconButton, FormControlLabel, Switch, Accordion, AccordionSummary, AccordionDetails, Divider, Stepper, Step, StepLabel } from '@material-ui/core';
7
- import { WarningPanel, SubvalueCell, StatusError, StatusOK, StatusAborted, Button as Button$1, CodeSnippet, StructuredMetadataTable, Table, InfoCard, Page, Content, Progress, MissingAnnotationEmptyState } from '@backstage/core-components';
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';
8
+ import EmptyStateImage from './assets/emptystate.svg';
8
9
  import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
9
10
  import Close from '@material-ui/icons/Close';
10
11
  import OpenInNewIcon from '@material-ui/icons/OpenInNew';
11
12
  import { withStyles } from '@material-ui/core/styles';
12
13
  import jsYaml from 'js-yaml';
13
- import EmptyStateImage from './assets/emptystate.svg';
14
+ import { useInterval } from 'react-use';
14
15
  import lodash from 'lodash';
15
16
  import PauseIcon from '@material-ui/icons/Pause';
16
17
  import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline';
@@ -142,6 +143,7 @@ const kubernetesPlugin = createPlugin({
142
143
  }
143
144
  });
144
145
  const EntityKubernetesContent = kubernetesPlugin.provide(createRoutableExtension({
146
+ name: "EntityKubernetesContent",
145
147
  component: () => Promise.resolve().then(function () { return Router$1; }).then((m) => m.Router),
146
148
  mountPoint: rootCatalogKubernetesRouteRef
147
149
  }));
@@ -171,6 +173,123 @@ const ErrorPanel$1 = ({
171
173
  variant: "body2"
172
174
  }, "Errors: ", errorMessage));
173
175
 
176
+ const columns$1 = [
177
+ {
178
+ title: "cluster",
179
+ width: "15%",
180
+ render: (detectedError) => detectedError.cluster
181
+ },
182
+ {
183
+ title: "kind",
184
+ width: "15%",
185
+ render: (detectedError) => detectedError.kind
186
+ },
187
+ {
188
+ title: "name",
189
+ width: "30%",
190
+ render: (detectedError) => {
191
+ const errorCount = detectedError.names.length;
192
+ if (errorCount === 0) {
193
+ return null;
194
+ }
195
+ const displayName = detectedError.names[0];
196
+ const otherErrorCount = errorCount - 1;
197
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, displayName, " ", otherErrorCount > 0 && /* @__PURE__ */ React.createElement(Chip, {
198
+ label: `+ ${otherErrorCount} other${otherErrorCount > 1 ? "s" : ""}`,
199
+ size: "small"
200
+ }));
201
+ }
202
+ },
203
+ {
204
+ title: "messages",
205
+ width: "40%",
206
+ render: (detectedError) => /* @__PURE__ */ React.createElement(React.Fragment, null, detectedError.message.map((m, i) => /* @__PURE__ */ React.createElement("div", {
207
+ key: i
208
+ }, m)))
209
+ }
210
+ ];
211
+ const sortBySeverity = (a, b) => {
212
+ if (a.severity < b.severity) {
213
+ return 1;
214
+ } else if (b.severity < a.severity) {
215
+ return -1;
216
+ }
217
+ return 0;
218
+ };
219
+ const ErrorEmptyState = () => {
220
+ return /* @__PURE__ */ React.createElement(Grid, {
221
+ container: true,
222
+ justifyContent: "space-around",
223
+ direction: "row",
224
+ alignItems: "center",
225
+ spacing: 2
226
+ }, /* @__PURE__ */ React.createElement(Grid, {
227
+ item: true,
228
+ xs: 4
229
+ }, /* @__PURE__ */ React.createElement(Typography, {
230
+ variant: "h5"
231
+ }, "Nice! There are no errors to report!")), /* @__PURE__ */ React.createElement(Grid, {
232
+ item: true,
233
+ xs: 4
234
+ }, /* @__PURE__ */ React.createElement("img", {
235
+ src: EmptyStateImage,
236
+ alt: "EmptyState",
237
+ "data-testid": "emptyStateImg"
238
+ })));
239
+ };
240
+ const ErrorReporting = ({detectedErrors}) => {
241
+ const errors = Array.from(detectedErrors.values()).flat().sort(sortBySeverity);
242
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, errors.length === 0 ? /* @__PURE__ */ React.createElement(InfoCard, {
243
+ title: "Error Reporting"
244
+ }, /* @__PURE__ */ React.createElement(ErrorEmptyState, null)) : /* @__PURE__ */ React.createElement(Table, {
245
+ title: "Error Reporting",
246
+ data: errors,
247
+ columns: columns$1,
248
+ options: {paging: true, search: false}
249
+ }));
250
+ };
251
+
252
+ const groupResponses = (fetchResponse) => {
253
+ return fetchResponse.reduce((prev, next) => {
254
+ switch (next.type) {
255
+ case "deployments":
256
+ prev.deployments.push(...next.resources);
257
+ break;
258
+ case "pods":
259
+ prev.pods.push(...next.resources);
260
+ break;
261
+ case "replicasets":
262
+ prev.replicaSets.push(...next.resources);
263
+ break;
264
+ case "services":
265
+ prev.services.push(...next.resources);
266
+ break;
267
+ case "configmaps":
268
+ prev.configMaps.push(...next.resources);
269
+ break;
270
+ case "horizontalpodautoscalers":
271
+ prev.horizontalPodAutoscalers.push(...next.resources);
272
+ break;
273
+ case "ingresses":
274
+ prev.ingresses.push(...next.resources);
275
+ break;
276
+ case "customresources":
277
+ prev.customResources.push(...next.resources);
278
+ break;
279
+ }
280
+ return prev;
281
+ }, {
282
+ pods: [],
283
+ replicaSets: [],
284
+ deployments: [],
285
+ services: [],
286
+ configMaps: [],
287
+ horizontalPodAutoscalers: [],
288
+ ingresses: [],
289
+ customResources: []
290
+ });
291
+ };
292
+
174
293
  const imageChips = (pod) => {
175
294
  var _a, _b;
176
295
  const containerStatuses2 = (_b = (_a = pod.status) == null ? void 0 : _a.containerStatuses) != null ? _b : [];
@@ -242,851 +361,760 @@ const renderCondition = (condition) => {
242
361
  return [condition.type, /* @__PURE__ */ React__default.createElement(StatusAborted, null)];
243
362
  };
244
363
 
245
- const useKubernetesObjects = (entity) => {
246
- const kubernetesApi = useApi(kubernetesApiRef);
247
- const kubernetesAuthProvidersApi = useApi(kubernetesAuthProvidersApiRef);
248
- const [kubernetesObjects, setKubernetesObjects] = useState(void 0);
249
- const [error, setError] = useState(void 0);
250
- useEffect(() => {
251
- (async () => {
252
- let clusters = [];
253
- try {
254
- clusters = await kubernetesApi.getClusters();
255
- } catch (e) {
256
- setError(e.message);
257
- return;
258
- }
259
- const authProviders = [
260
- ...new Set(clusters.map((c) => c.authProvider))
261
- ];
262
- let requestBody = {
263
- entity
264
- };
265
- for (const authProviderStr of authProviders) {
266
- try {
267
- requestBody = await kubernetesAuthProvidersApi.decorateRequestBodyForAuth(authProviderStr, requestBody);
268
- } catch (e) {
269
- setError(e.message);
270
- return;
364
+ const detectErrorsInObjects = (objects, kind, clusterName, errorMappers) => {
365
+ var _a, _b;
366
+ const errors = new Map();
367
+ for (const object of objects) {
368
+ for (const errorMapper of errorMappers) {
369
+ if (errorMapper.errorExists(object)) {
370
+ const message = errorMapper.messageAccessor(object);
371
+ const dedupKey = message.join("");
372
+ const value = errors.get(dedupKey);
373
+ const name = (_b = (_a = object.metadata) == null ? void 0 : _a.name) != null ? _b : "unknown";
374
+ if (value !== void 0) {
375
+ value.names.push(name);
376
+ errors.set(dedupKey, value);
377
+ } else {
378
+ errors.set(dedupKey, {
379
+ cluster: clusterName,
380
+ kind,
381
+ names: [name],
382
+ message,
383
+ severity: errorMapper.severity
384
+ });
271
385
  }
272
386
  }
273
- try {
274
- setKubernetesObjects(await kubernetesApi.getObjectsByEntity(requestBody));
275
- } catch (e) {
276
- setError(e.message);
277
- return;
278
- }
279
- })();
280
- }, [entity.metadata.name, kubernetesApi, kubernetesAuthProvidersApi]);
281
- return {
282
- kubernetesObjects,
283
- error
284
- };
285
- };
286
-
287
- const PodNamesWithErrorsContext = React__default.createContext(new Set());
288
-
289
- const GroupedResponsesContext = React__default.createContext({
290
- pods: [],
291
- replicaSets: [],
292
- deployments: [],
293
- services: [],
294
- configMaps: [],
295
- horizontalPodAutoscalers: [],
296
- ingresses: [],
297
- customResources: []
298
- });
299
-
300
- const ClusterContext = React__default.createContext({
301
- name: ""
302
- });
303
-
304
- const kindMappings = {
305
- deployment: "deployment",
306
- ingress: "ingress",
307
- service: "service",
308
- horizontalpodautoscaler: "deployment"
309
- };
310
- function standardFormatter(options) {
311
- var _a, _b;
312
- const result = new URL(options.dashboardUrl.href);
313
- const name = (_a = options.object.metadata) == null ? void 0 : _a.name;
314
- const namespace = (_b = options.object.metadata) == null ? void 0 : _b.namespace;
315
- const validKind = kindMappings[options.kind.toLocaleLowerCase("en-US")];
316
- if (namespace) {
317
- result.searchParams.set("namespace", namespace);
318
- }
319
- if (validKind && name && namespace) {
320
- result.hash = `/${validKind}/${namespace}/${name}`;
321
- } else if (namespace) {
322
- result.hash = "/workloads";
387
+ }
323
388
  }
324
- return result;
325
- }
326
-
327
- function rancherFormatter(_options) {
328
- throw new Error("Rancher formatter is not yet implemented. Please, contribute!");
329
- }
330
-
331
- function openshiftFormatter(_options) {
332
- throw new Error("OpenShift formatter is not yet implemented. Please, contribute!");
333
- }
334
-
335
- function aksFormatter(_options) {
336
- throw new Error("AKS formatter is not yet implemented. Please, contribute!");
337
- }
338
-
339
- function eksFormatter(_options) {
340
- throw new Error("EKS formatter is not yet implemented. Please, contribute!");
341
- }
342
-
343
- function gkeFormatter(_options) {
344
- throw new Error("GKE formatter is not yet implemented. Please, contribute!");
345
- }
346
-
347
- const clusterLinksFormatters = {
348
- standard: standardFormatter,
349
- rancher: rancherFormatter,
350
- openshift: openshiftFormatter,
351
- aks: aksFormatter,
352
- eks: eksFormatter,
353
- gke: gkeFormatter
389
+ return Array.from(errors.values());
354
390
  };
355
- const defaultFormatterName = "standard";
356
-
357
- function formatClusterLink(options) {
358
- if (!options.dashboardUrl) {
359
- return void 0;
360
- }
361
- if (!options.object) {
362
- return options.dashboardUrl;
363
- }
364
- const app = options.dashboardApp || defaultFormatterName;
365
- const formatter = clusterLinksFormatters[app];
366
- if (!formatter) {
367
- throw new Error(`Could not find Kubernetes dashboard app named '${app}'`);
368
- }
369
- const url = formatter({
370
- dashboardUrl: new URL(options.dashboardUrl),
371
- object: options.object,
372
- kind: options.kind
373
- });
374
- return `${url.origin}${url.pathname}${url.hash}${url.search}`;
375
- }
376
391
 
377
- const useDrawerStyles = makeStyles((theme) => createStyles({
378
- paper: {
379
- width: "50%",
380
- justifyContent: "space-between",
381
- padding: theme.spacing(2.5)
382
- }
383
- }));
384
- const useDrawerContentStyles = makeStyles((_) => createStyles({
385
- header: {
386
- display: "flex",
387
- flexDirection: "row",
388
- justifyContent: "space-between"
389
- },
390
- errorMessage: {
391
- marginTop: "1em",
392
- marginBottom: "1em"
393
- },
394
- options: {
395
- display: "flex",
396
- flexDirection: "row",
397
- justifyContent: "space-between"
398
- },
399
- icon: {
400
- fontSize: 20
401
- },
402
- content: {
403
- height: "80%"
404
- }
405
- }));
406
- const PodDrawerButton = withStyles({
407
- root: {
408
- padding: "6px 5px"
409
- },
410
- label: {
411
- textTransform: "none"
412
- }
413
- })(Button);
414
- const ErrorPanel = ({cluster, errorMessage}) => /* @__PURE__ */ React__default.createElement(WarningPanel, {
415
- title: "There was a problem formatting the link to the Kubernetes dashboard",
416
- 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"}.'`
417
- }, errorMessage && /* @__PURE__ */ React__default.createElement(Typography, {
418
- variant: "body2"
419
- }, "Errors: ", errorMessage));
420
- function replaceNullsWithUndefined(someObj) {
421
- const replacer = (_, value) => String(value) === "null" || String(value) === "undefined" ? void 0 : value;
422
- return JSON.parse(JSON.stringify(someObj, replacer));
423
- }
424
- function tryFormatClusterLink(options) {
425
- try {
426
- return {
427
- clusterLink: formatClusterLink(options),
428
- errorMessage: ""
429
- };
430
- } catch (err) {
431
- return {
432
- clusterLink: "",
433
- errorMessage: err.message || err.toString()
434
- };
435
- }
436
- }
437
- const KubernetesDrawerContent = ({
438
- toggleDrawer,
439
- object,
440
- renderObject,
441
- kind
442
- }) => {
443
- var _a, _b;
444
- const [isYaml, setIsYaml] = useState(false);
445
- const classes = useDrawerContentStyles();
446
- const cluster = useContext(ClusterContext);
447
- const {clusterLink, errorMessage} = tryFormatClusterLink({
448
- dashboardUrl: cluster.dashboardUrl,
449
- dashboardApp: cluster.dashboardApp,
450
- object,
451
- kind
452
- });
453
- return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement("div", {
454
- className: classes.header
455
- }, /* @__PURE__ */ React__default.createElement(Grid, {
456
- container: true,
457
- direction: "column",
458
- justifyContent: "flex-start",
459
- alignItems: "flex-start"
460
- }, /* @__PURE__ */ React__default.createElement(Grid, {
461
- item: true
462
- }, /* @__PURE__ */ React__default.createElement(Typography, {
463
- variant: "h5"
464
- }, (_b = (_a = object.metadata) == null ? void 0 : _a.name) != null ? _b : "unknown name")), /* @__PURE__ */ React__default.createElement(Grid, {
465
- item: true
466
- }, /* @__PURE__ */ React__default.createElement(Typography, {
467
- color: "textSecondary",
468
- variant: "body1"
469
- }, kind))), /* @__PURE__ */ React__default.createElement(IconButton, {
470
- key: "dismiss",
471
- title: "Close the drawer",
472
- onClick: (e) => toggleDrawer(e, false),
473
- color: "inherit"
474
- }, /* @__PURE__ */ React__default.createElement(Close, {
475
- className: classes.icon
476
- }))), errorMessage && /* @__PURE__ */ React__default.createElement("div", {
477
- className: classes.errorMessage
478
- }, /* @__PURE__ */ React__default.createElement(ErrorPanel, {
479
- cluster,
480
- errorMessage
481
- })), /* @__PURE__ */ React__default.createElement("div", {
482
- className: classes.options
483
- }, /* @__PURE__ */ React__default.createElement("div", null, clusterLink && /* @__PURE__ */ React__default.createElement(Button$1, {
484
- variant: "outlined",
485
- color: "primary",
486
- size: "small",
487
- to: clusterLink,
488
- endIcon: /* @__PURE__ */ React__default.createElement(OpenInNewIcon, null)
489
- }, "Open Kubernetes Dashboard")), /* @__PURE__ */ React__default.createElement(FormControlLabel, {
490
- control: /* @__PURE__ */ React__default.createElement(Switch, {
491
- checked: isYaml,
492
- onChange: (event) => {
493
- setIsYaml(event.target.checked);
494
- },
495
- name: "YAML"
496
- }),
497
- label: "YAML"
498
- })), /* @__PURE__ */ React__default.createElement("div", {
499
- className: classes.content
500
- }, isYaml && /* @__PURE__ */ React__default.createElement(CodeSnippet, {
501
- language: "yaml",
502
- text: jsYaml.dump(object)
503
- }), !isYaml && /* @__PURE__ */ React__default.createElement(StructuredMetadataTable, {
504
- metadata: renderObject(replaceNullsWithUndefined(object))
505
- })));
506
- };
507
- const KubernetesDrawer = ({
508
- object,
509
- renderObject,
510
- kind,
511
- buttonVariant = "subtitle2",
512
- expanded = false,
513
- children
514
- }) => {
515
- var _a, _b;
516
- const [isOpen, setIsOpen] = useState(expanded);
517
- const classes = useDrawerStyles();
518
- const toggleDrawer = (e, newValue) => {
519
- e.stopPropagation();
520
- setIsOpen(newValue);
521
- };
522
- return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(PodDrawerButton, {
523
- onClick: (e) => toggleDrawer(e, true),
524
- onFocus: (event) => event.stopPropagation()
525
- }, children === void 0 ? /* @__PURE__ */ React__default.createElement(Typography, {
526
- variant: buttonVariant
527
- }, (_b = (_a = object.metadata) == null ? void 0 : _a.name) != null ? _b : "unknown object") : children), /* @__PURE__ */ React__default.createElement(Drawer, {
528
- classes: {
529
- paper: classes.paper
392
+ const podErrorMappers = [
393
+ {
394
+ severity: 5,
395
+ errorExplanation: "status-message",
396
+ errorExists: (pod) => {
397
+ var _a;
398
+ return ((_a = pod.status) == null ? void 0 : _a.message) !== void 0;
530
399
  },
531
- anchor: "right",
532
- open: isOpen,
533
- onClose: (e) => toggleDrawer(e, false),
534
- onClick: (event) => event.stopPropagation()
535
- }, /* @__PURE__ */ React__default.createElement(KubernetesDrawerContent, {
536
- kind,
537
- toggleDrawer,
538
- object,
539
- renderObject
540
- })));
541
- };
542
-
543
- const PodDrawer = ({
544
- pod,
545
- expanded
546
- }) => {
547
- return /* @__PURE__ */ React__default.createElement(KubernetesDrawer, {
548
- object: pod,
549
- expanded,
550
- kind: "Pod",
551
- renderObject: (podObject) => {
552
- var _a, _b, _c, _d, _e, _f, _g;
553
- const phase = (_b = (_a = podObject.status) == null ? void 0 : _a.phase) != null ? _b : "unknown";
554
- const ports = (_e = (_d = (_c = podObject.spec) == null ? void 0 : _c.containers) == null ? void 0 : _d.map((c) => {
555
- return {
556
- [c.name]: c.ports
557
- };
558
- })) != null ? _e : "N/A";
559
- const conditions = ((_g = (_f = podObject.status) == null ? void 0 : _f.conditions) != null ? _g : []).map(renderCondition).reduce((accum, next) => {
560
- accum[next[0]] = next[1];
561
- return accum;
562
- }, {});
563
- return {
564
- images: imageChips(podObject),
565
- phase,
566
- "Containers Ready": containersReady(podObject),
567
- "Total Restarts": totalRestarts(podObject),
568
- "Container Statuses": containerStatuses(podObject),
569
- ...conditions,
570
- "Exposed ports": ports
571
- };
400
+ messageAccessor: (pod) => {
401
+ var _a, _b;
402
+ return [(_b = (_a = pod.status) == null ? void 0 : _a.message) != null ? _b : ""];
572
403
  }
573
- });
574
- };
575
-
576
- const columns$1 = [
404
+ },
577
405
  {
578
- title: "name",
579
- highlight: true,
580
- render: (pod) => /* @__PURE__ */ React__default.createElement(PodDrawer, {
581
- pod
582
- })
406
+ severity: 4,
407
+ errorExplanation: "containers-restarting",
408
+ errorExists: (pod) => {
409
+ return totalRestarts(pod) > 3;
410
+ },
411
+ messageAccessor: (pod) => {
412
+ var _a, _b;
413
+ return ((_b = (_a = pod.status) == null ? void 0 : _a.containerStatuses) != null ? _b : []).filter((cs) => cs.restartCount > 0).map((cs) => `container=${cs.name} restarted ${cs.restartCount} times`);
414
+ }
583
415
  },
584
416
  {
585
- title: "phase",
586
- render: (pod) => {
417
+ severity: 5,
418
+ errorExplanation: "condition-message-present",
419
+ errorExists: (pod) => {
587
420
  var _a, _b;
588
- return (_b = (_a = pod.status) == null ? void 0 : _a.phase) != null ? _b : "unknown";
421
+ return ((_b = (_a = pod.status) == null ? void 0 : _a.conditions) != null ? _b : []).some((c) => c.message !== void 0);
422
+ },
423
+ messageAccessor: (pod) => {
424
+ var _a, _b;
425
+ return ((_b = (_a = pod.status) == null ? void 0 : _a.conditions) != null ? _b : []).filter((c) => c.message !== void 0).map((c) => {
426
+ var _a2;
427
+ return (_a2 = c.message) != null ? _a2 : "";
428
+ });
589
429
  }
590
430
  },
591
431
  {
592
- title: "containers ready",
593
- align: "center",
594
- render: containersReady
432
+ severity: 6,
433
+ errorExplanation: "container-waiting",
434
+ errorExists: (pod) => {
435
+ var _a, _b;
436
+ return ((_b = (_a = pod.status) == null ? void 0 : _a.containerStatuses) != null ? _b : []).some((cs) => {
437
+ var _a2, _b2;
438
+ return ((_b2 = (_a2 = cs.state) == null ? void 0 : _a2.waiting) == null ? void 0 : _b2.message) !== void 0;
439
+ });
440
+ },
441
+ messageAccessor: (pod) => {
442
+ var _a, _b;
443
+ return ((_b = (_a = pod.status) == null ? void 0 : _a.containerStatuses) != null ? _b : []).filter((cs) => {
444
+ var _a2, _b2;
445
+ return ((_b2 = (_a2 = cs.state) == null ? void 0 : _a2.waiting) == null ? void 0 : _b2.message) !== void 0;
446
+ }).map((cs) => {
447
+ var _a2, _b2, _c;
448
+ return (_c = (_b2 = (_a2 = cs.state) == null ? void 0 : _a2.waiting) == null ? void 0 : _b2.message) != null ? _c : "";
449
+ });
450
+ }
595
451
  },
596
452
  {
597
- title: "total restarts",
598
- align: "center",
599
- render: totalRestarts,
600
- type: "numeric"
601
- },
453
+ severity: 4,
454
+ errorExplanation: "container-last-state-error",
455
+ errorExists: (pod) => {
456
+ var _a, _b;
457
+ return ((_b = (_a = pod.status) == null ? void 0 : _a.containerStatuses) != null ? _b : []).some((cs) => {
458
+ var _a2, _b2, _c;
459
+ return ((_c = (_b2 = (_a2 = cs.lastState) == null ? void 0 : _a2.terminated) == null ? void 0 : _b2.reason) != null ? _c : "") === "Error";
460
+ });
461
+ },
462
+ messageAccessor: (pod) => {
463
+ var _a, _b;
464
+ return ((_b = (_a = pod.status) == null ? void 0 : _a.containerStatuses) != null ? _b : []).filter((cs) => {
465
+ var _a2, _b2, _c;
466
+ return ((_c = (_b2 = (_a2 = cs.lastState) == null ? void 0 : _a2.terminated) == null ? void 0 : _b2.reason) != null ? _c : "") === "Error";
467
+ }).map((cs) => {
468
+ var _a2, _b2;
469
+ return `container=${cs.name} exited with error code (${(_b2 = (_a2 = cs.lastState) == null ? void 0 : _a2.terminated) == null ? void 0 : _b2.exitCode})`;
470
+ });
471
+ }
472
+ }
473
+ ];
474
+ const detectErrorsInPods = (pods, clusterName) => detectErrorsInObjects(pods, "Pod", clusterName, podErrorMappers);
475
+
476
+ const deploymentErrorMappers = [
602
477
  {
603
- title: "status",
604
- render: containerStatuses
478
+ severity: 6,
479
+ errorExplanation: "condition-message-present",
480
+ errorExists: (deployment) => {
481
+ var _a, _b;
482
+ return ((_b = (_a = deployment.status) == null ? void 0 : _a.conditions) != null ? _b : []).filter((c) => c.status === "False").some((c) => c.message !== void 0);
483
+ },
484
+ messageAccessor: (deployment) => {
485
+ var _a, _b;
486
+ return ((_b = (_a = deployment.status) == null ? void 0 : _a.conditions) != null ? _b : []).filter((c) => c.status === "False").filter((c) => c.message !== void 0).map((c) => {
487
+ var _a2;
488
+ return (_a2 = c.message) != null ? _a2 : "";
489
+ });
490
+ }
605
491
  }
606
492
  ];
607
- const PodsTable = ({pods}) => {
608
- const tableStyle = {
609
- minWidth: "0",
610
- width: "100%"
611
- };
612
- return /* @__PURE__ */ React__default.createElement("div", {
613
- style: tableStyle
614
- }, /* @__PURE__ */ React__default.createElement(Table, {
615
- options: {paging: true, search: false},
616
- data: pods,
617
- columns: columns$1
618
- }));
619
- };
493
+ const detectErrorsInDeployments = (deployments, clusterName) => detectErrorsInObjects(deployments, "Deployment", clusterName, deploymentErrorMappers);
620
494
 
621
- const DeploymentDrawer = ({
622
- deployment,
623
- expanded
624
- }) => {
625
- var _a, _b;
626
- return /* @__PURE__ */ React__default.createElement(KubernetesDrawer, {
627
- object: deployment,
628
- expanded,
629
- kind: "Deployment",
630
- renderObject: (deploymentObj) => {
631
- var _a2, _b2, _c, _d, _e, _f, _g, _h;
632
- const conditions = ((_b2 = (_a2 = deploymentObj.status) == null ? void 0 : _a2.conditions) != null ? _b2 : []).map(renderCondition).reduce((accum, next) => {
633
- accum[next[0]] = next[1];
634
- return accum;
635
- }, {});
636
- return {
637
- strategy: (_d = (_c = deploymentObj.spec) == null ? void 0 : _c.strategy) != null ? _d : "???",
638
- minReadySeconds: (_f = (_e = deploymentObj.spec) == null ? void 0 : _e.minReadySeconds) != null ? _f : "???",
639
- progressDeadlineSeconds: (_h = (_g = deploymentObj.spec) == null ? void 0 : _g.progressDeadlineSeconds) != null ? _h : "???",
640
- ...conditions
641
- };
495
+ const hpaErrorMappers = [
496
+ {
497
+ severity: 8,
498
+ errorExplanation: "hpa-max-current-replicas",
499
+ errorExists: (hpa) => {
500
+ var _a, _b, _c;
501
+ return ((_b = (_a = hpa.spec) == null ? void 0 : _a.maxReplicas) != null ? _b : -1) === ((_c = hpa.status) == null ? void 0 : _c.currentReplicas);
502
+ },
503
+ messageAccessor: (hpa) => {
504
+ var _a, _b, _c;
505
+ return [
506
+ `Current number of replicas (${(_a = hpa.status) == null ? void 0 : _a.currentReplicas}) is equal to the configured max number of replicas (${(_c = (_b = hpa.spec) == null ? void 0 : _b.maxReplicas) != null ? _c : -1})`
507
+ ];
642
508
  }
643
- }, /* @__PURE__ */ React__default.createElement(Grid, {
644
- container: true,
645
- direction: "column",
646
- justifyContent: "flex-start",
647
- alignItems: "flex-start",
648
- spacing: 0
649
- }, /* @__PURE__ */ React__default.createElement(Grid, {
650
- item: true
651
- }, /* @__PURE__ */ React__default.createElement(Typography, {
652
- variant: "h5"
653
- }, (_b = (_a = deployment.metadata) == null ? void 0 : _a.name) != null ? _b : "unknown object")), /* @__PURE__ */ React__default.createElement(Grid, {
654
- item: true
655
- }, /* @__PURE__ */ React__default.createElement(Typography, {
656
- color: "textSecondary",
657
- variant: "body1"
658
- }, "Deployment"))));
509
+ }
510
+ ];
511
+ const detectErrorsInHpa = (hpas, clusterName) => detectErrorsInObjects(hpas, "HorizontalPodAutoscaler", clusterName, hpaErrorMappers);
512
+
513
+ const detectErrors = (objects) => {
514
+ const errors = new Map();
515
+ for (const clusterResponse of objects.items) {
516
+ let clusterErrors = [];
517
+ const groupedResponses = groupResponses(clusterResponse.resources);
518
+ clusterErrors = clusterErrors.concat(detectErrorsInPods(groupedResponses.pods, clusterResponse.cluster.name));
519
+ clusterErrors = clusterErrors.concat(detectErrorsInDeployments(groupedResponses.deployments, clusterResponse.cluster.name));
520
+ clusterErrors = clusterErrors.concat(detectErrorsInHpa(groupedResponses.horizontalPodAutoscalers, clusterResponse.cluster.name));
521
+ errors.set(clusterResponse.cluster.name, clusterErrors);
522
+ }
523
+ return errors;
659
524
  };
660
525
 
661
- const HorizontalPodAutoscalerDrawer = ({
662
- hpa,
663
- expanded,
664
- children
665
- }) => {
666
- return /* @__PURE__ */ React__default.createElement(KubernetesDrawer, {
667
- kind: "HorizontalPodAutoscaler",
668
- object: hpa,
669
- expanded,
670
- renderObject: (hpaObject) => {
671
- var _a, _b, _c, _d, _e, _f;
672
- return {
673
- targetCPUUtilizationPercentage: (_a = hpaObject.spec) == null ? void 0 : _a.targetCPUUtilizationPercentage,
674
- currentCPUUtilizationPercentage: (_b = hpaObject.status) == null ? void 0 : _b.currentCPUUtilizationPercentage,
675
- minReplicas: (_c = hpaObject.spec) == null ? void 0 : _c.minReplicas,
676
- maxReplicas: (_d = hpaObject.spec) == null ? void 0 : _d.maxReplicas,
677
- currentReplicas: (_e = hpaObject.status) == null ? void 0 : _e.currentReplicas,
678
- desiredReplicas: (_f = hpaObject.status) == null ? void 0 : _f.desiredReplicas
679
- };
526
+ const useKubernetesObjects = (entity, intervalMs = 1e4) => {
527
+ const kubernetesApi = useApi(kubernetesApiRef);
528
+ const kubernetesAuthProvidersApi = useApi(kubernetesAuthProvidersApiRef);
529
+ const [kubernetesObjects, setKubernetesObjects] = useState(void 0);
530
+ const [error, setError] = useState(void 0);
531
+ const getObjects = async () => {
532
+ let clusters = [];
533
+ try {
534
+ clusters = await kubernetesApi.getClusters();
535
+ } catch (e) {
536
+ setError(e.message);
537
+ return;
680
538
  }
681
- }, children);
539
+ const authProviders = [
540
+ ...new Set(clusters.map((c) => c.authProvider))
541
+ ];
542
+ let requestBody = {
543
+ entity
544
+ };
545
+ for (const authProviderStr of authProviders) {
546
+ try {
547
+ requestBody = await kubernetesAuthProvidersApi.decorateRequestBodyForAuth(authProviderStr, requestBody);
548
+ } catch (e) {
549
+ setError(e.message);
550
+ return;
551
+ }
552
+ }
553
+ try {
554
+ setKubernetesObjects(await kubernetesApi.getObjectsByEntity(requestBody));
555
+ } catch (e) {
556
+ setError(e.message);
557
+ return;
558
+ }
559
+ };
560
+ useEffect(() => {
561
+ getObjects();
562
+ }, [entity.metadata.name, kubernetesApi, kubernetesAuthProvidersApi]);
563
+ useInterval(() => {
564
+ getObjects();
565
+ }, intervalMs);
566
+ return {
567
+ kubernetesObjects,
568
+ error
569
+ };
682
570
  };
683
571
 
684
- function getOwnedResources(potentialOwner, possiblyOwned) {
685
- return possiblyOwned.filter((p) => {
686
- var _a, _b, _c;
687
- return (_c = (_b = (_a = p.metadata) == null ? void 0 : _a.ownerReferences) == null ? void 0 : _b.some((o) => {
688
- var _a2;
689
- return o.uid === ((_a2 = potentialOwner.metadata) == null ? void 0 : _a2.uid);
690
- })) != null ? _c : false;
691
- });
572
+ const PodNamesWithErrorsContext = React__default.createContext(new Set());
573
+
574
+ const GroupedResponsesContext = React__default.createContext({
575
+ pods: [],
576
+ replicaSets: [],
577
+ deployments: [],
578
+ services: [],
579
+ configMaps: [],
580
+ horizontalPodAutoscalers: [],
581
+ ingresses: [],
582
+ customResources: []
583
+ });
584
+
585
+ const ClusterContext = React__default.createContext({
586
+ name: ""
587
+ });
588
+
589
+ const kindMappings$1 = {
590
+ deployment: "deployment",
591
+ ingress: "ingress",
592
+ service: "service",
593
+ horizontalpodautoscaler: "deployment"
594
+ };
595
+ function standardFormatter(options) {
596
+ var _a, _b;
597
+ const result = new URL(options.dashboardUrl.href);
598
+ const name = (_a = options.object.metadata) == null ? void 0 : _a.name;
599
+ const namespace = (_b = options.object.metadata) == null ? void 0 : _b.namespace;
600
+ const validKind = kindMappings$1[options.kind.toLocaleLowerCase("en-US")];
601
+ if (namespace) {
602
+ result.searchParams.set("namespace", namespace);
603
+ }
604
+ if (validKind && name && namespace) {
605
+ result.hash = `/${validKind}/${namespace}/${name}`;
606
+ } else if (namespace) {
607
+ result.hash = "/workloads";
608
+ }
609
+ return result;
692
610
  }
693
- const getOwnedPodsThroughReplicaSets = (potentialOwner, replicaSets, pods) => {
694
- return getOwnedResources(potentialOwner, replicaSets.filter((rs) => rs.status && rs.status.replicas > 0)).reduce((accum, rs) => {
695
- return accum.concat(getOwnedResources(rs, pods));
696
- }, []);
611
+
612
+ const kindMappings = {
613
+ deployment: "apps.deployment",
614
+ ingress: "networking.k8s.io.ingress",
615
+ service: "service",
616
+ horizontalpodautoscaler: "autoscaling.horizontalpodautoscaler"
697
617
  };
698
- const getMatchingHpa = (ownerName, ownerKind, hpas) => {
699
- return hpas.find((hpa) => {
700
- var _a, _b, _c, _d, _e, _f;
701
- return ((_c = (_b = (_a = hpa.spec) == null ? void 0 : _a.scaleTargetRef) == null ? void 0 : _b.kind) != null ? _c : "").toLowerCase() === ownerKind.toLowerCase() && ((_f = (_e = (_d = hpa.spec) == null ? void 0 : _d.scaleTargetRef) == null ? void 0 : _e.name) != null ? _f : "") === (ownerName != null ? ownerName : "unknown-deployment");
702
- });
618
+ function rancherFormatter(options) {
619
+ var _a, _b;
620
+ const result = new URL(options.dashboardUrl.href);
621
+ const name = (_a = options.object.metadata) == null ? void 0 : _a.name;
622
+ const namespace = (_b = options.object.metadata) == null ? void 0 : _b.namespace;
623
+ const validKind = kindMappings[options.kind.toLocaleLowerCase("en-US")];
624
+ if (validKind && name && namespace) {
625
+ result.pathname += `explorer/${validKind}/${namespace}/${name}`;
626
+ } else if (namespace) {
627
+ result.pathname += "explorer/workload";
628
+ }
629
+ return result;
630
+ }
631
+
632
+ function openshiftFormatter(_options) {
633
+ throw new Error("OpenShift formatter is not yet implemented. Please, contribute!");
634
+ }
635
+
636
+ function aksFormatter(_options) {
637
+ throw new Error("AKS formatter is not yet implemented. Please, contribute!");
638
+ }
639
+
640
+ function eksFormatter(_options) {
641
+ throw new Error("EKS formatter is not yet implemented. Please, contribute!");
642
+ }
643
+
644
+ function gkeFormatter(_options) {
645
+ throw new Error("GKE formatter is not yet implemented. Please, contribute!");
646
+ }
647
+
648
+ const clusterLinksFormatters = {
649
+ standard: standardFormatter,
650
+ rancher: rancherFormatter,
651
+ openshift: openshiftFormatter,
652
+ aks: aksFormatter,
653
+ eks: eksFormatter,
654
+ gke: gkeFormatter
703
655
  };
656
+ const defaultFormatterName = "standard";
704
657
 
705
- const DeploymentSummary = ({
706
- deployment,
707
- numberOfCurrentPods,
708
- numberOfPodsWithErrors,
709
- hpa
710
- }) => {
711
- var _a, _b, _c, _d, _e, _f, _g, _h;
712
- return /* @__PURE__ */ React__default.createElement(Grid, {
713
- container: true,
714
- direction: "row",
715
- justifyContent: "flex-start",
716
- alignItems: "center"
717
- }, /* @__PURE__ */ React__default.createElement(Grid, {
718
- xs: 3,
719
- item: true
720
- }, /* @__PURE__ */ React__default.createElement(DeploymentDrawer, {
721
- deployment
722
- })), /* @__PURE__ */ React__default.createElement(Grid, {
723
- item: true,
724
- xs: 1
725
- }, /* @__PURE__ */ React__default.createElement(Divider, {
726
- style: {height: "5em"},
727
- orientation: "vertical"
728
- })), hpa && /* @__PURE__ */ React__default.createElement(Grid, {
729
- item: true,
730
- xs: 3
731
- }, /* @__PURE__ */ React__default.createElement(HorizontalPodAutoscalerDrawer, {
732
- hpa
658
+ function formatClusterLink(options) {
659
+ if (!options.dashboardUrl) {
660
+ return void 0;
661
+ }
662
+ if (!options.object) {
663
+ return options.dashboardUrl;
664
+ }
665
+ const app = options.dashboardApp || defaultFormatterName;
666
+ const formatter = clusterLinksFormatters[app];
667
+ if (!formatter) {
668
+ throw new Error(`Could not find Kubernetes dashboard app named '${app}'`);
669
+ }
670
+ const url = formatter({
671
+ dashboardUrl: new URL(options.dashboardUrl),
672
+ object: options.object,
673
+ kind: options.kind
674
+ });
675
+ return `${url.origin}${url.pathname}${url.hash}${url.search}`;
676
+ }
677
+
678
+ const useDrawerStyles = makeStyles((theme) => createStyles({
679
+ paper: {
680
+ width: "50%",
681
+ justifyContent: "space-between",
682
+ padding: theme.spacing(2.5)
683
+ }
684
+ }));
685
+ const useDrawerContentStyles = makeStyles((_) => createStyles({
686
+ header: {
687
+ display: "flex",
688
+ flexDirection: "row",
689
+ justifyContent: "space-between"
690
+ },
691
+ errorMessage: {
692
+ marginTop: "1em",
693
+ marginBottom: "1em"
694
+ },
695
+ options: {
696
+ display: "flex",
697
+ flexDirection: "row",
698
+ justifyContent: "space-between"
699
+ },
700
+ icon: {
701
+ fontSize: 20
702
+ },
703
+ content: {
704
+ height: "80%"
705
+ }
706
+ }));
707
+ const PodDrawerButton = withStyles({
708
+ root: {
709
+ padding: "6px 5px"
710
+ },
711
+ label: {
712
+ textTransform: "none"
713
+ }
714
+ })(Button);
715
+ const ErrorPanel = ({cluster, errorMessage}) => /* @__PURE__ */ React__default.createElement(WarningPanel, {
716
+ title: "There was a problem formatting the link to the Kubernetes dashboard",
717
+ 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"}.'`
718
+ }, errorMessage && /* @__PURE__ */ React__default.createElement(Typography, {
719
+ variant: "body2"
720
+ }, "Errors: ", errorMessage));
721
+ function replaceNullsWithUndefined(someObj) {
722
+ const replacer = (_, value) => String(value) === "null" || String(value) === "undefined" ? void 0 : value;
723
+ return JSON.parse(JSON.stringify(someObj, replacer));
724
+ }
725
+ function tryFormatClusterLink(options) {
726
+ try {
727
+ return {
728
+ clusterLink: formatClusterLink(options),
729
+ errorMessage: ""
730
+ };
731
+ } catch (err) {
732
+ return {
733
+ clusterLink: "",
734
+ errorMessage: err.message || err.toString()
735
+ };
736
+ }
737
+ }
738
+ const KubernetesDrawerContent = ({
739
+ toggleDrawer,
740
+ object,
741
+ renderObject,
742
+ kind
743
+ }) => {
744
+ var _a, _b;
745
+ const [isYaml, setIsYaml] = useState(false);
746
+ const classes = useDrawerContentStyles();
747
+ const cluster = useContext(ClusterContext);
748
+ const {clusterLink, errorMessage} = tryFormatClusterLink({
749
+ dashboardUrl: cluster.dashboardUrl,
750
+ dashboardApp: cluster.dashboardApp,
751
+ object,
752
+ kind
753
+ });
754
+ return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement("div", {
755
+ className: classes.header
733
756
  }, /* @__PURE__ */ React__default.createElement(Grid, {
734
- item: true,
735
757
  container: true,
736
758
  direction: "column",
737
759
  justifyContent: "flex-start",
738
- alignItems: "flex-start",
739
- spacing: 0
760
+ alignItems: "flex-start"
740
761
  }, /* @__PURE__ */ React__default.createElement(Grid, {
741
762
  item: true
742
763
  }, /* @__PURE__ */ React__default.createElement(Typography, {
743
- variant: "subtitle2"
744
- }, "min replicas ", (_b = (_a = hpa.spec) == null ? void 0 : _a.minReplicas) != null ? _b : "?", " / max replicas", " ", (_d = (_c = hpa.spec) == null ? void 0 : _c.maxReplicas) != null ? _d : "?")), /* @__PURE__ */ React__default.createElement(Grid, {
745
- item: true
746
- }, /* @__PURE__ */ React__default.createElement(Typography, {
747
- variant: "subtitle2"
748
- }, "current CPU usage:", " ", (_f = (_e = hpa.status) == null ? void 0 : _e.currentCPUUtilizationPercentage) != null ? _f : "?", "%")), /* @__PURE__ */ React__default.createElement(Grid, {
764
+ variant: "h5"
765
+ }, (_b = (_a = object.metadata) == null ? void 0 : _a.name) != null ? _b : "unknown name")), /* @__PURE__ */ React__default.createElement(Grid, {
749
766
  item: true
750
767
  }, /* @__PURE__ */ React__default.createElement(Typography, {
751
- variant: "subtitle2"
752
- }, "target CPU usage:", " ", (_h = (_g = hpa.spec) == null ? void 0 : _g.targetCPUUtilizationPercentage) != null ? _h : "?", "%"))))), /* @__PURE__ */ React__default.createElement(Grid, {
753
- item: true,
754
- container: true,
755
- xs: 3,
756
- direction: "column",
757
- justifyContent: "flex-start",
758
- alignItems: "flex-start"
759
- }, /* @__PURE__ */ React__default.createElement(Grid, {
760
- item: true
761
- }, /* @__PURE__ */ React__default.createElement(StatusOK, null, numberOfCurrentPods, " pods")), /* @__PURE__ */ React__default.createElement(Grid, {
762
- item: true
763
- }, numberOfPodsWithErrors > 0 ? /* @__PURE__ */ React__default.createElement(StatusError, null, numberOfPodsWithErrors, " pod", numberOfPodsWithErrors > 1 ? "s" : "", " with errors") : /* @__PURE__ */ React__default.createElement(StatusOK, null, "No pods with errors"))));
768
+ color: "textSecondary",
769
+ variant: "body1"
770
+ }, kind))), /* @__PURE__ */ React__default.createElement(IconButton, {
771
+ key: "dismiss",
772
+ title: "Close the drawer",
773
+ onClick: (e) => toggleDrawer(e, false),
774
+ color: "inherit"
775
+ }, /* @__PURE__ */ React__default.createElement(Close, {
776
+ className: classes.icon
777
+ }))), errorMessage && /* @__PURE__ */ React__default.createElement("div", {
778
+ className: classes.errorMessage
779
+ }, /* @__PURE__ */ React__default.createElement(ErrorPanel, {
780
+ cluster,
781
+ errorMessage
782
+ })), /* @__PURE__ */ React__default.createElement("div", {
783
+ className: classes.options
784
+ }, /* @__PURE__ */ React__default.createElement("div", null, clusterLink && /* @__PURE__ */ React__default.createElement(Button$1, {
785
+ variant: "outlined",
786
+ color: "primary",
787
+ size: "small",
788
+ to: clusterLink,
789
+ endIcon: /* @__PURE__ */ React__default.createElement(OpenInNewIcon, null)
790
+ }, "Open Kubernetes Dashboard")), /* @__PURE__ */ React__default.createElement(FormControlLabel, {
791
+ control: /* @__PURE__ */ React__default.createElement(Switch, {
792
+ checked: isYaml,
793
+ onChange: (event) => {
794
+ setIsYaml(event.target.checked);
795
+ },
796
+ name: "YAML"
797
+ }),
798
+ label: "YAML"
799
+ })), /* @__PURE__ */ React__default.createElement("div", {
800
+ className: classes.content
801
+ }, isYaml && /* @__PURE__ */ React__default.createElement(CodeSnippet, {
802
+ language: "yaml",
803
+ text: jsYaml.dump(object)
804
+ }), !isYaml && /* @__PURE__ */ React__default.createElement(StructuredMetadataTable, {
805
+ metadata: renderObject(replaceNullsWithUndefined(object))
806
+ })));
764
807
  };
765
- const DeploymentAccordion = ({
766
- deployment,
767
- ownedPods,
768
- matchingHpa
808
+ const KubernetesDrawer = ({
809
+ object,
810
+ renderObject,
811
+ kind,
812
+ buttonVariant = "subtitle2",
813
+ expanded = false,
814
+ children
769
815
  }) => {
770
- const podNamesWithErrors = useContext(PodNamesWithErrorsContext);
771
- const podsWithErrors = ownedPods.filter((p) => {
772
- var _a, _b;
773
- return podNamesWithErrors.has((_b = (_a = p.metadata) == null ? void 0 : _a.name) != null ? _b : "");
774
- });
775
- return /* @__PURE__ */ React__default.createElement(Accordion, {
776
- TransitionProps: {unmountOnExit: true}
777
- }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
778
- expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
779
- }, /* @__PURE__ */ React__default.createElement(DeploymentSummary, {
780
- deployment,
781
- numberOfCurrentPods: ownedPods.length,
782
- numberOfPodsWithErrors: podsWithErrors.length,
783
- hpa: matchingHpa
784
- })), /* @__PURE__ */ React__default.createElement(AccordionDetails, null, /* @__PURE__ */ React__default.createElement(PodsTable, {
785
- pods: ownedPods
816
+ var _a, _b;
817
+ const [isOpen, setIsOpen] = useState(expanded);
818
+ const classes = useDrawerStyles();
819
+ const toggleDrawer = (e, newValue) => {
820
+ e.stopPropagation();
821
+ setIsOpen(newValue);
822
+ };
823
+ return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(PodDrawerButton, {
824
+ onClick: (e) => toggleDrawer(e, true),
825
+ onFocus: (event) => event.stopPropagation()
826
+ }, children === void 0 ? /* @__PURE__ */ React__default.createElement(Typography, {
827
+ variant: buttonVariant
828
+ }, (_b = (_a = object.metadata) == null ? void 0 : _a.name) != null ? _b : "unknown object") : children), /* @__PURE__ */ React__default.createElement(Drawer, {
829
+ classes: {
830
+ paper: classes.paper
831
+ },
832
+ anchor: "right",
833
+ open: isOpen,
834
+ onClose: (e) => toggleDrawer(e, false),
835
+ onClick: (event) => event.stopPropagation()
836
+ }, /* @__PURE__ */ React__default.createElement(KubernetesDrawerContent, {
837
+ kind,
838
+ toggleDrawer,
839
+ object,
840
+ renderObject
786
841
  })));
787
842
  };
788
- const DeploymentsAccordions = ({}) => {
789
- const groupedResponses = useContext(GroupedResponsesContext);
790
- return /* @__PURE__ */ React__default.createElement(Grid, {
791
- container: true,
792
- direction: "column",
793
- justifyContent: "flex-start",
794
- alignItems: "flex-start"
795
- }, groupedResponses.deployments.map((deployment, i) => {
796
- var _a;
797
- return /* @__PURE__ */ React__default.createElement(Grid, {
798
- container: true,
799
- item: true,
800
- key: i,
801
- xs: true
802
- }, /* @__PURE__ */ React__default.createElement(Grid, {
803
- item: true,
804
- xs: true
805
- }, /* @__PURE__ */ React__default.createElement(DeploymentAccordion, {
806
- matchingHpa: getMatchingHpa((_a = deployment.metadata) == null ? void 0 : _a.name, "deployment", groupedResponses.horizontalPodAutoscalers),
807
- ownedPods: getOwnedPodsThroughReplicaSets(deployment, groupedResponses.replicaSets, groupedResponses.pods),
808
- deployment
809
- })));
810
- }));
811
- };
812
843
 
813
- const columns = [
814
- {
815
- title: "cluster",
816
- width: "15%",
817
- render: (detectedError) => detectedError.cluster
818
- },
819
- {
820
- title: "kind",
821
- width: "15%",
822
- render: (detectedError) => detectedError.kind
823
- },
824
- {
825
- title: "name",
826
- width: "30%",
827
- render: (detectedError) => {
828
- const errorCount = detectedError.names.length;
829
- if (errorCount === 0) {
830
- return null;
831
- }
832
- const displayName = detectedError.names[0];
833
- const otherErrorCount = errorCount - 1;
834
- return /* @__PURE__ */ React.createElement(React.Fragment, null, displayName, " ", otherErrorCount > 0 && /* @__PURE__ */ React.createElement(Chip, {
835
- label: `+ ${otherErrorCount} other${otherErrorCount > 1 ? "s" : ""}`,
836
- size: "small"
837
- }));
838
- }
839
- },
840
- {
841
- title: "messages",
842
- width: "40%",
843
- render: (detectedError) => /* @__PURE__ */ React.createElement(React.Fragment, null, detectedError.message.map((m, i) => /* @__PURE__ */ React.createElement("div", {
844
- key: i
845
- }, m)))
846
- }
847
- ];
848
- const sortBySeverity = (a, b) => {
849
- if (a.severity < b.severity) {
850
- return 1;
851
- } else if (b.severity < a.severity) {
852
- return -1;
853
- }
854
- return 0;
855
- };
856
- const ErrorEmptyState = () => {
857
- return /* @__PURE__ */ React.createElement(Grid, {
858
- container: true,
859
- justifyContent: "space-around",
860
- direction: "row",
861
- alignItems: "center",
862
- spacing: 2
863
- }, /* @__PURE__ */ React.createElement(Grid, {
864
- item: true,
865
- xs: 4
866
- }, /* @__PURE__ */ React.createElement(Typography, {
867
- variant: "h5"
868
- }, "Nice! There are no errors to report!")), /* @__PURE__ */ React.createElement(Grid, {
869
- item: true,
870
- xs: 4
871
- }, /* @__PURE__ */ React.createElement("img", {
872
- src: EmptyStateImage,
873
- alt: "EmptyState",
874
- "data-testid": "emptyStateImg"
875
- })));
876
- };
877
- const ErrorReporting = ({detectedErrors}) => {
878
- const errors = Array.from(detectedErrors.values()).flat().sort(sortBySeverity);
879
- return /* @__PURE__ */ React.createElement(React.Fragment, null, errors.length === 0 ? /* @__PURE__ */ React.createElement(InfoCard, {
880
- title: "Error Reporting"
881
- }, /* @__PURE__ */ React.createElement(ErrorEmptyState, null)) : /* @__PURE__ */ React.createElement(Table, {
882
- title: "Error Reporting",
883
- data: errors,
884
- columns,
885
- options: {paging: true, search: false}
886
- }));
887
- };
888
-
889
- const groupResponses = (fetchResponse) => {
890
- return fetchResponse.reduce((prev, next) => {
891
- switch (next.type) {
892
- case "deployments":
893
- prev.deployments.push(...next.resources);
894
- break;
895
- case "pods":
896
- prev.pods.push(...next.resources);
897
- break;
898
- case "replicasets":
899
- prev.replicaSets.push(...next.resources);
900
- break;
901
- case "services":
902
- prev.services.push(...next.resources);
903
- break;
904
- case "configmaps":
905
- prev.configMaps.push(...next.resources);
906
- break;
907
- case "horizontalpodautoscalers":
908
- prev.horizontalPodAutoscalers.push(...next.resources);
909
- break;
910
- case "ingresses":
911
- prev.ingresses.push(...next.resources);
912
- break;
913
- case "customresources":
914
- prev.customResources.push(...next.resources);
915
- break;
844
+ const PodDrawer = ({
845
+ pod,
846
+ expanded
847
+ }) => {
848
+ return /* @__PURE__ */ React__default.createElement(KubernetesDrawer, {
849
+ object: pod,
850
+ expanded,
851
+ kind: "Pod",
852
+ renderObject: (podObject) => {
853
+ var _a, _b, _c, _d, _e, _f, _g;
854
+ const phase = (_b = (_a = podObject.status) == null ? void 0 : _a.phase) != null ? _b : "unknown";
855
+ const ports = (_e = (_d = (_c = podObject.spec) == null ? void 0 : _c.containers) == null ? void 0 : _d.map((c) => {
856
+ return {
857
+ [c.name]: c.ports
858
+ };
859
+ })) != null ? _e : "N/A";
860
+ const conditions = ((_g = (_f = podObject.status) == null ? void 0 : _f.conditions) != null ? _g : []).map(renderCondition).reduce((accum, next) => {
861
+ accum[next[0]] = next[1];
862
+ return accum;
863
+ }, {});
864
+ return {
865
+ images: imageChips(podObject),
866
+ phase,
867
+ "Containers Ready": containersReady(podObject),
868
+ "Total Restarts": totalRestarts(podObject),
869
+ "Container Statuses": containerStatuses(podObject),
870
+ ...conditions,
871
+ "Exposed ports": ports
872
+ };
916
873
  }
917
- return prev;
918
- }, {
919
- pods: [],
920
- replicaSets: [],
921
- deployments: [],
922
- services: [],
923
- configMaps: [],
924
- horizontalPodAutoscalers: [],
925
- ingresses: [],
926
- customResources: []
927
874
  });
928
875
  };
929
876
 
930
- const detectErrorsInObjects = (objects, kind, clusterName, errorMappers) => {
931
- var _a, _b;
932
- const errors = new Map();
933
- for (const object of objects) {
934
- for (const errorMapper of errorMappers) {
935
- if (errorMapper.errorExists(object)) {
936
- const message = errorMapper.messageAccessor(object);
937
- const dedupKey = message.join("");
938
- const value = errors.get(dedupKey);
939
- const name = (_b = (_a = object.metadata) == null ? void 0 : _a.name) != null ? _b : "unknown";
940
- if (value !== void 0) {
941
- value.names.push(name);
942
- errors.set(dedupKey, value);
943
- } else {
944
- errors.set(dedupKey, {
945
- cluster: clusterName,
946
- kind,
947
- names: [name],
948
- message,
949
- severity: errorMapper.severity
950
- });
951
- }
952
- }
953
- }
954
- }
955
- return Array.from(errors.values());
956
- };
957
-
958
- const podErrorMappers = [
959
- {
960
- severity: 5,
961
- errorExplanation: "status-message",
962
- errorExists: (pod) => {
963
- var _a;
964
- return ((_a = pod.status) == null ? void 0 : _a.message) !== void 0;
965
- },
966
- messageAccessor: (pod) => {
967
- var _a, _b;
968
- return [(_b = (_a = pod.status) == null ? void 0 : _a.message) != null ? _b : ""];
969
- }
970
- },
971
- {
972
- severity: 4,
973
- errorExplanation: "containers-restarting",
974
- errorExists: (pod) => {
975
- return totalRestarts(pod) > 3;
976
- },
977
- messageAccessor: (pod) => {
978
- var _a, _b;
979
- return ((_b = (_a = pod.status) == null ? void 0 : _a.containerStatuses) != null ? _b : []).filter((cs) => cs.restartCount > 0).map((cs) => `container=${cs.name} restarted ${cs.restartCount} times`);
980
- }
981
- },
982
- {
983
- severity: 5,
984
- errorExplanation: "condition-message-present",
985
- errorExists: (pod) => {
986
- var _a, _b;
987
- return ((_b = (_a = pod.status) == null ? void 0 : _a.conditions) != null ? _b : []).some((c) => c.message !== void 0);
988
- },
989
- messageAccessor: (pod) => {
990
- var _a, _b;
991
- return ((_b = (_a = pod.status) == null ? void 0 : _a.conditions) != null ? _b : []).filter((c) => c.message !== void 0).map((c) => {
992
- var _a2;
993
- return (_a2 = c.message) != null ? _a2 : "";
994
- });
995
- }
996
- },
997
- {
998
- severity: 6,
999
- errorExplanation: "container-waiting",
1000
- errorExists: (pod) => {
1001
- var _a, _b;
1002
- return ((_b = (_a = pod.status) == null ? void 0 : _a.containerStatuses) != null ? _b : []).some((cs) => {
1003
- var _a2, _b2;
1004
- return ((_b2 = (_a2 = cs.state) == null ? void 0 : _a2.waiting) == null ? void 0 : _b2.message) !== void 0;
1005
- });
1006
- },
1007
- messageAccessor: (pod) => {
1008
- var _a, _b;
1009
- return ((_b = (_a = pod.status) == null ? void 0 : _a.containerStatuses) != null ? _b : []).filter((cs) => {
1010
- var _a2, _b2;
1011
- return ((_b2 = (_a2 = cs.state) == null ? void 0 : _a2.waiting) == null ? void 0 : _b2.message) !== void 0;
1012
- }).map((cs) => {
1013
- var _a2, _b2, _c;
1014
- return (_c = (_b2 = (_a2 = cs.state) == null ? void 0 : _a2.waiting) == null ? void 0 : _b2.message) != null ? _c : "";
1015
- });
1016
- }
1017
- },
1018
- {
1019
- severity: 4,
1020
- errorExplanation: "container-last-state-error",
1021
- errorExists: (pod) => {
1022
- var _a, _b;
1023
- return ((_b = (_a = pod.status) == null ? void 0 : _a.containerStatuses) != null ? _b : []).some((cs) => {
1024
- var _a2, _b2, _c;
1025
- return ((_c = (_b2 = (_a2 = cs.lastState) == null ? void 0 : _a2.terminated) == null ? void 0 : _b2.reason) != null ? _c : "") === "Error";
1026
- });
1027
- },
1028
- messageAccessor: (pod) => {
1029
- var _a, _b;
1030
- return ((_b = (_a = pod.status) == null ? void 0 : _a.containerStatuses) != null ? _b : []).filter((cs) => {
1031
- var _a2, _b2, _c;
1032
- return ((_c = (_b2 = (_a2 = cs.lastState) == null ? void 0 : _a2.terminated) == null ? void 0 : _b2.reason) != null ? _c : "") === "Error";
1033
- }).map((cs) => {
1034
- var _a2, _b2;
1035
- return `container=${cs.name} exited with error code (${(_b2 = (_a2 = cs.lastState) == null ? void 0 : _a2.terminated) == null ? void 0 : _b2.exitCode})`;
1036
- });
1037
- }
1038
- }
1039
- ];
1040
- const detectErrorsInPods = (pods, clusterName) => detectErrorsInObjects(pods, "Pod", clusterName, podErrorMappers);
1041
-
1042
- const deploymentErrorMappers = [
877
+ const columns = [
1043
878
  {
1044
- severity: 6,
1045
- errorExplanation: "condition-message-present",
1046
- errorExists: (deployment) => {
1047
- var _a, _b;
1048
- return ((_b = (_a = deployment.status) == null ? void 0 : _a.conditions) != null ? _b : []).filter((c) => c.status === "False").some((c) => c.message !== void 0);
1049
- },
1050
- messageAccessor: (deployment) => {
879
+ title: "name",
880
+ highlight: true,
881
+ render: (pod) => /* @__PURE__ */ React__default.createElement(PodDrawer, {
882
+ pod
883
+ })
884
+ },
885
+ {
886
+ title: "phase",
887
+ render: (pod) => {
1051
888
  var _a, _b;
1052
- return ((_b = (_a = deployment.status) == null ? void 0 : _a.conditions) != null ? _b : []).filter((c) => c.status === "False").filter((c) => c.message !== void 0).map((c) => {
1053
- var _a2;
1054
- return (_a2 = c.message) != null ? _a2 : "";
1055
- });
889
+ return (_b = (_a = pod.status) == null ? void 0 : _a.phase) != null ? _b : "unknown";
1056
890
  }
891
+ },
892
+ {
893
+ title: "containers ready",
894
+ align: "center",
895
+ render: containersReady
896
+ },
897
+ {
898
+ title: "total restarts",
899
+ align: "center",
900
+ render: totalRestarts,
901
+ type: "numeric"
902
+ },
903
+ {
904
+ title: "status",
905
+ render: containerStatuses
1057
906
  }
1058
907
  ];
1059
- const detectErrorsInDeployments = (deployments, clusterName) => detectErrorsInObjects(deployments, "Deployment", clusterName, deploymentErrorMappers);
908
+ const PodsTable = ({pods}) => {
909
+ const tableStyle = {
910
+ minWidth: "0",
911
+ width: "100%"
912
+ };
913
+ return /* @__PURE__ */ React__default.createElement("div", {
914
+ style: tableStyle
915
+ }, /* @__PURE__ */ React__default.createElement(Table, {
916
+ options: {paging: true, search: false},
917
+ data: pods,
918
+ columns
919
+ }));
920
+ };
1060
921
 
1061
- const hpaErrorMappers = [
1062
- {
1063
- severity: 8,
1064
- errorExplanation: "hpa-max-current-replicas",
1065
- errorExists: (hpa) => {
1066
- var _a, _b, _c;
1067
- return ((_b = (_a = hpa.spec) == null ? void 0 : _a.maxReplicas) != null ? _b : -1) === ((_c = hpa.status) == null ? void 0 : _c.currentReplicas);
1068
- },
1069
- messageAccessor: (hpa) => {
1070
- var _a, _b, _c;
1071
- return [
1072
- `Current number of replicas (${(_a = hpa.status) == null ? void 0 : _a.currentReplicas}) is equal to the configured max number of replicas (${(_c = (_b = hpa.spec) == null ? void 0 : _b.maxReplicas) != null ? _c : -1})`
1073
- ];
922
+ const DeploymentDrawer = ({
923
+ deployment,
924
+ expanded
925
+ }) => {
926
+ var _a, _b, _c;
927
+ const namespace = (_a = deployment.metadata) == null ? void 0 : _a.namespace;
928
+ return /* @__PURE__ */ React__default.createElement(KubernetesDrawer, {
929
+ object: deployment,
930
+ expanded,
931
+ kind: "Deployment",
932
+ renderObject: (deploymentObj) => {
933
+ var _a2, _b2, _c2, _d, _e, _f, _g, _h;
934
+ const conditions = ((_b2 = (_a2 = deploymentObj.status) == null ? void 0 : _a2.conditions) != null ? _b2 : []).map(renderCondition).reduce((accum, next) => {
935
+ accum[next[0]] = next[1];
936
+ return accum;
937
+ }, {});
938
+ return {
939
+ strategy: (_d = (_c2 = deploymentObj.spec) == null ? void 0 : _c2.strategy) != null ? _d : "???",
940
+ minReadySeconds: (_f = (_e = deploymentObj.spec) == null ? void 0 : _e.minReadySeconds) != null ? _f : "???",
941
+ progressDeadlineSeconds: (_h = (_g = deploymentObj.spec) == null ? void 0 : _g.progressDeadlineSeconds) != null ? _h : "???",
942
+ ...conditions
943
+ };
1074
944
  }
1075
- }
1076
- ];
1077
- const detectErrorsInHpa = (hpas, clusterName) => detectErrorsInObjects(hpas, "HorizontalPodAutoscaler", clusterName, hpaErrorMappers);
945
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
946
+ container: true,
947
+ direction: "column",
948
+ justifyContent: "flex-start",
949
+ alignItems: "flex-start",
950
+ spacing: 0
951
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
952
+ item: true
953
+ }, /* @__PURE__ */ React__default.createElement(Typography, {
954
+ variant: "h5"
955
+ }, (_c = (_b = deployment.metadata) == null ? void 0 : _b.name) != null ? _c : "unknown object")), /* @__PURE__ */ React__default.createElement(Grid, {
956
+ item: true
957
+ }, /* @__PURE__ */ React__default.createElement(Typography, {
958
+ color: "textSecondary",
959
+ variant: "body1"
960
+ }, "Deployment")), namespace && /* @__PURE__ */ React__default.createElement(Grid, {
961
+ item: true
962
+ }, /* @__PURE__ */ React__default.createElement(Chip, {
963
+ size: "small",
964
+ label: `namespace: ${namespace}`
965
+ }))));
966
+ };
1078
967
 
1079
- const detectErrors = (objects) => {
1080
- const errors = new Map();
1081
- for (const clusterResponse of objects.items) {
1082
- let clusterErrors = [];
1083
- const groupedResponses = groupResponses(clusterResponse.resources);
1084
- clusterErrors = clusterErrors.concat(detectErrorsInPods(groupedResponses.pods, clusterResponse.cluster.name));
1085
- clusterErrors = clusterErrors.concat(detectErrorsInDeployments(groupedResponses.deployments, clusterResponse.cluster.name));
1086
- clusterErrors = clusterErrors.concat(detectErrorsInHpa(groupedResponses.horizontalPodAutoscalers, clusterResponse.cluster.name));
1087
- errors.set(clusterResponse.cluster.name, clusterErrors);
1088
- }
1089
- return errors;
968
+ const HorizontalPodAutoscalerDrawer = ({
969
+ hpa,
970
+ expanded,
971
+ children
972
+ }) => {
973
+ return /* @__PURE__ */ React__default.createElement(KubernetesDrawer, {
974
+ kind: "HorizontalPodAutoscaler",
975
+ object: hpa,
976
+ expanded,
977
+ renderObject: (hpaObject) => {
978
+ var _a, _b, _c, _d, _e, _f;
979
+ return {
980
+ targetCPUUtilizationPercentage: (_a = hpaObject.spec) == null ? void 0 : _a.targetCPUUtilizationPercentage,
981
+ currentCPUUtilizationPercentage: (_b = hpaObject.status) == null ? void 0 : _b.currentCPUUtilizationPercentage,
982
+ minReplicas: (_c = hpaObject.spec) == null ? void 0 : _c.minReplicas,
983
+ maxReplicas: (_d = hpaObject.spec) == null ? void 0 : _d.maxReplicas,
984
+ currentReplicas: (_e = hpaObject.status) == null ? void 0 : _e.currentReplicas,
985
+ desiredReplicas: (_f = hpaObject.status) == null ? void 0 : _f.desiredReplicas
986
+ };
987
+ }
988
+ }, children);
989
+ };
990
+
991
+ function getOwnedResources(potentialOwner, possiblyOwned) {
992
+ return possiblyOwned.filter((p) => {
993
+ var _a, _b, _c;
994
+ return (_c = (_b = (_a = p.metadata) == null ? void 0 : _a.ownerReferences) == null ? void 0 : _b.some((o) => {
995
+ var _a2;
996
+ return o.uid === ((_a2 = potentialOwner.metadata) == null ? void 0 : _a2.uid);
997
+ })) != null ? _c : false;
998
+ });
999
+ }
1000
+ const getOwnedPodsThroughReplicaSets = (potentialOwner, replicaSets, pods) => {
1001
+ return getOwnedResources(potentialOwner, replicaSets.filter((rs) => rs.status && rs.status.replicas > 0)).reduce((accum, rs) => {
1002
+ return accum.concat(getOwnedResources(rs, pods));
1003
+ }, []);
1004
+ };
1005
+ const getMatchingHpa = (ownerName, ownerKind, hpas) => {
1006
+ return hpas.find((hpa) => {
1007
+ var _a, _b, _c, _d, _e, _f;
1008
+ return ((_c = (_b = (_a = hpa.spec) == null ? void 0 : _a.scaleTargetRef) == null ? void 0 : _b.kind) != null ? _c : "").toLocaleLowerCase("en-US") === ownerKind.toLocaleLowerCase("en-US") && ((_f = (_e = (_d = hpa.spec) == null ? void 0 : _d.scaleTargetRef) == null ? void 0 : _e.name) != null ? _f : "") === (ownerName != null ? ownerName : "unknown-deployment");
1009
+ });
1010
+ };
1011
+
1012
+ const DeploymentSummary = ({
1013
+ deployment,
1014
+ numberOfCurrentPods,
1015
+ numberOfPodsWithErrors,
1016
+ hpa
1017
+ }) => {
1018
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1019
+ return /* @__PURE__ */ React__default.createElement(Grid, {
1020
+ container: true,
1021
+ direction: "row",
1022
+ justifyContent: "flex-start",
1023
+ alignItems: "center"
1024
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1025
+ xs: 3,
1026
+ item: true
1027
+ }, /* @__PURE__ */ React__default.createElement(DeploymentDrawer, {
1028
+ deployment
1029
+ })), /* @__PURE__ */ React__default.createElement(Grid, {
1030
+ item: true,
1031
+ xs: 1
1032
+ }, /* @__PURE__ */ React__default.createElement(Divider, {
1033
+ style: {height: "5em"},
1034
+ orientation: "vertical"
1035
+ })), hpa && /* @__PURE__ */ React__default.createElement(Grid, {
1036
+ item: true,
1037
+ xs: 3
1038
+ }, /* @__PURE__ */ React__default.createElement(HorizontalPodAutoscalerDrawer, {
1039
+ hpa
1040
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1041
+ item: true,
1042
+ container: true,
1043
+ direction: "column",
1044
+ justifyContent: "flex-start",
1045
+ alignItems: "flex-start",
1046
+ spacing: 0
1047
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1048
+ item: true
1049
+ }, /* @__PURE__ */ React__default.createElement(Typography, {
1050
+ variant: "subtitle2"
1051
+ }, "min replicas ", (_b = (_a = hpa.spec) == null ? void 0 : _a.minReplicas) != null ? _b : "?", " / max replicas", " ", (_d = (_c = hpa.spec) == null ? void 0 : _c.maxReplicas) != null ? _d : "?")), /* @__PURE__ */ React__default.createElement(Grid, {
1052
+ item: true
1053
+ }, /* @__PURE__ */ React__default.createElement(Typography, {
1054
+ variant: "subtitle2"
1055
+ }, "current CPU usage:", " ", (_f = (_e = hpa.status) == null ? void 0 : _e.currentCPUUtilizationPercentage) != null ? _f : "?", "%")), /* @__PURE__ */ React__default.createElement(Grid, {
1056
+ item: true
1057
+ }, /* @__PURE__ */ React__default.createElement(Typography, {
1058
+ variant: "subtitle2"
1059
+ }, "target CPU usage:", " ", (_h = (_g = hpa.spec) == null ? void 0 : _g.targetCPUUtilizationPercentage) != null ? _h : "?", "%"))))), /* @__PURE__ */ React__default.createElement(Grid, {
1060
+ item: true,
1061
+ container: true,
1062
+ xs: 3,
1063
+ direction: "column",
1064
+ justifyContent: "flex-start",
1065
+ alignItems: "flex-start"
1066
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1067
+ item: true
1068
+ }, /* @__PURE__ */ React__default.createElement(StatusOK, null, numberOfCurrentPods, " pods")), /* @__PURE__ */ React__default.createElement(Grid, {
1069
+ item: true
1070
+ }, numberOfPodsWithErrors > 0 ? /* @__PURE__ */ React__default.createElement(StatusError, null, numberOfPodsWithErrors, " pod", numberOfPodsWithErrors > 1 ? "s" : "", " with errors") : /* @__PURE__ */ React__default.createElement(StatusOK, null, "No pods with errors"))));
1071
+ };
1072
+ const DeploymentAccordion = ({
1073
+ deployment,
1074
+ ownedPods,
1075
+ matchingHpa
1076
+ }) => {
1077
+ const podNamesWithErrors = useContext(PodNamesWithErrorsContext);
1078
+ const podsWithErrors = ownedPods.filter((p) => {
1079
+ var _a, _b;
1080
+ return podNamesWithErrors.has((_b = (_a = p.metadata) == null ? void 0 : _a.name) != null ? _b : "");
1081
+ });
1082
+ return /* @__PURE__ */ React__default.createElement(Accordion, {
1083
+ TransitionProps: {unmountOnExit: true}
1084
+ }, /* @__PURE__ */ React__default.createElement(AccordionSummary, {
1085
+ expandIcon: /* @__PURE__ */ React__default.createElement(ExpandMoreIcon, null)
1086
+ }, /* @__PURE__ */ React__default.createElement(DeploymentSummary, {
1087
+ deployment,
1088
+ numberOfCurrentPods: ownedPods.length,
1089
+ numberOfPodsWithErrors: podsWithErrors.length,
1090
+ hpa: matchingHpa
1091
+ })), /* @__PURE__ */ React__default.createElement(AccordionDetails, null, /* @__PURE__ */ React__default.createElement(PodsTable, {
1092
+ pods: ownedPods
1093
+ })));
1094
+ };
1095
+ const DeploymentsAccordions = ({}) => {
1096
+ const groupedResponses = useContext(GroupedResponsesContext);
1097
+ return /* @__PURE__ */ React__default.createElement(Grid, {
1098
+ container: true,
1099
+ direction: "column",
1100
+ justifyContent: "flex-start",
1101
+ alignItems: "flex-start"
1102
+ }, groupedResponses.deployments.map((deployment, i) => {
1103
+ var _a;
1104
+ return /* @__PURE__ */ React__default.createElement(Grid, {
1105
+ container: true,
1106
+ item: true,
1107
+ key: i,
1108
+ xs: true
1109
+ }, /* @__PURE__ */ React__default.createElement(Grid, {
1110
+ item: true,
1111
+ xs: true
1112
+ }, /* @__PURE__ */ React__default.createElement(DeploymentAccordion, {
1113
+ matchingHpa: getMatchingHpa((_a = deployment.metadata) == null ? void 0 : _a.name, "deployment", groupedResponses.horizontalPodAutoscalers),
1114
+ ownedPods: getOwnedPodsThroughReplicaSets(deployment, groupedResponses.replicaSets, groupedResponses.pods),
1115
+ deployment
1116
+ })));
1117
+ }));
1090
1118
  };
1091
1119
 
1092
1120
  const IngressDrawer = ({
@@ -1689,6 +1717,7 @@ const Cluster = ({clusterObjects, podsWithErrors}) => {
1689
1717
  item: true
1690
1718
  }, /* @__PURE__ */ React__default.createElement(ServicesAccordions, null))))))));
1691
1719
  };
1720
+
1692
1721
  const KubernetesContent = ({entity}) => {
1693
1722
  var _a;
1694
1723
  const {kubernetesObjects, error} = useKubernetesObjects(entity);
@@ -1765,6 +1794,10 @@ const KubernetesContent = ({entity}) => {
1765
1794
 
1766
1795
  const KUBERNETES_ANNOTATION = "backstage.io/kubernetes-id";
1767
1796
  const KUBERNETES_LABEL_SELECTOR_QUERY_ANNOTATION = "backstage.io/kubernetes-label-selector";
1797
+ const isKubernetesAvailable = (entity) => {
1798
+ var _a, _b;
1799
+ return Boolean((_a = entity.metadata.annotations) == null ? void 0 : _a[KUBERNETES_ANNOTATION]) || Boolean((_b = entity.metadata.annotations) == null ? void 0 : _b[KUBERNETES_LABEL_SELECTOR_QUERY_ANNOTATION]);
1800
+ };
1768
1801
  const Router = (_props) => {
1769
1802
  var _a, _b;
1770
1803
  const {entity} = useEntity();
@@ -1789,8 +1822,9 @@ const Router = (_props) => {
1789
1822
 
1790
1823
  var Router$1 = /*#__PURE__*/Object.freeze({
1791
1824
  __proto__: null,
1825
+ isKubernetesAvailable: isKubernetesAvailable,
1792
1826
  Router: Router
1793
1827
  });
1794
1828
 
1795
- export { EntityKubernetesContent, KubernetesAuthProviders, Router, clusterLinksFormatters, formatClusterLink, kubernetesAuthProvidersApiRef, kubernetesPlugin, kubernetesPlugin as plugin };
1829
+ export { EntityKubernetesContent, KubernetesAuthProviders, Router, clusterLinksFormatters, formatClusterLink, isKubernetesAvailable, kubernetesApiRef, kubernetesAuthProvidersApiRef, kubernetesPlugin, kubernetesPlugin as plugin };
1796
1830
  //# sourceMappingURL=index.esm.js.map