@eqtylab/explorer 0.0.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.
Files changed (140) hide show
  1. package/.env.api +6 -0
  2. package/.env.iframe +2 -0
  3. package/.env.manifest +2 -0
  4. package/.envrc +1 -0
  5. package/.eslintrc.cjs +17 -0
  6. package/.github/workflows/ci.yml +46 -0
  7. package/.github/workflows/publish.yml +44 -0
  8. package/.husky/pre-push +2 -0
  9. package/.prettierignore +2 -0
  10. package/.prettierrc.json +13 -0
  11. package/LICENSE +201 -0
  12. package/README.md +84 -0
  13. package/flake.lock +61 -0
  14. package/flake.nix +23 -0
  15. package/index.html +13 -0
  16. package/package.json +93 -0
  17. package/postcss.config.js +5 -0
  18. package/public/logos/eci.svg +23 -0
  19. package/public/logos/eqty.svg +3 -0
  20. package/public/manifests/B-200-merged.json +773 -0
  21. package/public/manifests/amd-sev.json +426 -0
  22. package/public/manifests/anchors.json +609 -0
  23. package/public/manifests/association-no-metadata.json +1393 -0
  24. package/public/manifests/association.json +1402 -0
  25. package/public/manifests/eqty-docker.json +562 -0
  26. package/public/manifests/governance.json +1324 -0
  27. package/public/manifests/intel-tdx-with-nvidia.json +441 -0
  28. package/public/manifests/intel-tdx.json +404 -0
  29. package/public/manifests/iroh-collections.json +1285 -0
  30. package/public/manifests/model-signing.json +1536 -0
  31. package/public/manifests/multiple-registrations.json +129 -0
  32. package/public/manifests/no-vcs.json +114 -0
  33. package/public/manifests/storage.json +294 -0
  34. package/public/manifests/sub-graphs.json +1 -0
  35. package/src/animations/variants.ts +52 -0
  36. package/src/app/apiClient/integrityService/fetchAttributes.ts +29 -0
  37. package/src/app/apiClient/integrityService/fetchManifest.ts +28 -0
  38. package/src/app/apiClient/integrityService/types.ts +12 -0
  39. package/src/app/components/integrityService/IntegrityService.tsx +105 -0
  40. package/src/app/components/integrityService/components/AuthButton.tsx +26 -0
  41. package/src/app/components/integrityService/components/FiltersModal.tsx +214 -0
  42. package/src/app/components/integrityService/components/FiltersToggle.tsx +28 -0
  43. package/src/app/components/manifestViewer/ManifestViewer.tsx +90 -0
  44. package/src/app/components/manifestViewer/components/ManifestButtons.tsx +215 -0
  45. package/src/app/config.json +23 -0
  46. package/src/app/config.ts +28 -0
  47. package/src/app/pages/index.tsx +34 -0
  48. package/src/app/stores/manifestStore.ts +19 -0
  49. package/src/app/utils/appMode.ts +37 -0
  50. package/src/assets/fonts/TWKLausanne-300.woff +0 -0
  51. package/src/assets/fonts/TWKLausanne-300.woff2 +0 -0
  52. package/src/assets/fonts/TWKLausanne-400.woff +0 -0
  53. package/src/assets/fonts/TWKLausanne-400.woff2 +0 -0
  54. package/src/assets/fonts/TWKLausanne-500.woff +0 -0
  55. package/src/assets/fonts/TWKLausanne-500.woff2 +0 -0
  56. package/src/assets/fonts/TWKLausanne-600.woff +0 -0
  57. package/src/assets/fonts/TWKLausanne-600.woff2 +0 -0
  58. package/src/assets/vectors/eqtylab.svg +6 -0
  59. package/src/components/app-header.tsx +61 -0
  60. package/src/components/dark-mode-switch.tsx +16 -0
  61. package/src/components/ui/accordion.tsx +47 -0
  62. package/src/components/ui/certificate-chain.tsx +60 -0
  63. package/src/components/ui/certificate-code.tsx +39 -0
  64. package/src/components/ui/details.tsx +20 -0
  65. package/src/components/ui/display-code.tsx +47 -0
  66. package/src/components/ui/panel-label.tsx +12 -0
  67. package/src/components/ui/verify-state-badge.tsx +32 -0
  68. package/src/explorer-component/LineageExplorer.tsx +89 -0
  69. package/src/explorer-component/components/CustomEdge.tsx +28 -0
  70. package/src/explorer-component/components/LineageFlow.tsx +220 -0
  71. package/src/explorer-component/components/NodeIconLoader.tsx +26 -0
  72. package/src/explorer-component/components/nodes/ComputationNode.tsx +56 -0
  73. package/src/explorer-component/components/nodes/CustomNode.tsx +112 -0
  74. package/src/explorer-component/components/nodes/index.ts +4 -0
  75. package/src/explorer-component/components/nodes/nodes.tsx +144 -0
  76. package/src/explorer-component/components/nodes/types.ts +167 -0
  77. package/src/explorer-component/components/sidebar/SectionList.tsx +54 -0
  78. package/src/explorer-component/components/sidebar/SectionTitle.tsx +10 -0
  79. package/src/explorer-component/components/sidebar/Sidebar.tsx +70 -0
  80. package/src/explorer-component/components/sidebar/SidebarWrapper.tsx +76 -0
  81. package/src/explorer-component/components/sidebar/sections/CustomSection.tsx +238 -0
  82. package/src/explorer-component/components/sidebar/sections/FileViewer.tsx +229 -0
  83. package/src/explorer-component/components/sidebar/sections/GovernanceSection.tsx +52 -0
  84. package/src/explorer-component/components/sidebar/sections/RegistrationSection.tsx +254 -0
  85. package/src/explorer-component/components/sidebar/sections/SigstoreSection.tsx +37 -0
  86. package/src/explorer-component/components/sidebar/sections/StorageSection.tsx +73 -0
  87. package/src/explorer-component/components/sidebar/sections/custom/IrohCollectionsView.tsx +139 -0
  88. package/src/explorer-component/components/sidebar/sections/registration/VerificationIcon.tsx +108 -0
  89. package/src/explorer-component/components/sidebar/sections/registration/actor/ActorBadge.tsx +62 -0
  90. package/src/explorer-component/components/sidebar/sections/registration/actor/svg/AMDLogo.tsx +40 -0
  91. package/src/explorer-component/components/sidebar/sections/registration/actor/svg/AzureLogo.tsx +65 -0
  92. package/src/explorer-component/components/sidebar/sections/registration/actor/svg/IntelLogo.tsx +24 -0
  93. package/src/explorer-component/components/sidebar/sections/registration/actor/svg/NvidiaLogo.tsx +27 -0
  94. package/src/explorer-component/components/sidebar/sections/registration/verifierClient.ts +13 -0
  95. package/src/explorer-component/icons/AttributionIcon.svg +6 -0
  96. package/src/explorer-component/icons/BenchmarkIcon.svg +6 -0
  97. package/src/explorer-component/icons/BenchmarkResultIcon.svg +6 -0
  98. package/src/explorer-component/icons/CertificateIcon.svg +6 -0
  99. package/src/explorer-component/icons/CodeIcon.svg +6 -0
  100. package/src/explorer-component/icons/ComputationIcon.svg +6 -0
  101. package/src/explorer-component/icons/DataIcon.svg +6 -0
  102. package/src/explorer-component/icons/DatabaseIcon.svg +6 -0
  103. package/src/explorer-component/icons/DatasetIcon.svg +6 -0
  104. package/src/explorer-component/icons/DeltaLakeIcon.svg +7 -0
  105. package/src/explorer-component/icons/DocumentIcon.svg +6 -0
  106. package/src/explorer-component/icons/MediaIcon.svg +6 -0
  107. package/src/explorer-component/icons/ModelIcon.svg +6 -0
  108. package/src/explorer-component/icons/TeamIcon.svg +3 -0
  109. package/src/explorer-component/icons/TokenIcon.svg +6 -0
  110. package/src/explorer-component/icons/UnknownIcon.svg +6 -0
  111. package/src/explorer-component/icons/index.ts +35 -0
  112. package/src/explorer-component/index.ts +5 -0
  113. package/src/explorer-component/integrityTypes/AnchorTypes.ts +31 -0
  114. package/src/explorer-component/integrityTypes/StatementTypes.ts +333 -0
  115. package/src/explorer-component/integrityTypes/VCTypes.ts +85 -0
  116. package/src/explorer-component/integrityTypes/types.ts +3 -0
  117. package/src/explorer-component/utils/config.ts +163 -0
  118. package/src/explorer-component/utils/formatTimestamp.ts +5 -0
  119. package/src/explorer-component/utils/generateGraph.ts +271 -0
  120. package/src/explorer-component/utils/layoutGraph.ts +148 -0
  121. package/src/explorer-component/utils/resolveIrohBlobs.ts +117 -0
  122. package/src/explorer-component/utils/statementHelpers.ts +252 -0
  123. package/src/explorer-component/utils/stripPrefix.ts +4 -0
  124. package/src/hooks/use-mobile.tsx +19 -0
  125. package/src/index.ts +3 -0
  126. package/src/lib/resource-types.ts +31 -0
  127. package/src/lib/use-escape-key.tsx +16 -0
  128. package/src/lib/utils.ts +19 -0
  129. package/src/main.css +82 -0
  130. package/src/main.tsx +39 -0
  131. package/src/v-comp-viewer/components/container-did-details.tsx +53 -0
  132. package/src/v-comp-viewer/components/system-did-details.tsx +356 -0
  133. package/src/v-comp-viewer/useVComp.ts +196 -0
  134. package/src/v-comp-viewer/utils.tsx +824 -0
  135. package/src/vite-env.d.ts +1 -0
  136. package/tsconfig.json +33 -0
  137. package/tsconfig.node.json +10 -0
  138. package/vercel.json +13 -0
  139. package/vite.app.config.ts +48 -0
  140. package/vite.lib.config.ts +61 -0
@@ -0,0 +1,52 @@
1
+ // Framer Motion animation variants
2
+ import { cubicBezier } from "framer-motion";
3
+
4
+ const easeOut = cubicBezier(0.25, 0.46, 0.45, 0.94);
5
+ const easeInOut = cubicBezier(0.42, 0, 0.58, 1);
6
+
7
+ export const expandAndCollapseVariant = {
8
+ expanded: {
9
+ opacity: 1,
10
+ height: "auto",
11
+ transition: {
12
+ duration: 0.5,
13
+ opacity: {
14
+ delay: 0.2,
15
+ duration: 0.4,
16
+ ease: easeOut,
17
+ },
18
+ },
19
+ },
20
+ collapsed: {
21
+ opacity: 0,
22
+ height: 0,
23
+ overflow: "hidden",
24
+ transition: {
25
+ duration: 0.4,
26
+ ease: easeOut,
27
+ },
28
+ },
29
+ };
30
+
31
+ export const slideInFromRightVariant = (delay: number = 0) => ({
32
+ hidden: {
33
+ x: "100%",
34
+ transition: {
35
+ duration: 0.4,
36
+ ease: easeOut,
37
+ },
38
+ },
39
+ visible: {
40
+ x: 0,
41
+ transition: {
42
+ duration: 0.5,
43
+ ease: easeOut,
44
+ delay,
45
+ },
46
+ },
47
+ });
48
+
49
+ export const fadeInAndOutVariant = {
50
+ hidden: { opacity: 0, transition: { duration: 0.3, ease: easeInOut } },
51
+ visible: { opacity: 1, transition: { duration: 0.5, ease: easeInOut } },
52
+ };
@@ -0,0 +1,29 @@
1
+ import { PossibleAttributeList } from "./types";
2
+
3
+ export async function fetchPossibleAttributes(
4
+ token: string,
5
+ integrityServiceUrl: string,
6
+ ): Promise<PossibleAttributeList> {
7
+ const url = `${integrityServiceUrl}/manifest/v1/filters`;
8
+
9
+ const response = await fetch(url, {
10
+ headers: {
11
+ Authorization: `Bearer ${token}`,
12
+ },
13
+ });
14
+
15
+ if (!response.ok) {
16
+ throw new Error(`HTTP error! status: ${response.status}`);
17
+ }
18
+
19
+ const json = await response.json();
20
+
21
+ const sortedData = Object.keys(json)
22
+ .sort()
23
+ .reduce((acc: PossibleAttributeList, key: string) => {
24
+ acc[key] = json[key];
25
+ return acc;
26
+ }, {});
27
+
28
+ return sortedData;
29
+ }
@@ -0,0 +1,28 @@
1
+ import { SelectedAttributeList } from "./types";
2
+
3
+ export async function fetchManifest(
4
+ token: string,
5
+ integrityServiceUrl: string,
6
+ selectedAttributes: SelectedAttributeList,
7
+ ): Promise<any> {
8
+ let params = "include_context=false";
9
+ for (const [key, value] of Object.entries(selectedAttributes)) {
10
+ if (value.length > 0) {
11
+ params += `&${key}=${value}`;
12
+ }
13
+ }
14
+
15
+ const response = await fetch(`${integrityServiceUrl}/manifest/v1?${params}`, {
16
+ headers: {
17
+ Authorization: `Bearer ${token}`,
18
+ },
19
+ });
20
+
21
+ if (!response.ok) {
22
+ throw new Error(`HTTP error! status: ${response.status}`);
23
+ }
24
+
25
+ const manifest = await response.json();
26
+
27
+ return manifest;
28
+ }
@@ -0,0 +1,12 @@
1
+ export interface PossibleAttributeSummary {
2
+ n: number;
3
+ values?: any[];
4
+ }
5
+
6
+ export interface PossibleAttributeList {
7
+ [key: string]: PossibleAttributeSummary;
8
+ }
9
+
10
+ export interface SelectedAttributeList {
11
+ [key: string]: any;
12
+ }
@@ -0,0 +1,105 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ import FiltersToggle from "@/app/components/integrityService/components/FiltersToggle";
4
+
5
+ import { LineageExplorer } from "@/explorer-component";
6
+ import { useNavigate } from "react-router-dom";
7
+ import { useManifestStore } from "@/app/stores/manifestStore";
8
+ import { fetchPossibleAttributes } from "@/app/apiClient/integrityService/fetchAttributes";
9
+ import { fetchManifest } from "@/app/apiClient/integrityService/fetchManifest";
10
+ import { DarkModeSwitch } from "@/components/dark-mode-switch";
11
+
12
+ import AuthButton from "./components/AuthButton";
13
+ import { useAuth0 } from "@auth0/auth0-react";
14
+ import { PossibleAttributeList, SelectedAttributeList } from "@/app/apiClient/integrityService/types";
15
+ import { Config } from "@/app/config";
16
+ import EQTYLabLogo from "@/assets/vectors/eqtylab.svg";
17
+ import { useVComp } from "@/v-comp-viewer/useVComp";
18
+
19
+ interface IntegrityServiceProps {
20
+ searchParams: URLSearchParams;
21
+ config: Config;
22
+ }
23
+
24
+ const IntegrityService = ({ searchParams, config }: IntegrityServiceProps) => {
25
+ const navigate = useNavigate();
26
+ const { isAuthenticated } = useAuth0();
27
+ const { getAccessTokenSilently } = useAuth0();
28
+ const [integrityServiceUrl, setIntegrityServiceUrl] = useState("");
29
+ const [possibleAttributeList, setPossibleAttributeList] = useState<PossibleAttributeList>({});
30
+ const [attributeFilterCount, setAttributeFilterCount] = useState<number>(0);
31
+ const { manifest } = useManifestStore.getState();
32
+
33
+ useEffect(() => {
34
+ const integrityServiceUrl = searchParams.get("integrity_service_url");
35
+ if (integrityServiceUrl) {
36
+ setIntegrityServiceUrl(integrityServiceUrl);
37
+ } else {
38
+ setIntegrityServiceUrl(import.meta.env.VITE_INTEGRITY_SERVICE_URL);
39
+ }
40
+ }, [searchParams]);
41
+
42
+ useEffect(() => {
43
+ (async () => {
44
+ const token = await getAccessTokenSilently();
45
+ const list = await fetchPossibleAttributes(token, integrityServiceUrl);
46
+ setPossibleAttributeList(list);
47
+ })();
48
+ }, [integrityServiceUrl, isAuthenticated]);
49
+
50
+ const getManifestFromIntegrityService = async (attributeFilters: SelectedAttributeList) => {
51
+ setAttributeFilterCount(Object.entries(attributeFilters).length);
52
+ const token = await getAccessTokenSilently();
53
+ const manifest = await fetchManifest(token, integrityServiceUrl, attributeFilters);
54
+ const { setManifest } = useManifestStore.getState();
55
+ setManifest(manifest);
56
+ navigate(`/?${searchParams.toString()}`);
57
+ };
58
+
59
+ const { vCompData, vCompVerifyData } = useVComp({ manifestJson: manifest });
60
+
61
+ return (
62
+ <div className="flex flex-col h-screen w-screen overflow-hidden">
63
+ <div className="w-full border-b border-border/10 bg-background py-3.5 px-4 z-50">
64
+ <div className="flex w-full items-center justify-between">
65
+ <div className="flex items-center gap-3">
66
+ <div className="[&>svg]:h-auto [&>svg]:w-28 [&>svg]:fill-text-primary">
67
+ <EQTYLabLogo />
68
+ </div>
69
+ <div className="h-6 w-px bg-text-primary/20" />
70
+ <span className="text-lg text-brand-lineage-explorer">Lineage Explorer</span>
71
+ </div>
72
+ <div className="flex items-center gap-4">
73
+ <DarkModeSwitch />
74
+ {isAuthenticated && (
75
+ <FiltersToggle
76
+ label={
77
+ attributeFilterCount > 0
78
+ ? `Filters Applied (${attributeFilterCount})`
79
+ : "Filter Results"
80
+ }
81
+ allAttributesList={possibleAttributeList}
82
+ handleApply={getManifestFromIntegrityService}
83
+ />
84
+ )}
85
+ <AuthButton />
86
+ </div>
87
+ </div>
88
+ </div>
89
+ {isAuthenticated ? (
90
+ <LineageExplorer
91
+ manifestJson={manifest}
92
+ config={config}
93
+ vCompData={vCompData}
94
+ vCompVerifyData={vCompVerifyData}
95
+ />
96
+ ) : (
97
+ <div className="flex flex-col items-center justify-center h-full">
98
+ <p className="text-lg font-medium">You must log in to view the lineage</p>
99
+ </div>
100
+ )}
101
+ </div>
102
+ );
103
+ };
104
+
105
+ export default IntegrityService;
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import { useAuth0 } from "@auth0/auth0-react";
3
+ import { Button } from "@eqtylab/equality";
4
+
5
+ const AuthButton: React.FC = () => {
6
+ const { isAuthenticated, loginWithRedirect, logout } = useAuth0();
7
+
8
+ return (
9
+ <div>
10
+ {isAuthenticated ? (
11
+ <Button
12
+ variant="primary"
13
+ onClick={() => logout({ logoutParams: { returnTo: window.location.origin } })}
14
+ >
15
+ Log out
16
+ </Button>
17
+ ) : (
18
+ <Button variant="primary" onClick={() => loginWithRedirect()}>
19
+ Log in
20
+ </Button>
21
+ )}
22
+ </div>
23
+ );
24
+ };
25
+
26
+ export default AuthButton;
@@ -0,0 +1,214 @@
1
+ import { useEffect, useState } from "react";
2
+ import {
3
+ Select,
4
+ SelectContent,
5
+ SelectItem,
6
+ SelectTrigger,
7
+ DropdownMenu,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ DropdownMenuTrigger,
11
+ Button,
12
+ IconButton,
13
+ TooltipProvider,
14
+ Tooltip,
15
+ TooltipContent,
16
+ TooltipTrigger,
17
+ Label,
18
+ } from "@eqtylab/equality";
19
+
20
+ import {
21
+ PossibleAttributeList,
22
+ PossibleAttributeSummary,
23
+ SelectedAttributeList,
24
+ } from "@/app/apiClient/integrityService/types";
25
+
26
+ import { CirclePlus } from "lucide-react";
27
+
28
+ interface FiltersModalProps {
29
+ // All possible filterable attributes, as returned from the integrity service
30
+ allAttributesList: PossibleAttributeList;
31
+ handleApply: (attributeFilters: SelectedAttributeList) => Promise<void>;
32
+ }
33
+
34
+ export const FiltersModal = ({ allAttributesList, handleApply }: FiltersModalProps) => {
35
+ const [selectedAttributes, setSelectedAttributes] = useState<SelectedAttributeList>({});
36
+ // The filters that have already been selected and/or applied
37
+ const [selectedFilters, setSelectedFilters] = useState<PossibleAttributeList>({});
38
+ // Attributes the user hasn't added to the filter list
39
+ const [unusedAttributesList, setUnusedAttributesList] = useState<PossibleAttributeList>({});
40
+ // The options available to add to the filter list
41
+ const [filterOptions, setFilterOptions] = useState<any[]>([]);
42
+
43
+ // Initialize unusedAttributesList based on allAttributesList and selectedFilters
44
+ useEffect(() => {
45
+ const unusedAttrs = Object.keys(allAttributesList).reduce((acc, key) => {
46
+ if (!selectedFilters[key]) {
47
+ acc[key] = allAttributesList[key];
48
+ }
49
+ return acc;
50
+ }, {} as PossibleAttributeList);
51
+ setUnusedAttributesList(unusedAttrs);
52
+ }, [allAttributesList, selectedFilters]);
53
+
54
+ // Update the "add new filter button" based on available and unselected filters
55
+ useEffect(() => {
56
+ const options = Object.entries(unusedAttributesList).length
57
+ ? [
58
+ ...Object.entries(unusedAttributesList).map(([attr, v]) => ({
59
+ key: attr,
60
+ label: attr.replace(/_/g, " "),
61
+ onClick: () => handleNewFilterSelected(attr, v),
62
+ })),
63
+ ]
64
+ : [];
65
+
66
+ setFilterOptions(options);
67
+ }, [unusedAttributesList]);
68
+
69
+ // Add filter selection dropdown
70
+ const handleNewFilterSelected = (attribute: string, summary: PossibleAttributeSummary) => {
71
+ // Update the unusedAttributesList to exclude the newly selected filter
72
+ setUnusedAttributesList((prevList) => {
73
+ const newList = { ...prevList };
74
+ delete newList[attribute];
75
+ return newList;
76
+ });
77
+
78
+ // Add the selected attribute to the list of selected filters
79
+ setSelectedFilters((prevList: PossibleAttributeList) => ({
80
+ ...prevList,
81
+ [attribute]: summary,
82
+ }));
83
+ };
84
+
85
+ // Reset filters back to initial view
86
+ const handleReset = () => {
87
+ setSelectedFilters({});
88
+ setSelectedAttributes({});
89
+ setUnusedAttributesList(allAttributesList);
90
+ };
91
+
92
+ // Handler for when an attribute filter is UN-selected.
93
+ const handleUnselectedFilter = (attr: string) => {
94
+ const newSelectedAttributes = { ...selectedAttributes };
95
+ delete newSelectedAttributes[attr];
96
+ setSelectedAttributes(newSelectedAttributes);
97
+ };
98
+
99
+ // Handler for when an attribute filter is selected.
100
+ const handleSelectFilter = (attr: string, value: string) => {
101
+ setSelectedAttributes((prev) => {
102
+ const updatedAttributes = { ...prev };
103
+ updatedAttributes[attr] = value;
104
+ return updatedAttributes;
105
+ });
106
+ };
107
+
108
+ // Handler for removing a filter option from the list of active filters, and returning it to the unusedAttributeList
109
+ const handleRemoveFilter = (attr: string) => {
110
+ setSelectedFilters((prevList) => {
111
+ const newList = { ...prevList };
112
+ delete newList[attr];
113
+ return newList;
114
+ });
115
+
116
+ setUnusedAttributesList((prevList) => ({
117
+ ...prevList,
118
+ [attr]: allAttributesList[attr],
119
+ }));
120
+
121
+ handleUnselectedFilter(attr);
122
+ };
123
+
124
+ return (
125
+ <div className="space-y-3">
126
+ <h3 className="text-lg font-medium">Filter Results</h3>
127
+ <div className="grid grid-cols-3 gap-2">
128
+ {Object.entries(selectedFilters).map(([attr, v], i) => {
129
+ const label = attr.replace(/_/g, " ");
130
+ const selectedValue = selectedAttributes[attr];
131
+
132
+ const options = v.values
133
+ ? [
134
+ ...v.values.map((value) => ({
135
+ key: value,
136
+ label: value,
137
+ onClick: () => {
138
+ handleSelectFilter(attr, value);
139
+ if (selectedValue === value) handleUnselectedFilter(attr);
140
+ },
141
+ })),
142
+ ]
143
+ : [];
144
+
145
+ return (
146
+ <div key={`d1-${i}`} className="space-y-1">
147
+ <div className="flex items-center justify-between gap-2 pr-1">
148
+ <Label className="capitalize">{label}</Label>
149
+ <IconButton name="X" size="sm" onClick={() => handleRemoveFilter(attr)}></IconButton>
150
+ </div>
151
+ <Select
152
+ value={selectedValue}
153
+ onValueChange={(value) => {
154
+ handleSelectFilter(attr, value);
155
+ if (selectedValue === value) handleUnselectedFilter(attr);
156
+ }}
157
+ >
158
+ <SelectTrigger>
159
+ <span>
160
+ {selectedValue?.length > 24 ? (
161
+ <TooltipProvider>
162
+ <Tooltip>
163
+ <TooltipTrigger asChild>
164
+ <span>{selectedValue.substring(0, 24 - 3) + "..."}</span>
165
+ </TooltipTrigger>
166
+ <TooltipContent>{selectedValue}</TooltipContent>
167
+ </Tooltip>
168
+ </TooltipProvider>
169
+ ) : selectedValue ? (
170
+ <span>{selectedValue}</span>
171
+ ) : (
172
+ "Select..."
173
+ )}
174
+ </span>
175
+ </SelectTrigger>
176
+ <SelectContent>
177
+ {options.map((option) => (
178
+ <SelectItem key={option.key} value={option.key} onClick={option.onClick}>
179
+ {option.label}
180
+ </SelectItem>
181
+ ))}
182
+ </SelectContent>
183
+ </Select>
184
+ </div>
185
+ );
186
+ })}
187
+ </div>
188
+ <div className="flex gap-2">
189
+ <Button variant="tertiary" size="sm" onClick={() => handleApply(selectedAttributes)}>
190
+ Apply
191
+ </Button>
192
+ {filterOptions.length > 0 && (
193
+ <DropdownMenu>
194
+ <DropdownMenuTrigger asChild>
195
+ <Button size="sm" variant="tertiary" suffix={<CirclePlus />}>
196
+ Add new filter
197
+ </Button>
198
+ </DropdownMenuTrigger>
199
+ <DropdownMenuContent align="start">
200
+ {filterOptions.map((option) => (
201
+ <DropdownMenuItem key={option.key} onSelect={option.onClick}>
202
+ {option.label}
203
+ </DropdownMenuItem>
204
+ ))}
205
+ </DropdownMenuContent>
206
+ </DropdownMenu>
207
+ )}
208
+ <Button variant="tertiary" size="sm" onClick={handleReset}>
209
+ Reset
210
+ </Button>
211
+ </div>
212
+ </div>
213
+ );
214
+ };
@@ -0,0 +1,28 @@
1
+ import { ListFilter } from "lucide-react";
2
+ import { Button, Popover, PopoverContent, PopoverTrigger } from "@eqtylab/equality";
3
+
4
+ import { FiltersModal } from "./FiltersModal";
5
+ import { PossibleAttributeList, SelectedAttributeList } from "@/app/apiClient/integrityService/types";
6
+
7
+ interface FiltersToggleProps {
8
+ label: string;
9
+ allAttributesList: PossibleAttributeList;
10
+ handleApply: (attributeFilters: SelectedAttributeList) => Promise<void>;
11
+ }
12
+
13
+ const FiltersToggle = ({ label, allAttributesList, handleApply }: FiltersToggleProps) => {
14
+ return (
15
+ <Popover>
16
+ <PopoverTrigger asChild>
17
+ <Button variant="tertiary" prefix={<ListFilter />}>
18
+ {label}
19
+ </Button>
20
+ </PopoverTrigger>
21
+ <PopoverContent className="w-175!">
22
+ <FiltersModal allAttributesList={allAttributesList} handleApply={handleApply} />
23
+ </PopoverContent>
24
+ </Popover>
25
+ );
26
+ };
27
+
28
+ export default FiltersToggle;
@@ -0,0 +1,90 @@
1
+ import { useRevalidator } from "react-router-dom";
2
+
3
+ import { LineageExplorer } from "@/explorer-component";
4
+ import { useManifestStore } from "../../stores/manifestStore";
5
+ import { AppHeader } from "@/components/app-header";
6
+ import { Config } from "@/app/config";
7
+ import { useVComp } from "@/v-comp-viewer/useVComp";
8
+
9
+ export interface AttributeList {
10
+ [key: string]: {
11
+ values?: string[];
12
+ description?: string;
13
+ type?: string;
14
+ };
15
+ }
16
+
17
+ interface ManifestProps {
18
+ config: Config;
19
+ customIcons?: Record<string, string>;
20
+ }
21
+
22
+ async function getManifestSampleFromPath(path: string): Promise<string | undefined> {
23
+ const fullPath = `/manifests/${path}.json`;
24
+ try {
25
+ const response = await fetch(fullPath);
26
+ return await response.json();
27
+ } catch (error) {
28
+ console.error(`Failed to load JSON from path: ${path}`, error);
29
+ return undefined;
30
+ }
31
+ }
32
+
33
+ const ManifestViewer = ({ config, customIcons }: ManifestProps) => {
34
+ const { setManifest, manifest: manifestJson } = useManifestStore();
35
+ const revalidator = useRevalidator();
36
+
37
+ const useSampleManifest = async (event: string) => {
38
+ const sampleData = await getManifestSampleFromPath(event);
39
+
40
+ if (sampleData) {
41
+ setManifest(sampleData);
42
+ revalidator.revalidate();
43
+ }
44
+ };
45
+
46
+ const handleFileChange = (file: File) => {
47
+ if (!file) return;
48
+
49
+ const reader = new FileReader();
50
+ reader.onload = (e) => {
51
+ if (e.target) {
52
+ const fileContent = e.target.result as string;
53
+ setManifest(JSON.parse(fileContent));
54
+ revalidator.revalidate();
55
+ }
56
+ };
57
+ reader.readAsText(file);
58
+ };
59
+
60
+ const { vCompData, vCompVerifyData, uploads, handleDetectedUploads, onFilesSelected, handleDeleteUpload } =
61
+ useVComp({
62
+ manifestJson,
63
+ });
64
+
65
+ return (
66
+ <div className="flex flex-col h-screen w-screen overflow-hidden">
67
+ <AppHeader
68
+ config={config}
69
+ useSampleManifest={useSampleManifest}
70
+ handleFileChange={handleFileChange}
71
+ allowArtifactsUploads={!!vCompData}
72
+ handleDetectedUploads={handleDetectedUploads}
73
+ onFilesSelected={onFilesSelected}
74
+ uploads={uploads}
75
+ hasManifest={!!manifestJson}
76
+ handleDeleteManifest={() => setManifest(null)}
77
+ handleDeleteUpload={handleDeleteUpload}
78
+ />
79
+ <LineageExplorer
80
+ manifestJson={manifestJson}
81
+ config={config}
82
+ customIcons={customIcons}
83
+ vCompData={vCompData}
84
+ vCompVerifyData={vCompVerifyData}
85
+ />
86
+ </div>
87
+ );
88
+ };
89
+
90
+ export default ManifestViewer;