@harnessio/backstage-plugin-harness-iacm 0.2.0 → 0.4.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
@@ -1,13 +1,20 @@
1
- import React, { useState } from 'react';
1
+ import React, { useState, useMemo, useCallback } from 'react';
2
2
  import { Routes, Route } from 'react-router';
3
- import { CircularProgress, Link, Typography, makeStyles, FormControl, InputLabel, Select, ListSubheader, MenuItem, FormHelperText } from '@material-ui/core';
3
+ import { makeStyles, Button, Typography, CircularProgress, Box, IconButton, Divider, TextField, InputAdornment, Drawer, FormControl, InputLabel, Select, ListSubheader, MenuItem, FormHelperText, Tabs, Tab } from '@material-ui/core';
4
4
  import { Grid } from '@mui/material';
5
5
  import { useApi, discoveryApiRef, createRouteRef, createPlugin, createRoutableExtension } from '@backstage/core-plugin-api';
6
6
  import useAsyncRetry from 'react-use/lib/useAsyncRetry';
7
7
  import { useEntity } from '@backstage/plugin-catalog-react';
8
8
  import { match } from 'path-to-regexp';
9
- import { Table, MissingAnnotationEmptyState } from '@backstage/core-components';
10
- import RetryIcon from '@material-ui/icons/Replay';
9
+ import { Table, EmptyState, MissingAnnotationEmptyState } from '@backstage/core-components';
10
+ import ReplayIcon from '@material-ui/icons/Replay';
11
+ import CopyIcon from '@material-ui/icons/FileCopyOutlined';
12
+ import VisibilityIcon from '@material-ui/icons/Visibility';
13
+ import VisibilityOffIcon from '@material-ui/icons/VisibilityOff';
14
+ import DeleteIcon from '@material-ui/icons/Delete';
15
+ import LensIcon from '@material-ui/icons/Lens';
16
+ import CloseIcon from '@material-ui/icons/Close';
17
+ import SearchIcon from '@material-ui/icons/Search';
11
18
 
12
19
  var AsyncStatus = /* @__PURE__ */ ((AsyncStatus2) => {
13
20
  AsyncStatus2[AsyncStatus2["Init"] = 0] = "Init";
@@ -65,7 +72,11 @@ const useGetResources = ({
65
72
  ...resource,
66
73
  id: (/* @__PURE__ */ new Date()).getTime().toString() + index.toString()
67
74
  })
68
- )
75
+ ),
76
+ outputs: data.outputs.map((output, index) => ({
77
+ ...output,
78
+ id: (/* @__PURE__ */ new Date()).getTime().toString() + index.toString()
79
+ }))
69
80
  };
70
81
  setResources(dataWithId);
71
82
  setStatus(AsyncStatus.Success);
@@ -294,24 +305,237 @@ const Kubernetes = () => /* @__PURE__ */ React.createElement(
294
305
  )
295
306
  );
296
307
 
297
- const getProviderIcon = (name) => {
298
- if (!name)
299
- return /* @__PURE__ */ React.createElement(Aws, null);
300
- if (name.includes("aws")) {
301
- return /* @__PURE__ */ React.createElement(Aws, null);
308
+ const useStyles$5 = makeStyles(() => ({
309
+ iconButton: {
310
+ color: "#0A6EBE",
311
+ width: 15,
312
+ height: 15
302
313
  }
303
- if (name.includes("azure")) {
304
- return /* @__PURE__ */ React.createElement(Azure, null);
314
+ }));
315
+ const CopyToClipboard = ({ copyValue }) => {
316
+ const classes = useStyles$5();
317
+ const copy = (content) => async () => {
318
+ try {
319
+ await navigator.clipboard.writeText(content || "");
320
+ } catch (err) {
321
+ console.error("Failed to copy text: ", err);
322
+ }
323
+ };
324
+ return /* @__PURE__ */ React.createElement(Button, { onClick: copy(copyValue), variant: "text" }, /* @__PURE__ */ React.createElement(CopyIcon, { className: classes.iconButton }));
325
+ };
326
+
327
+ const useStyles$4 = makeStyles((theme) => ({
328
+ container: {
329
+ width: "100%"
330
+ },
331
+ flexCenter: {
332
+ display: "flex",
333
+ alignItems: "center"
334
+ },
335
+ smallGreyText: {
336
+ fontSize: "small",
337
+ color: "grey"
338
+ },
339
+ gap: {
340
+ display: "flex",
341
+ gap: theme.spacing(0.5)
342
+ },
343
+ iconButton: {
344
+ color: "#0A6EBE",
345
+ width: 15,
346
+ height: 15
347
+ },
348
+ paddingRightSmall: {
349
+ paddingRight: theme.spacing(0.5)
305
350
  }
306
- if (name.includes("gcp") || name.includes("google")) {
307
- return /* @__PURE__ */ React.createElement(Gcp, null);
351
+ }));
352
+ const useGetWorkspaceTableColumns = () => {
353
+ const classes = useStyles$4();
354
+ const getProviderIcon = (name) => {
355
+ if (!name)
356
+ return /* @__PURE__ */ React.createElement(Aws, null);
357
+ if (name.includes("aws"))
358
+ return /* @__PURE__ */ React.createElement(Aws, null);
359
+ if (name.includes("azure"))
360
+ return /* @__PURE__ */ React.createElement(Azure, null);
361
+ if (name.includes("gcp") || name.includes("google"))
362
+ return /* @__PURE__ */ React.createElement(Gcp, null);
363
+ if (name.includes("kubernetes") || name.includes("k8s"))
364
+ return /* @__PURE__ */ React.createElement(Kubernetes, null);
365
+ return null;
366
+ };
367
+ const resourceColumns = useMemo(
368
+ () => [
369
+ {
370
+ title: "Provider",
371
+ field: "col1",
372
+ width: "22%",
373
+ render: (row) => /* @__PURE__ */ React.createElement(Typography, { className: classes.smallGreyText }, row.provider),
374
+ customFilterAndSearch: (term, row) => {
375
+ var _a;
376
+ return ((_a = row.provider) != null ? _a : "").toLowerCase().includes(term.toLowerCase());
377
+ },
378
+ customSort: (row1, row2) => {
379
+ var _a, _b;
380
+ return ((_a = row1.provider) != null ? _a : "").localeCompare((_b = row2.provider) != null ? _b : "");
381
+ }
382
+ },
383
+ {
384
+ title: "Type",
385
+ field: "col2",
386
+ width: "18%",
387
+ render: (row) => /* @__PURE__ */ React.createElement("span", { className: classes.gap }, getProviderIcon(row.provider), /* @__PURE__ */ React.createElement(Typography, { className: classes.smallGreyText }, row.type)),
388
+ customFilterAndSearch: (term, row) => {
389
+ var _a;
390
+ return ((_a = row.type) != null ? _a : "").toLowerCase().includes(term.toLowerCase());
391
+ },
392
+ customSort: (row1, row2) => {
393
+ var _a, _b;
394
+ return ((_a = row1.type) != null ? _a : "").localeCompare((_b = row2.type) != null ? _b : "");
395
+ }
396
+ },
397
+ {
398
+ title: "Name",
399
+ field: "col3",
400
+ width: "30%",
401
+ render: (row) => /* @__PURE__ */ React.createElement(Typography, { className: classes.smallGreyText }, row.name),
402
+ customFilterAndSearch: (term, row) => {
403
+ var _a;
404
+ return ((_a = row.name) != null ? _a : "").toLowerCase().includes(term.toLowerCase());
405
+ },
406
+ customSort: (row1, row2) => {
407
+ var _a, _b;
408
+ return ((_a = row1.name) != null ? _a : "").localeCompare((_b = row2.name) != null ? _b : "");
409
+ }
410
+ },
411
+ {
412
+ title: "Module",
413
+ field: "col4",
414
+ width: "20%",
415
+ render: (row) => /* @__PURE__ */ React.createElement(Typography, { className: classes.smallGreyText }, row.module),
416
+ customFilterAndSearch: (term, row) => {
417
+ var _a;
418
+ return ((_a = row.module) != null ? _a : "").toLowerCase().includes(term.toLowerCase());
419
+ },
420
+ customSort: (row1, row2) => {
421
+ var _a, _b;
422
+ return ((_a = row1.module) != null ? _a : "").localeCompare((_b = row2.module) != null ? _b : "");
423
+ }
424
+ }
425
+ ],
426
+ [classes]
427
+ );
428
+ const outputsColumns = useMemo(
429
+ () => [
430
+ {
431
+ title: "Name",
432
+ field: "col3",
433
+ width: "30%",
434
+ render: (row) => /* @__PURE__ */ React.createElement("div", { className: classes.flexCenter }, /* @__PURE__ */ React.createElement(
435
+ Typography,
436
+ {
437
+ className: `${classes.smallGreyText} ${classes.paddingRightSmall}`
438
+ },
439
+ row.name
440
+ ), /* @__PURE__ */ React.createElement(CopyToClipboard, { copyValue: row.name })),
441
+ customFilterAndSearch: (term, row) => {
442
+ var _a;
443
+ return ((_a = row.name) != null ? _a : "").toLowerCase().includes(term.toLowerCase());
444
+ },
445
+ customSort: (row1, row2) => {
446
+ var _a, _b;
447
+ return ((_a = row1.name) != null ? _a : "").localeCompare((_b = row2.name) != null ? _b : "");
448
+ }
449
+ },
450
+ {
451
+ title: "Value",
452
+ field: "col2",
453
+ width: "70%",
454
+ render: (row) => {
455
+ const isSensitive = row.sensitive;
456
+ const [show, setShow] = useState(!isSensitive);
457
+ return /* @__PURE__ */ React.createElement("span", { className: classes.flexCenter }, /* @__PURE__ */ React.createElement(Typography, { className: classes.smallGreyText }, show ? row.value : "********"), show && /* @__PURE__ */ React.createElement(CopyToClipboard, { copyValue: row.value }), isSensitive && /* @__PURE__ */ React.createElement(Button, { onClick: () => setShow(!show) }, show ? /* @__PURE__ */ React.createElement(VisibilityOffIcon, { className: classes.iconButton }) : /* @__PURE__ */ React.createElement(VisibilityIcon, { className: classes.iconButton })));
458
+ },
459
+ customFilterAndSearch: (term, row) => {
460
+ var _a;
461
+ return ((_a = row.value) != null ? _a : "").toLowerCase().includes(term.toLowerCase());
462
+ },
463
+ customSort: (row1, row2) => {
464
+ var _a, _b;
465
+ return ((_a = row1.value) != null ? _a : "").localeCompare((_b = row2.value) != null ? _b : "");
466
+ }
467
+ }
468
+ ],
469
+ [classes]
470
+ );
471
+ return {
472
+ resourceColumns,
473
+ dataSourceColumns: resourceColumns,
474
+ // dataSource is also a resource column are same.
475
+ outputsColumns
476
+ };
477
+ };
478
+
479
+ const getWorkspaceTableConfig = (workspaceDataType, columns) => {
480
+ switch (workspaceDataType) {
481
+ case WorkspaceDataType.ResourceType:
482
+ return {
483
+ columns: columns.resourceColumns,
484
+ title: "Workspace Resources"
485
+ };
486
+ case WorkspaceDataType.OutputType:
487
+ return {
488
+ columns: columns.outputsColumns,
489
+ title: "Workspace Outputs"
490
+ };
491
+ case WorkspaceDataType.DataSourceType:
492
+ return {
493
+ columns: columns.dataSourceColumns,
494
+ title: "Workspace Data Sources"
495
+ };
496
+ default:
497
+ return { columns: [], title: "" };
308
498
  }
309
- if (name.includes("kubernetes") || name.includes("k8s")) {
310
- return /* @__PURE__ */ React.createElement(Kubernetes, null);
499
+ };
500
+
501
+ const useStyles$3 = makeStyles((theme) => ({
502
+ emptyContainer: {
503
+ display: "flex",
504
+ flexDirection: "column",
505
+ alignItems: "center",
506
+ justifyContent: "center",
507
+ padding: theme.spacing(8, 2),
508
+ minHeight: 200
509
+ },
510
+ emptyText: {
511
+ color: theme.palette.text.secondary,
512
+ marginTop: theme.spacing(2)
513
+ },
514
+ loadingContainer: {
515
+ display: "flex",
516
+ justifyContent: "center",
517
+ alignItems: "center",
518
+ padding: theme.spacing(4),
519
+ minHeight: 200
520
+ }
521
+ }));
522
+ const TableEmptyState = ({
523
+ status,
524
+ hasData
525
+ }) => {
526
+ const localClasses = useStyles$3();
527
+ const isLoading = status === AsyncStatus.Init || status === AsyncStatus.Loading;
528
+ const isEmpty = status === AsyncStatus.Success && !hasData;
529
+ if (isLoading) {
530
+ return /* @__PURE__ */ React.createElement("div", { className: localClasses.loadingContainer }, /* @__PURE__ */ React.createElement(CircularProgress, null));
531
+ }
532
+ if (isEmpty) {
533
+ return /* @__PURE__ */ React.createElement(Box, { className: localClasses.emptyContainer }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6", color: "textSecondary" }, "No data available"), /* @__PURE__ */ React.createElement(Typography, { variant: "body2", className: localClasses.emptyText }, "There are no items to display in this table."));
311
534
  }
312
535
  return null;
313
536
  };
314
- const ResourceTable = ({
537
+
538
+ const WorkspaceTable = ({
315
539
  setRefresh,
316
540
  refresh,
317
541
  pageSize,
@@ -321,93 +545,16 @@ const ResourceTable = ({
321
545
  totalElements,
322
546
  handleChangeRowsPerPage,
323
547
  classes,
324
- baseUrl
548
+ workspaceDataType,
549
+ onRowClick,
550
+ status
325
551
  }) => {
326
- const columns = [
327
- {
328
- title: "Provider",
329
- field: "col1",
330
- width: "22%",
331
- render: (row) => {
332
- return /* @__PURE__ */ React.createElement(
333
- Link,
334
- {
335
- href: baseUrl,
336
- target: "_blank",
337
- style: {
338
- display: "flex",
339
- justifyContent: "center",
340
- alignItems: "center"
341
- },
342
- key: row.id
343
- },
344
- /* @__PURE__ */ React.createElement("b", null, row.provider)
345
- );
346
- },
347
- customFilterAndSearch: (term, row) => {
348
- var _a;
349
- const temp = (_a = row == null ? void 0 : row.provider) != null ? _a : "";
350
- return temp.toLowerCase().indexOf(term.toLowerCase()) > -1;
351
- },
352
- customSort: (row1, row2) => {
353
- var _a, _b;
354
- const a = (_a = row1.provider) != null ? _a : "";
355
- const b = (_b = row2.provider) != null ? _b : "";
356
- return a > b ? 1 : -1;
357
- }
358
- },
359
- {
360
- title: "Type",
361
- field: "col2",
362
- width: "18%",
363
- render: (row) => /* @__PURE__ */ React.createElement("span", { style: { display: "flex", gap: "5px" } }, getProviderIcon(row.provider), /* @__PURE__ */ React.createElement(Typography, { style: { fontSize: "small", color: "grey" } }, row.type)),
364
- customFilterAndSearch: (term, row) => {
365
- var _a;
366
- const temp = (_a = row == null ? void 0 : row.type) != null ? _a : "";
367
- return temp.toLowerCase().indexOf(term.toLowerCase()) > -1;
368
- },
369
- customSort: (row1, row2) => {
370
- var _a, _b;
371
- const a = (_a = row1.type) != null ? _a : "";
372
- const b = (_b = row2.type) != null ? _b : "";
373
- return a > b ? 1 : -1;
374
- }
375
- },
376
- {
377
- title: "Name",
378
- field: "col3",
379
- width: "30%",
380
- render: (row) => /* @__PURE__ */ React.createElement(Typography, { style: { fontSize: "small", color: "grey" } }, row.name),
381
- customFilterAndSearch: (term, row) => {
382
- var _a;
383
- const temp = (_a = row == null ? void 0 : row.name) != null ? _a : "";
384
- return temp.toLowerCase().indexOf(term.toLowerCase()) > -1;
385
- },
386
- customSort: (row1, row2) => {
387
- var _a, _b;
388
- const a = (_a = row1.name) != null ? _a : "";
389
- const b = (_b = row2.name) != null ? _b : "";
390
- return a > b ? 1 : -1;
391
- }
392
- },
393
- {
394
- title: "Module",
395
- field: "col4",
396
- width: "20%",
397
- render: (row) => /* @__PURE__ */ React.createElement(Typography, { style: { fontSize: "small", color: "grey" } }, row.module),
398
- customFilterAndSearch: (term, row) => {
399
- var _a;
400
- const temp = (_a = row == null ? void 0 : row.module) != null ? _a : "";
401
- return temp.toLowerCase().indexOf(term.toLowerCase()) > -1;
402
- },
403
- customSort: (row1, row2) => {
404
- var _a, _b;
405
- const a = (_a = row1.module) != null ? _a : "";
406
- const b = (_b = row2.module) != null ? _b : "";
407
- return a > b ? 1 : -1;
408
- }
409
- }
410
- ];
552
+ const columnsData = useGetWorkspaceTableColumns();
553
+ const { columns, title } = useMemo(
554
+ () => getWorkspaceTableConfig(workspaceDataType, columnsData),
555
+ [workspaceDataType, columnsData]
556
+ );
557
+ const hasData = currTableData && currTableData.length > 0;
411
558
  return /* @__PURE__ */ React.createElement(
412
559
  Table,
413
560
  {
@@ -416,14 +563,16 @@ const ResourceTable = ({
416
563
  filtering: false,
417
564
  emptyRowsWhenPaging: false,
418
565
  pageSize,
419
- pageSizeOptions: [5, 10, 25]
566
+ search: true,
567
+ pageSizeOptions: [10, 25, 50],
568
+ rowStyle: { cursor: onRowClick ? "pointer" : "default" }
420
569
  },
421
570
  key: "id'",
422
571
  data: currTableData != null ? currTableData : [],
423
572
  columns,
424
573
  actions: [
425
574
  {
426
- icon: () => /* @__PURE__ */ React.createElement(RetryIcon, null),
575
+ icon: () => /* @__PURE__ */ React.createElement(ReplayIcon, null),
427
576
  tooltip: "Refresh Data",
428
577
  isFreeAction: true,
429
578
  onClick: () => {
@@ -431,8 +580,16 @@ const ResourceTable = ({
431
580
  }
432
581
  }
433
582
  ],
434
- emptyContent: /* @__PURE__ */ React.createElement("div", { className: classes.empty }, /* @__PURE__ */ React.createElement(CircularProgress, null)),
435
- title: "Workspace Resources",
583
+ onRowClick: onRowClick ? (_event, _rowData) => onRowClick(_rowData) : void 0,
584
+ emptyContent: /* @__PURE__ */ React.createElement(
585
+ TableEmptyState,
586
+ {
587
+ status,
588
+ hasData: !!hasData,
589
+ classes
590
+ }
591
+ ),
592
+ title,
436
593
  page,
437
594
  onPageChange: handleChangePage,
438
595
  totalCount: totalElements,
@@ -441,6 +598,453 @@ const ResourceTable = ({
441
598
  );
442
599
  };
443
600
 
601
+ const getCurrTableData = (workspaceDataType, data) => {
602
+ switch (workspaceDataType) {
603
+ case WorkspaceDataType.ResourceType:
604
+ return data.resources || [];
605
+ case WorkspaceDataType.DataSourceType:
606
+ return data.dataSources || [];
607
+ case WorkspaceDataType.OutputType:
608
+ return data.outputs || [];
609
+ default:
610
+ return [];
611
+ }
612
+ };
613
+ const getTotalElements = (workspaceDataType, data) => {
614
+ var _a, _b, _c;
615
+ switch (workspaceDataType) {
616
+ case WorkspaceDataType.ResourceType:
617
+ return ((_a = data.resources) == null ? void 0 : _a.length) || 0;
618
+ case WorkspaceDataType.DataSourceType:
619
+ return ((_b = data.dataSources) == null ? void 0 : _b.length) || 0;
620
+ case WorkspaceDataType.OutputType:
621
+ return ((_c = data.outputs) == null ? void 0 : _c.length) || 0;
622
+ default:
623
+ return 0;
624
+ }
625
+ };
626
+
627
+ const useStyles$2 = makeStyles((theme) => ({
628
+ drawerContent: {
629
+ width: 1e3,
630
+ display: "flex",
631
+ flexDirection: "column",
632
+ height: "100%",
633
+ padding: theme.spacing(2),
634
+ overflow: "hidden"
635
+ },
636
+ drawerHeader: {
637
+ display: "flex",
638
+ alignItems: "center",
639
+ justifyContent: "space-between",
640
+ paddingLeft: theme.spacing(1),
641
+ paddingRight: theme.spacing(1),
642
+ flexShrink: 0
643
+ },
644
+ drawerTitle: {
645
+ fontWeight: "bold",
646
+ flex: 1
647
+ },
648
+ drawerSubHeader: {
649
+ display: "flex",
650
+ alignItems: "center",
651
+ padding: theme.spacing(1),
652
+ gap: theme.spacing(1),
653
+ flexShrink: 0
654
+ },
655
+ subHeaderItem: {
656
+ display: "flex",
657
+ alignItems: "center",
658
+ gap: theme.spacing(0.5)
659
+ },
660
+ subHeaderLabel: {
661
+ color: theme.palette.text.secondary
662
+ },
663
+ subHeaderValue: {
664
+ fontWeight: 600,
665
+ color: theme.palette.text.primary
666
+ },
667
+ subHeaderDivider: {
668
+ height: 16,
669
+ margin: `0 ${theme.spacing(0.5)}`
670
+ },
671
+ drawerBody: {
672
+ flex: 1,
673
+ minHeight: 0,
674
+ paddingLeft: theme.spacing(2),
675
+ paddingRight: theme.spacing(2),
676
+ paddingBottom: theme.spacing(2),
677
+ overflowY: "auto",
678
+ overflowX: "hidden"
679
+ },
680
+ searchField: {
681
+ marginBottom: theme.spacing(2)
682
+ },
683
+ searchFieldSticky: {
684
+ position: "sticky",
685
+ top: 0,
686
+ backgroundColor: theme.palette.background.paper,
687
+ zIndex: 10,
688
+ paddingTop: theme.spacing(2),
689
+ paddingBottom: theme.spacing(1),
690
+ marginBottom: theme.spacing(2),
691
+ marginLeft: theme.spacing(-2),
692
+ marginRight: theme.spacing(-2),
693
+ paddingLeft: theme.spacing(2),
694
+ paddingRight: theme.spacing(2)
695
+ },
696
+ valueHeader: {
697
+ fontWeight: 600,
698
+ textTransform: "uppercase",
699
+ marginBottom: theme.spacing(1),
700
+ color: theme.palette.text.secondary
701
+ },
702
+ attributeRow: {
703
+ backgroundColor: theme.palette.background.paper,
704
+ border: `1px solid ${theme.palette.divider}`,
705
+ borderRadius: 4,
706
+ marginBottom: theme.spacing(1),
707
+ padding: theme.spacing(1.5),
708
+ paddingBottom: theme.spacing(1),
709
+ position: "relative"
710
+ },
711
+ attributeRowDrift: {
712
+ backgroundColor: theme.palette.type === "dark" ? theme.palette.background.paper : "#fff3e0",
713
+ borderColor: "#ffb74d"
714
+ },
715
+ attributeKey: {
716
+ fontWeight: 500,
717
+ marginBottom: theme.spacing(0.75),
718
+ color: theme.palette.text.primary,
719
+ display: "flex",
720
+ alignItems: "center",
721
+ gap: theme.spacing(0.5),
722
+ fontFamily: "inherit"
723
+ },
724
+ attributeValue: {
725
+ fontWeight: 400,
726
+ color: theme.palette.text.primary,
727
+ flex: 1,
728
+ wordBreak: "break-word",
729
+ fontFamily: "inherit"
730
+ },
731
+ jsonValue: {
732
+ padding: theme.spacing(1),
733
+ borderRadius: 4,
734
+ overflow: "auto",
735
+ marginTop: theme.spacing(0.5),
736
+ border: `1px solid ${theme.palette.divider}`,
737
+ fontFamily: "inherit"
738
+ },
739
+ deletedBadge: {
740
+ fontWeight: 600,
741
+ textTransform: "uppercase",
742
+ backgroundColor: theme.palette.type === "dark" ? "rgba(244, 67, 54, 0.2)" : "#ffebee",
743
+ color: theme.palette.type === "dark" ? "#ffcdd2" : theme.palette.text.primary,
744
+ padding: "2px 6px",
745
+ borderRadius: 4,
746
+ display: "inline-flex",
747
+ alignItems: "center",
748
+ gap: 4,
749
+ marginLeft: theme.spacing(0.5)
750
+ },
751
+ valueComparison: {
752
+ marginTop: theme.spacing(1)
753
+ },
754
+ valueLabel: {
755
+ fontWeight: 600,
756
+ marginRight: theme.spacing(1),
757
+ color: theme.palette.text.primary
758
+ },
759
+ divider: {
760
+ margin: 0
761
+ },
762
+ copyButtonWrapper: {
763
+ position: "absolute",
764
+ top: theme.spacing(1),
765
+ right: 0,
766
+ zIndex: 1
767
+ },
768
+ iconContainer: {
769
+ marginRight: theme.spacing(1),
770
+ display: "inline-flex",
771
+ alignItems: "center"
772
+ },
773
+ iconContainerInline: {
774
+ display: "inline-flex",
775
+ alignItems: "center",
776
+ marginRight: theme.spacing(0.5)
777
+ }
778
+ }));
779
+
780
+ const isValueUnknown = (value) => {
781
+ return value === null || value === void 0 || value === "";
782
+ };
783
+ const formatValue = (value) => {
784
+ if (isValueUnknown(value)) {
785
+ return "Unknown";
786
+ }
787
+ if (typeof value === "object") {
788
+ return JSON.stringify(value, null, 2);
789
+ }
790
+ return String(value);
791
+ };
792
+ const filterAttributes = (attributes, driftAttributes, searchValue) => {
793
+ if (!attributes)
794
+ return [];
795
+ const attributesArray = Object.entries(attributes).map(([key, value]) => ({
796
+ key,
797
+ value,
798
+ driftValue: driftAttributes[key],
799
+ hasDrift: !!driftAttributes[key]
800
+ }));
801
+ if (!searchValue)
802
+ return attributesArray;
803
+ const lowerSearch = searchValue.toLowerCase();
804
+ return attributesArray.filter(
805
+ (item) => item.key.toLowerCase().includes(lowerSearch) || JSON.stringify(item.value).toLowerCase().includes(lowerSearch) || item.driftValue && JSON.stringify(item.driftValue).toLowerCase().includes(lowerSearch)
806
+ );
807
+ };
808
+
809
+ const getDriftIcon = (driftStatus) => {
810
+ switch (driftStatus == null ? void 0 : driftStatus.toLowerCase()) {
811
+ case "drifted":
812
+ return /* @__PURE__ */ React.createElement(ReplayIcon, { style: { fontSize: 18, color: "#ff9800" } });
813
+ case "changed":
814
+ return /* @__PURE__ */ React.createElement(ReplayIcon, { style: { fontSize: 18, color: "#ff9800" } });
815
+ case "deleted":
816
+ return /* @__PURE__ */ React.createElement(DeleteIcon, { style: { fontSize: 18, color: "#9e9e9e" } });
817
+ case "unchanged":
818
+ return /* @__PURE__ */ React.createElement(LensIcon, { style: { fontSize: 18, color: "#9e9e9e" } });
819
+ default:
820
+ return null;
821
+ }
822
+ };
823
+
824
+ const Header = ({ title, icon, onClose }) => {
825
+ const classes = useStyles$2();
826
+ return /* @__PURE__ */ React.createElement(Box, { className: classes.drawerHeader }, /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center" }, icon && /* @__PURE__ */ React.createElement(Box, { className: classes.iconContainer }, icon), /* @__PURE__ */ React.createElement(Typography, { variant: "h6", className: classes.drawerTitle }, title)), /* @__PURE__ */ React.createElement(
827
+ IconButton,
828
+ {
829
+ onClick: onClose,
830
+ "aria-label": "close drawer",
831
+ edge: "end",
832
+ style: { padding: 0 }
833
+ },
834
+ /* @__PURE__ */ React.createElement(CloseIcon, null)
835
+ ));
836
+ };
837
+
838
+ const SubHeader = ({ name, provider, module }) => {
839
+ const classes = useStyles$2();
840
+ if (!name && !provider && !module)
841
+ return null;
842
+ return /* @__PURE__ */ React.createElement(Box, { className: classes.drawerSubHeader }, /* @__PURE__ */ React.createElement(Box, { className: classes.subHeaderItem }, /* @__PURE__ */ React.createElement(Typography, { className: classes.subHeaderLabel }, "Name:"), /* @__PURE__ */ React.createElement(Typography, { className: classes.subHeaderValue }, name || "-")), /* @__PURE__ */ React.createElement(
843
+ Divider,
844
+ {
845
+ orientation: "vertical",
846
+ className: classes.subHeaderDivider,
847
+ flexItem: true
848
+ }
849
+ ), /* @__PURE__ */ React.createElement(Box, { className: classes.subHeaderItem }, /* @__PURE__ */ React.createElement(Typography, { className: classes.subHeaderLabel }, "Provider:"), /* @__PURE__ */ React.createElement(Typography, { className: classes.subHeaderValue }, provider || "-")), /* @__PURE__ */ React.createElement(
850
+ Divider,
851
+ {
852
+ orientation: "vertical",
853
+ className: classes.subHeaderDivider,
854
+ flexItem: true
855
+ }
856
+ ), /* @__PURE__ */ React.createElement(Box, { className: classes.subHeaderItem }, /* @__PURE__ */ React.createElement(Typography, { className: classes.subHeaderLabel }, "Module:"), /* @__PURE__ */ React.createElement(Typography, { className: classes.subHeaderValue }, module || "-")));
857
+ };
858
+
859
+ const SearchField = ({ value, onChange }) => {
860
+ const classes = useStyles$2();
861
+ const handleClear = () => {
862
+ onChange("");
863
+ };
864
+ return /* @__PURE__ */ React.createElement(Box, { className: classes.searchFieldSticky }, /* @__PURE__ */ React.createElement(
865
+ TextField,
866
+ {
867
+ placeholder: "Search",
868
+ variant: "outlined",
869
+ size: "small",
870
+ fullWidth: true,
871
+ value,
872
+ onChange: (e) => onChange(e.target.value),
873
+ InputProps: {
874
+ startAdornment: /* @__PURE__ */ React.createElement(InputAdornment, { position: "start" }, /* @__PURE__ */ React.createElement(SearchIcon, { fontSize: "small" })),
875
+ endAdornment: value && /* @__PURE__ */ React.createElement(InputAdornment, { position: "end" }, /* @__PURE__ */ React.createElement(
876
+ IconButton,
877
+ {
878
+ size: "small",
879
+ onClick: handleClear,
880
+ "aria-label": "clear search",
881
+ edge: "end"
882
+ },
883
+ /* @__PURE__ */ React.createElement(CloseIcon, { fontSize: "small" })
884
+ ))
885
+ }
886
+ }
887
+ ));
888
+ };
889
+
890
+ const useStyles$1 = makeStyles((theme) => ({
891
+ jsonValue: {
892
+ padding: theme.spacing(1),
893
+ borderRadius: 4,
894
+ overflow: "auto",
895
+ marginTop: theme.spacing(0.5),
896
+ border: `1px solid ${theme.palette.divider}`,
897
+ fontFamily: "inherit",
898
+ backgroundColor: theme.palette.type === "dark" ? theme.palette.background.default : "#F3F3FA"
899
+ },
900
+ jsonValueDrift: {
901
+ padding: theme.spacing(1),
902
+ borderRadius: 4,
903
+ overflow: "auto",
904
+ marginTop: theme.spacing(0.5),
905
+ border: `1px solid ${theme.palette.divider}`,
906
+ fontFamily: "inherit",
907
+ backgroundColor: theme.palette.type === "dark" ? theme.palette.background.default : "#fff3e0"
908
+ },
909
+ attributeValue: {
910
+ fontSize: "1rem",
911
+ fontWeight: 400,
912
+ color: theme.palette.text.primary,
913
+ flex: 1,
914
+ wordBreak: "break-word",
915
+ fontFamily: "inherit"
916
+ },
917
+ valueLabel: {
918
+ fontWeight: 600,
919
+ marginRight: theme.spacing(1),
920
+ color: theme.palette.text.primary
921
+ },
922
+ copyButtonWrapper: {
923
+ position: "absolute",
924
+ top: 0,
925
+ right: 0,
926
+ zIndex: 1
927
+ },
928
+ stringValue: {
929
+ padding: "8px",
930
+ borderRadius: 4,
931
+ border: `1px solid ${theme.palette.divider}`,
932
+ fontFamily: "inherit",
933
+ backgroundColor: theme.palette.type === "dark" ? theme.palette.background.default : "#F3F3FA"
934
+ },
935
+ stringValueDrift: {
936
+ padding: "8px",
937
+ borderRadius: 4,
938
+ border: `1px solid ${theme.palette.divider}`,
939
+ fontFamily: "inherit",
940
+ backgroundColor: theme.palette.type === "dark" ? theme.palette.background.default : "#fff3e0"
941
+ }
942
+ }));
943
+ const formatValueDisplay = (value, classes, isDrift = false) => {
944
+ if (typeof value === "object") {
945
+ return /* @__PURE__ */ React.createElement("pre", { className: isDrift ? classes.jsonValueDrift : classes.jsonValue }, JSON.stringify(value, null, 2));
946
+ }
947
+ return /* @__PURE__ */ React.createElement("div", { className: isDrift ? classes.stringValueDrift : classes.stringValue }, isValueUnknown(value) ? "Unknown" : String(value));
948
+ };
949
+ const ValueDisplay = ({
950
+ value,
951
+ isDrift = false,
952
+ showCopy = true,
953
+ label,
954
+ copyTopOffset
955
+ }) => {
956
+ const classes = useStyles$1();
957
+ return /* @__PURE__ */ React.createElement(Box, { style: { position: "relative", width: "100%" } }, showCopy && !isValueUnknown(value) && /* @__PURE__ */ React.createElement(
958
+ Box,
959
+ {
960
+ className: classes.copyButtonWrapper,
961
+ style: copyTopOffset ? { top: copyTopOffset } : {}
962
+ },
963
+ /* @__PURE__ */ React.createElement(CopyToClipboard, { copyValue: formatValue(value) })
964
+ ), label && /* @__PURE__ */ React.createElement(Typography, { className: classes.valueLabel }, label), formatValueDisplay(value, classes, isDrift));
965
+ };
966
+
967
+ const AttributeList = ({
968
+ attributes,
969
+ driftStatus,
970
+ isDeleted,
971
+ allDeleted
972
+ }) => {
973
+ const classes = useStyles$2();
974
+ if (attributes.length === 0) {
975
+ return /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "No attributes found");
976
+ }
977
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, attributes.map((item, index) => {
978
+ const hasDrift = item.hasDrift;
979
+ const driftValue = item.driftValue;
980
+ const attributeIcon = hasDrift && driftStatus ? getDriftIcon(driftStatus) : null;
981
+ return /* @__PURE__ */ React.createElement(
982
+ Box,
983
+ {
984
+ key: `${item.key}-${index}`,
985
+ className: hasDrift ? `${classes.attributeRow} ${classes.attributeRowDrift}` : classes.attributeRow
986
+ },
987
+ /* @__PURE__ */ React.createElement(Typography, { component: "div", className: classes.attributeKey }, attributeIcon && /* @__PURE__ */ React.createElement("span", { className: classes.iconContainerInline }, attributeIcon), item.key, (isDeleted || allDeleted && hasDrift) && /* @__PURE__ */ React.createElement("span", { className: classes.deletedBadge }, /* @__PURE__ */ React.createElement(DeleteIcon, { style: { fontSize: 12 } }), "DELETED")),
988
+ hasDrift && driftValue ? /* @__PURE__ */ React.createElement(Box, { className: classes.valueComparison }, /* @__PURE__ */ React.createElement(
989
+ ValueDisplay,
990
+ {
991
+ value: driftValue,
992
+ isDrift: true,
993
+ label: "Actual Value:",
994
+ copyTopOffset: "25px"
995
+ }
996
+ ), /* @__PURE__ */ React.createElement(
997
+ ValueDisplay,
998
+ {
999
+ value: item.value,
1000
+ isDrift: true,
1001
+ label: "Expected Value:",
1002
+ copyTopOffset: "25px"
1003
+ }
1004
+ )) : /* @__PURE__ */ React.createElement(ValueDisplay, { value: item.value })
1005
+ );
1006
+ }));
1007
+ };
1008
+
1009
+ const ResourceDetailDrawer = ({
1010
+ open,
1011
+ onClose,
1012
+ width = 1e3,
1013
+ resource,
1014
+ title = ""
1015
+ }) => {
1016
+ const classes = useStyles$2();
1017
+ const [searchValue, setSearchValue] = useState("");
1018
+ const driftStatus = resource == null ? void 0 : resource.drift_status;
1019
+ const driftAttributes = useMemo(
1020
+ () => (resource == null ? void 0 : resource.drift_attributes) || {},
1021
+ [resource]
1022
+ );
1023
+ const isDeleted = driftStatus === "deleted";
1024
+ const allDeleted = isDeleted && Object.keys(driftAttributes).length > 0;
1025
+ const headerIcon = driftStatus ? getDriftIcon(driftStatus) : null;
1026
+ const filteredAttributes = useMemo(
1027
+ () => filterAttributes(resource == null ? void 0 : resource.attributes, driftAttributes, searchValue),
1028
+ [resource == null ? void 0 : resource.attributes, searchValue, driftAttributes]
1029
+ );
1030
+ return /* @__PURE__ */ React.createElement(Drawer, { anchor: "right", open, onClose }, /* @__PURE__ */ React.createElement("div", { className: classes.drawerContent, style: { width } }, /* @__PURE__ */ React.createElement(Header, { title, icon: headerIcon, onClose }), /* @__PURE__ */ React.createElement(
1031
+ SubHeader,
1032
+ {
1033
+ name: resource == null ? void 0 : resource.name,
1034
+ provider: resource == null ? void 0 : resource.provider,
1035
+ module: resource == null ? void 0 : resource.module
1036
+ }
1037
+ ), /* @__PURE__ */ React.createElement(Divider, { className: classes.divider }), /* @__PURE__ */ React.createElement("div", { className: classes.drawerBody }, resource && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(SearchField, { value: searchValue, onChange: setSearchValue }), /* @__PURE__ */ React.createElement(Typography, { className: classes.valueHeader }, "VALUE"), /* @__PURE__ */ React.createElement(
1038
+ AttributeList,
1039
+ {
1040
+ attributes: filteredAttributes,
1041
+ driftStatus,
1042
+ isDeleted,
1043
+ allDeleted
1044
+ }
1045
+ )))));
1046
+ };
1047
+
444
1048
  const useStyles = makeStyles((theme) => ({
445
1049
  container: {
446
1050
  width: "100%"
@@ -469,10 +1073,25 @@ const useStyles = makeStyles((theme) => ({
469
1073
  display: "flex",
470
1074
  alignItems: "center",
471
1075
  gap: "5px"
472
- }
1076
+ },
1077
+ noAccess: {
1078
+ padding: theme.spacing(2),
1079
+ display: "flex",
1080
+ textAlign: "center"
1081
+ },
1082
+ workspaceList: {
1083
+ marginTop: "-40px"
1084
+ },
1085
+ workspaceItem: { padding: "0.75rem", fontSize: "1rem" }
473
1086
  }));
1087
+ var WorkspaceDataType = /* @__PURE__ */ ((WorkspaceDataType2) => {
1088
+ WorkspaceDataType2["ResourceType"] = "Resource";
1089
+ WorkspaceDataType2["DataSourceType"] = "DataSource";
1090
+ WorkspaceDataType2["OutputType"] = "Output";
1091
+ return WorkspaceDataType2;
1092
+ })(WorkspaceDataType || {});
474
1093
  function WorkspaceList() {
475
- var _a;
1094
+ var _a, _b, _c;
476
1095
  const [refresh, setRefresh] = useState(false);
477
1096
  const classes = useStyles();
478
1097
  const discoveryApi = useApi(discoveryApiRef);
@@ -485,19 +1104,24 @@ function WorkspaceList() {
485
1104
  return harnessWorkspaceUrlObject[Object.keys(harnessWorkspaceUrlObject)[0]];
486
1105
  });
487
1106
  const [page, setPage] = useState(0);
488
- const [pageSize, setPageSize] = useState(5);
489
- const {
490
- projectId,
491
- orgId,
492
- accountId,
493
- envFromUrl,
494
- workspaceId,
495
- cleanedString: urlForWorkspace
496
- } = useResourceSlugFromEntity(
1107
+ const [pageSize, setPageSize] = useState(10);
1108
+ const [selectedTab, setSelectedTab] = React.useState(
1109
+ "Resource" /* ResourceType */
1110
+ );
1111
+ const [selectedRowData, setSelectedRowData] = useState(null);
1112
+ const handleChange = (_event, resourceType) => {
1113
+ setSelectedTab(resourceType);
1114
+ };
1115
+ const drawerTitle = useMemo(() => {
1116
+ const driftStatus = (selectedRowData == null ? void 0 : selectedRowData.drift_status) || "";
1117
+ const capitalizedStatus = driftStatus ? driftStatus.charAt(0).toUpperCase() + driftStatus.slice(1) : "";
1118
+ return `${capitalizedStatus} ${selectedTab === "Resource" /* ResourceType */ ? "Resources" : "Data Sources"}`;
1119
+ }, [selectedRowData, selectedTab]);
1120
+ const { projectId, orgId, accountId, envFromUrl, workspaceId } = useResourceSlugFromEntity(
497
1121
  isWorkspaceAnnotationPresent,
498
1122
  selectedResourceUrl
499
1123
  );
500
- const { resources, status: state } = useGetResources({
1124
+ const { resources: workspaceData, status: state } = useGetResources({
501
1125
  backendBaseUrl,
502
1126
  accountId,
503
1127
  orgId,
@@ -506,18 +1130,52 @@ function WorkspaceList() {
506
1130
  envFromUrl,
507
1131
  workspace: workspaceId || null
508
1132
  });
1133
+ const { resources, outputs, data_sources: dataSources } = workspaceData || {};
1134
+ const workspaceDataObj = useMemo(
1135
+ () => ({
1136
+ resources,
1137
+ outputs,
1138
+ dataSources
1139
+ }),
1140
+ [resources, outputs, dataSources]
1141
+ );
1142
+ const currTableData = useMemo(
1143
+ () => getCurrTableData(selectedTab, workspaceDataObj),
1144
+ [selectedTab, workspaceDataObj]
1145
+ );
1146
+ const totalElements = useMemo(
1147
+ () => getTotalElements(selectedTab, workspaceDataObj),
1148
+ [selectedTab, workspaceDataObj]
1149
+ );
509
1150
  const handleWorkspaceChange = (event) => {
510
1151
  setSelectedProjectUrl(event.target.value);
511
1152
  setSelectedResourceUrl(harnessWorkspaceUrlObject[event.target.value]);
512
1153
  };
513
- const handleChangePage = (currentPage, currentPageSize) => {
514
- setPage(currentPage);
515
- setPageSize(currentPageSize);
516
- };
517
- const handleChangeRowsPerPage = (currentPageSize) => {
518
- setPage(0);
519
- setPageSize(currentPageSize);
520
- };
1154
+ const handleChangePage = useCallback(
1155
+ (currentPage, currentPageSize) => {
1156
+ setPage(currentPage);
1157
+ setPageSize(currentPageSize);
1158
+ },
1159
+ [setPage, setPageSize]
1160
+ );
1161
+ const handleChangeRowsPerPage = useCallback(
1162
+ (currentPageSize) => {
1163
+ setPage(0);
1164
+ setPageSize(currentPageSize);
1165
+ },
1166
+ [setPage, setPageSize]
1167
+ );
1168
+ const handleRowClick = useCallback(
1169
+ (data) => {
1170
+ if (selectedTab === "Output" /* OutputType */)
1171
+ return;
1172
+ setSelectedRowData(data);
1173
+ },
1174
+ [selectedTab]
1175
+ );
1176
+ const handleDrawerClose = useCallback(() => {
1177
+ setSelectedRowData(null);
1178
+ }, []);
521
1179
  const newWorkspaceDropdown = /* @__PURE__ */ React.createElement(FormControl, { fullWidth: true }, /* @__PURE__ */ React.createElement(
522
1180
  InputLabel,
523
1181
  {
@@ -541,27 +1199,86 @@ function WorkspaceList() {
541
1199
  Object.keys(harnessWorkspaceUrlObject).map((workspace) => /* @__PURE__ */ React.createElement(MenuItem, { value: workspace, key: workspace }, /* @__PURE__ */ React.createElement("span", { className: classes.menuItem }, workspace)))
542
1200
  ), /* @__PURE__ */ React.createElement(FormHelperText, null));
543
1201
  const DropDownComponent = /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 3, marginBottom: 4, marginLeft: 1, paddingTop: 2 }, isWorkspaceAnnotationPresent ? /* @__PURE__ */ React.createElement(Grid, { item: true, md: 3 }, newWorkspaceDropdown) : null);
1202
+ if (state === AsyncStatus.Unauthorized) {
1203
+ const urls = Object.values(harnessWorkspaceUrlObject).map(
1204
+ (url) => url.replace("|", "")
1205
+ );
1206
+ return /* @__PURE__ */ React.createElement(
1207
+ EmptyState,
1208
+ {
1209
+ title: "You don't have the permission to View the following IaCM workspace(s)",
1210
+ missing: "info",
1211
+ action: /* @__PURE__ */ React.createElement("ul", { className: classes.workspaceList }, urls.map((workspace) => /* @__PURE__ */ React.createElement(
1212
+ "li",
1213
+ {
1214
+ value: workspace,
1215
+ key: workspace,
1216
+ className: classes.workspaceItem
1217
+ },
1218
+ /* @__PURE__ */ React.createElement("span", null, workspace)
1219
+ )))
1220
+ }
1221
+ );
1222
+ }
544
1223
  if (state === AsyncStatus.Init || state === AsyncStatus.Loading) {
545
1224
  return /* @__PURE__ */ React.createElement("div", { className: classes.empty }, /* @__PURE__ */ React.createElement(CircularProgress, null));
546
1225
  }
547
1226
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: classes.container }, DropDownComponent, /* @__PURE__ */ React.createElement(
548
- ResourceTable,
1227
+ Tabs,
1228
+ {
1229
+ value: selectedTab,
1230
+ indicatorColor: "primary",
1231
+ textColor: "primary",
1232
+ onChange: handleChange,
1233
+ "aria-label": "workspace_list_tabs"
1234
+ },
1235
+ /* @__PURE__ */ React.createElement(
1236
+ Tab,
1237
+ {
1238
+ label: `Resources (${(_a = resources == null ? void 0 : resources.length) != null ? _a : 0})`,
1239
+ value: "Resource" /* ResourceType */
1240
+ }
1241
+ ),
1242
+ /* @__PURE__ */ React.createElement(
1243
+ Tab,
1244
+ {
1245
+ label: `Data Sources (${(_b = dataSources == null ? void 0 : dataSources.length) != null ? _b : 0})`,
1246
+ value: "DataSource" /* DataSourceType */
1247
+ }
1248
+ ),
1249
+ /* @__PURE__ */ React.createElement(
1250
+ Tab,
1251
+ {
1252
+ label: `Outputs (${(_c = outputs == null ? void 0 : outputs.length) != null ? _c : 0})`,
1253
+ value: "Output" /* OutputType */
1254
+ }
1255
+ )
1256
+ ), /* @__PURE__ */ React.createElement(
1257
+ WorkspaceTable,
549
1258
  {
550
- accountId,
551
- orgId,
552
- backendBaseUrl,
553
1259
  setRefresh,
554
1260
  refresh,
555
1261
  pageSize,
556
- currTableData: resources == null ? void 0 : resources.resources,
1262
+ currTableData,
557
1263
  page,
558
1264
  handleChangePage,
559
- totalElements: (_a = resources == null ? void 0 : resources.resources) == null ? void 0 : _a.length,
1265
+ totalElements,
560
1266
  handleChangeRowsPerPage,
561
1267
  classes,
562
- baseUrl: urlForWorkspace
1268
+ workspaceDataType: selectedTab,
1269
+ onRowClick: handleRowClick,
1270
+ status: state
1271
+ }
1272
+ )), /* @__PURE__ */ React.createElement(
1273
+ ResourceDetailDrawer,
1274
+ {
1275
+ open: !!selectedRowData,
1276
+ resource: selectedRowData,
1277
+ onClose: handleDrawerClose,
1278
+ title: drawerTitle,
1279
+ width: 1200
563
1280
  }
564
- )));
1281
+ ));
565
1282
  }
566
1283
 
567
1284
  const isHarnessIacmAvailable = (entity) => {