@harnessio/backstage-plugin-harness-iacm 0.3.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,16 +1,20 @@
1
1
  import React, { useState, useMemo, useCallback } from 'react';
2
2
  import { Routes, Route } from 'react-router';
3
- import { makeStyles, Button, Link, Typography, CircularProgress, FormControl, InputLabel, Select, ListSubheader, MenuItem, FormHelperText, Tabs, Tab } 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
9
  import { Table, EmptyState, MissingAnnotationEmptyState } from '@backstage/core-components';
10
- import RetryIcon from '@material-ui/icons/Replay';
10
+ import ReplayIcon from '@material-ui/icons/Replay';
11
11
  import CopyIcon from '@material-ui/icons/FileCopyOutlined';
12
12
  import VisibilityIcon from '@material-ui/icons/Visibility';
13
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';
14
18
 
15
19
  var AsyncStatus = /* @__PURE__ */ ((AsyncStatus2) => {
16
20
  AsyncStatus2[AsyncStatus2["Init"] = 0] = "Init";
@@ -301,7 +305,7 @@ const Kubernetes = () => /* @__PURE__ */ React.createElement(
301
305
  )
302
306
  );
303
307
 
304
- const useStyles$2 = makeStyles(() => ({
308
+ const useStyles$5 = makeStyles(() => ({
305
309
  iconButton: {
306
310
  color: "#0A6EBE",
307
311
  width: 15,
@@ -309,7 +313,7 @@ const useStyles$2 = makeStyles(() => ({
309
313
  }
310
314
  }));
311
315
  const CopyToClipboard = ({ copyValue }) => {
312
- const classes = useStyles$2();
316
+ const classes = useStyles$5();
313
317
  const copy = (content) => async () => {
314
318
  try {
315
319
  await navigator.clipboard.writeText(content || "");
@@ -320,7 +324,7 @@ const CopyToClipboard = ({ copyValue }) => {
320
324
  return /* @__PURE__ */ React.createElement(Button, { onClick: copy(copyValue), variant: "text" }, /* @__PURE__ */ React.createElement(CopyIcon, { className: classes.iconButton }));
321
325
  };
322
326
 
323
- const useStyles$1 = makeStyles((theme) => ({
327
+ const useStyles$4 = makeStyles((theme) => ({
324
328
  container: {
325
329
  width: "100%"
326
330
  },
@@ -345,10 +349,8 @@ const useStyles$1 = makeStyles((theme) => ({
345
349
  paddingRight: theme.spacing(0.5)
346
350
  }
347
351
  }));
348
- const useGetWorkspaceTableColumns = ({
349
- baseUrl
350
- }) => {
351
- const classes = useStyles$1();
352
+ const useGetWorkspaceTableColumns = () => {
353
+ const classes = useStyles$4();
352
354
  const getProviderIcon = (name) => {
353
355
  if (!name)
354
356
  return /* @__PURE__ */ React.createElement(Aws, null);
@@ -368,16 +370,7 @@ const useGetWorkspaceTableColumns = ({
368
370
  title: "Provider",
369
371
  field: "col1",
370
372
  width: "22%",
371
- render: (row) => /* @__PURE__ */ React.createElement(
372
- Link,
373
- {
374
- href: baseUrl,
375
- target: "_blank",
376
- className: classes.flexCenter,
377
- key: row.id
378
- },
379
- /* @__PURE__ */ React.createElement("b", null, row.provider)
380
- ),
373
+ render: (row) => /* @__PURE__ */ React.createElement(Typography, { className: classes.smallGreyText }, row.provider),
381
374
  customFilterAndSearch: (term, row) => {
382
375
  var _a;
383
376
  return ((_a = row.provider) != null ? _a : "").toLowerCase().includes(term.toLowerCase());
@@ -430,7 +423,7 @@ const useGetWorkspaceTableColumns = ({
430
423
  }
431
424
  }
432
425
  ],
433
- [baseUrl, classes]
426
+ [classes]
434
427
  );
435
428
  const outputsColumns = useMemo(
436
429
  () => [
@@ -477,10 +470,71 @@ const useGetWorkspaceTableColumns = ({
477
470
  );
478
471
  return {
479
472
  resourceColumns,
473
+ dataSourceColumns: resourceColumns,
474
+ // dataSource is also a resource column are same.
480
475
  outputsColumns
481
476
  };
482
477
  };
483
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: "" };
498
+ }
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."));
534
+ }
535
+ return null;
536
+ };
537
+
484
538
  const WorkspaceTable = ({
485
539
  setRefresh,
486
540
  refresh,
@@ -491,12 +545,16 @@ const WorkspaceTable = ({
491
545
  totalElements,
492
546
  handleChangeRowsPerPage,
493
547
  classes,
494
- baseUrl,
495
- workspaceDataType
548
+ workspaceDataType,
549
+ onRowClick,
550
+ status
496
551
  }) => {
497
- const { resourceColumns, outputsColumns } = useGetWorkspaceTableColumns({
498
- baseUrl: baseUrl || ""
499
- });
552
+ const columnsData = useGetWorkspaceTableColumns();
553
+ const { columns, title } = useMemo(
554
+ () => getWorkspaceTableConfig(workspaceDataType, columnsData),
555
+ [workspaceDataType, columnsData]
556
+ );
557
+ const hasData = currTableData && currTableData.length > 0;
500
558
  return /* @__PURE__ */ React.createElement(
501
559
  Table,
502
560
  {
@@ -505,14 +563,16 @@ const WorkspaceTable = ({
505
563
  filtering: false,
506
564
  emptyRowsWhenPaging: false,
507
565
  pageSize,
508
- pageSizeOptions: [5, 10, 25]
566
+ search: true,
567
+ pageSizeOptions: [10, 25, 50],
568
+ rowStyle: { cursor: onRowClick ? "pointer" : "default" }
509
569
  },
510
570
  key: "id'",
511
571
  data: currTableData != null ? currTableData : [],
512
- columns: workspaceDataType === WorkspaceDataType.Resource ? resourceColumns : outputsColumns,
572
+ columns,
513
573
  actions: [
514
574
  {
515
- icon: () => /* @__PURE__ */ React.createElement(RetryIcon, null),
575
+ icon: () => /* @__PURE__ */ React.createElement(ReplayIcon, null),
516
576
  tooltip: "Refresh Data",
517
577
  isFreeAction: true,
518
578
  onClick: () => {
@@ -520,8 +580,16 @@ const WorkspaceTable = ({
520
580
  }
521
581
  }
522
582
  ],
523
- emptyContent: /* @__PURE__ */ React.createElement("div", { className: classes.empty }, /* @__PURE__ */ React.createElement(CircularProgress, null)),
524
- title: workspaceDataType === WorkspaceDataType.Resource ? "Workspace Resources" : "Workspace Outputs",
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,
525
593
  page,
526
594
  onPageChange: handleChangePage,
527
595
  totalCount: totalElements,
@@ -530,6 +598,453 @@ const WorkspaceTable = ({
530
598
  );
531
599
  };
532
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
+
533
1048
  const useStyles = makeStyles((theme) => ({
534
1049
  container: {
535
1050
  width: "100%"
@@ -570,11 +1085,13 @@ const useStyles = makeStyles((theme) => ({
570
1085
  workspaceItem: { padding: "0.75rem", fontSize: "1rem" }
571
1086
  }));
572
1087
  var WorkspaceDataType = /* @__PURE__ */ ((WorkspaceDataType2) => {
573
- WorkspaceDataType2[WorkspaceDataType2["Resource"] = 0] = "Resource";
574
- WorkspaceDataType2[WorkspaceDataType2["Output"] = 1] = "Output";
1088
+ WorkspaceDataType2["ResourceType"] = "Resource";
1089
+ WorkspaceDataType2["DataSourceType"] = "DataSource";
1090
+ WorkspaceDataType2["OutputType"] = "Output";
575
1091
  return WorkspaceDataType2;
576
1092
  })(WorkspaceDataType || {});
577
1093
  function WorkspaceList() {
1094
+ var _a, _b, _c;
578
1095
  const [refresh, setRefresh] = useState(false);
579
1096
  const classes = useStyles();
580
1097
  const discoveryApi = useApi(discoveryApiRef);
@@ -587,21 +1104,20 @@ function WorkspaceList() {
587
1104
  return harnessWorkspaceUrlObject[Object.keys(harnessWorkspaceUrlObject)[0]];
588
1105
  });
589
1106
  const [page, setPage] = useState(0);
590
- const [pageSize, setPageSize] = useState(5);
1107
+ const [pageSize, setPageSize] = useState(10);
591
1108
  const [selectedTab, setSelectedTab] = React.useState(
592
- 0 /* Resource */
1109
+ "Resource" /* ResourceType */
593
1110
  );
1111
+ const [selectedRowData, setSelectedRowData] = useState(null);
594
1112
  const handleChange = (_event, resourceType) => {
595
1113
  setSelectedTab(resourceType);
596
1114
  };
597
- const {
598
- projectId,
599
- orgId,
600
- accountId,
601
- envFromUrl,
602
- workspaceId,
603
- cleanedString: urlForWorkspace
604
- } = useResourceSlugFromEntity(
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(
605
1121
  isWorkspaceAnnotationPresent,
606
1122
  selectedResourceUrl
607
1123
  );
@@ -614,7 +1130,23 @@ function WorkspaceList() {
614
1130
  envFromUrl,
615
1131
  workspace: workspaceId || null
616
1132
  });
617
- const { resources, outputs } = workspaceData || {};
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
+ );
618
1150
  const handleWorkspaceChange = (event) => {
619
1151
  setSelectedProjectUrl(event.target.value);
620
1152
  setSelectedResourceUrl(harnessWorkspaceUrlObject[event.target.value]);
@@ -633,6 +1165,17 @@ function WorkspaceList() {
633
1165
  },
634
1166
  [setPage, setPageSize]
635
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
+ }, []);
636
1179
  const newWorkspaceDropdown = /* @__PURE__ */ React.createElement(FormControl, { fullWidth: true }, /* @__PURE__ */ React.createElement(
637
1180
  InputLabel,
638
1181
  {
@@ -689,24 +1232,53 @@ function WorkspaceList() {
689
1232
  onChange: handleChange,
690
1233
  "aria-label": "workspace_list_tabs"
691
1234
  },
692
- /* @__PURE__ */ React.createElement(Tab, { label: `Resources (${resources == null ? void 0 : resources.length})` }),
693
- /* @__PURE__ */ React.createElement(Tab, { label: `Outputs (${outputs == null ? void 0 : outputs.length})` })
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
+ )
694
1256
  ), /* @__PURE__ */ React.createElement(
695
1257
  WorkspaceTable,
696
1258
  {
697
1259
  setRefresh,
698
1260
  refresh,
699
1261
  pageSize,
700
- currTableData: selectedTab === 0 /* Resource */ ? resources : outputs,
1262
+ currTableData,
701
1263
  page,
702
1264
  handleChangePage,
703
- totalElements: selectedTab === 0 /* Resource */ ? resources == null ? void 0 : resources.length : outputs == null ? void 0 : outputs.length,
1265
+ totalElements,
704
1266
  handleChangeRowsPerPage,
705
1267
  classes,
706
- baseUrl: urlForWorkspace,
707
- workspaceDataType: selectedTab
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
708
1280
  }
709
- )));
1281
+ ));
710
1282
  }
711
1283
 
712
1284
  const isHarnessIacmAvailable = (entity) => {