@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.
- package/.env.api +6 -0
- package/.env.iframe +2 -0
- package/.env.manifest +2 -0
- package/.envrc +1 -0
- package/.eslintrc.cjs +17 -0
- package/.github/workflows/ci.yml +46 -0
- package/.github/workflows/publish.yml +44 -0
- package/.husky/pre-push +2 -0
- package/.prettierignore +2 -0
- package/.prettierrc.json +13 -0
- package/LICENSE +201 -0
- package/README.md +84 -0
- package/flake.lock +61 -0
- package/flake.nix +23 -0
- package/index.html +13 -0
- package/package.json +93 -0
- package/postcss.config.js +5 -0
- package/public/logos/eci.svg +23 -0
- package/public/logos/eqty.svg +3 -0
- package/public/manifests/B-200-merged.json +773 -0
- package/public/manifests/amd-sev.json +426 -0
- package/public/manifests/anchors.json +609 -0
- package/public/manifests/association-no-metadata.json +1393 -0
- package/public/manifests/association.json +1402 -0
- package/public/manifests/eqty-docker.json +562 -0
- package/public/manifests/governance.json +1324 -0
- package/public/manifests/intel-tdx-with-nvidia.json +441 -0
- package/public/manifests/intel-tdx.json +404 -0
- package/public/manifests/iroh-collections.json +1285 -0
- package/public/manifests/model-signing.json +1536 -0
- package/public/manifests/multiple-registrations.json +129 -0
- package/public/manifests/no-vcs.json +114 -0
- package/public/manifests/storage.json +294 -0
- package/public/manifests/sub-graphs.json +1 -0
- package/src/animations/variants.ts +52 -0
- package/src/app/apiClient/integrityService/fetchAttributes.ts +29 -0
- package/src/app/apiClient/integrityService/fetchManifest.ts +28 -0
- package/src/app/apiClient/integrityService/types.ts +12 -0
- package/src/app/components/integrityService/IntegrityService.tsx +105 -0
- package/src/app/components/integrityService/components/AuthButton.tsx +26 -0
- package/src/app/components/integrityService/components/FiltersModal.tsx +214 -0
- package/src/app/components/integrityService/components/FiltersToggle.tsx +28 -0
- package/src/app/components/manifestViewer/ManifestViewer.tsx +90 -0
- package/src/app/components/manifestViewer/components/ManifestButtons.tsx +215 -0
- package/src/app/config.json +23 -0
- package/src/app/config.ts +28 -0
- package/src/app/pages/index.tsx +34 -0
- package/src/app/stores/manifestStore.ts +19 -0
- package/src/app/utils/appMode.ts +37 -0
- package/src/assets/fonts/TWKLausanne-300.woff +0 -0
- package/src/assets/fonts/TWKLausanne-300.woff2 +0 -0
- package/src/assets/fonts/TWKLausanne-400.woff +0 -0
- package/src/assets/fonts/TWKLausanne-400.woff2 +0 -0
- package/src/assets/fonts/TWKLausanne-500.woff +0 -0
- package/src/assets/fonts/TWKLausanne-500.woff2 +0 -0
- package/src/assets/fonts/TWKLausanne-600.woff +0 -0
- package/src/assets/fonts/TWKLausanne-600.woff2 +0 -0
- package/src/assets/vectors/eqtylab.svg +6 -0
- package/src/components/app-header.tsx +61 -0
- package/src/components/dark-mode-switch.tsx +16 -0
- package/src/components/ui/accordion.tsx +47 -0
- package/src/components/ui/certificate-chain.tsx +60 -0
- package/src/components/ui/certificate-code.tsx +39 -0
- package/src/components/ui/details.tsx +20 -0
- package/src/components/ui/display-code.tsx +47 -0
- package/src/components/ui/panel-label.tsx +12 -0
- package/src/components/ui/verify-state-badge.tsx +32 -0
- package/src/explorer-component/LineageExplorer.tsx +89 -0
- package/src/explorer-component/components/CustomEdge.tsx +28 -0
- package/src/explorer-component/components/LineageFlow.tsx +220 -0
- package/src/explorer-component/components/NodeIconLoader.tsx +26 -0
- package/src/explorer-component/components/nodes/ComputationNode.tsx +56 -0
- package/src/explorer-component/components/nodes/CustomNode.tsx +112 -0
- package/src/explorer-component/components/nodes/index.ts +4 -0
- package/src/explorer-component/components/nodes/nodes.tsx +144 -0
- package/src/explorer-component/components/nodes/types.ts +167 -0
- package/src/explorer-component/components/sidebar/SectionList.tsx +54 -0
- package/src/explorer-component/components/sidebar/SectionTitle.tsx +10 -0
- package/src/explorer-component/components/sidebar/Sidebar.tsx +70 -0
- package/src/explorer-component/components/sidebar/SidebarWrapper.tsx +76 -0
- package/src/explorer-component/components/sidebar/sections/CustomSection.tsx +238 -0
- package/src/explorer-component/components/sidebar/sections/FileViewer.tsx +229 -0
- package/src/explorer-component/components/sidebar/sections/GovernanceSection.tsx +52 -0
- package/src/explorer-component/components/sidebar/sections/RegistrationSection.tsx +254 -0
- package/src/explorer-component/components/sidebar/sections/SigstoreSection.tsx +37 -0
- package/src/explorer-component/components/sidebar/sections/StorageSection.tsx +73 -0
- package/src/explorer-component/components/sidebar/sections/custom/IrohCollectionsView.tsx +139 -0
- package/src/explorer-component/components/sidebar/sections/registration/VerificationIcon.tsx +108 -0
- package/src/explorer-component/components/sidebar/sections/registration/actor/ActorBadge.tsx +62 -0
- package/src/explorer-component/components/sidebar/sections/registration/actor/svg/AMDLogo.tsx +40 -0
- package/src/explorer-component/components/sidebar/sections/registration/actor/svg/AzureLogo.tsx +65 -0
- package/src/explorer-component/components/sidebar/sections/registration/actor/svg/IntelLogo.tsx +24 -0
- package/src/explorer-component/components/sidebar/sections/registration/actor/svg/NvidiaLogo.tsx +27 -0
- package/src/explorer-component/components/sidebar/sections/registration/verifierClient.ts +13 -0
- package/src/explorer-component/icons/AttributionIcon.svg +6 -0
- package/src/explorer-component/icons/BenchmarkIcon.svg +6 -0
- package/src/explorer-component/icons/BenchmarkResultIcon.svg +6 -0
- package/src/explorer-component/icons/CertificateIcon.svg +6 -0
- package/src/explorer-component/icons/CodeIcon.svg +6 -0
- package/src/explorer-component/icons/ComputationIcon.svg +6 -0
- package/src/explorer-component/icons/DataIcon.svg +6 -0
- package/src/explorer-component/icons/DatabaseIcon.svg +6 -0
- package/src/explorer-component/icons/DatasetIcon.svg +6 -0
- package/src/explorer-component/icons/DeltaLakeIcon.svg +7 -0
- package/src/explorer-component/icons/DocumentIcon.svg +6 -0
- package/src/explorer-component/icons/MediaIcon.svg +6 -0
- package/src/explorer-component/icons/ModelIcon.svg +6 -0
- package/src/explorer-component/icons/TeamIcon.svg +3 -0
- package/src/explorer-component/icons/TokenIcon.svg +6 -0
- package/src/explorer-component/icons/UnknownIcon.svg +6 -0
- package/src/explorer-component/icons/index.ts +35 -0
- package/src/explorer-component/index.ts +5 -0
- package/src/explorer-component/integrityTypes/AnchorTypes.ts +31 -0
- package/src/explorer-component/integrityTypes/StatementTypes.ts +333 -0
- package/src/explorer-component/integrityTypes/VCTypes.ts +85 -0
- package/src/explorer-component/integrityTypes/types.ts +3 -0
- package/src/explorer-component/utils/config.ts +163 -0
- package/src/explorer-component/utils/formatTimestamp.ts +5 -0
- package/src/explorer-component/utils/generateGraph.ts +271 -0
- package/src/explorer-component/utils/layoutGraph.ts +148 -0
- package/src/explorer-component/utils/resolveIrohBlobs.ts +117 -0
- package/src/explorer-component/utils/statementHelpers.ts +252 -0
- package/src/explorer-component/utils/stripPrefix.ts +4 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/index.ts +3 -0
- package/src/lib/resource-types.ts +31 -0
- package/src/lib/use-escape-key.tsx +16 -0
- package/src/lib/utils.ts +19 -0
- package/src/main.css +82 -0
- package/src/main.tsx +39 -0
- package/src/v-comp-viewer/components/container-did-details.tsx +53 -0
- package/src/v-comp-viewer/components/system-did-details.tsx +356 -0
- package/src/v-comp-viewer/useVComp.ts +196 -0
- package/src/v-comp-viewer/utils.tsx +824 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +33 -0
- package/tsconfig.node.json +10 -0
- package/vercel.json +13 -0
- package/vite.app.config.ts +48 -0
- 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,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;
|