Skip to content

Commit cb2fc10

Browse files
feat: add ai models to sbom details page (#968)
Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
1 parent 3428706 commit cb2fc10

File tree

8 files changed

+495
-3
lines changed

8 files changed

+495
-3
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type React from "react";
2+
3+
import { Bullseye, Spinner } from "@patternfly/react-core";
4+
5+
import { StateError } from "@app/components/StateError";
6+
import { StateNoData } from "@app/components/StateNoData";
7+
8+
export interface IConditionalDataListBodyProps {
9+
isLoading?: boolean;
10+
isError?: boolean;
11+
isNoData?: boolean;
12+
errorEmptyState?: React.ReactNode;
13+
noDataEmptyState?: React.ReactNode;
14+
children: React.ReactNode;
15+
}
16+
17+
export const ConditionalDataListBody: React.FC<
18+
IConditionalDataListBodyProps
19+
> = ({
20+
isLoading = false,
21+
isError = false,
22+
isNoData = false,
23+
errorEmptyState = null,
24+
noDataEmptyState = null,
25+
children,
26+
}) => (
27+
<>
28+
{isLoading ? (
29+
<Bullseye>
30+
<Spinner size="xl" />
31+
</Bullseye>
32+
) : isError ? (
33+
<Bullseye>{errorEmptyState || <StateError />}</Bullseye>
34+
) : isNoData ? (
35+
<Bullseye>{noDataEmptyState || <StateNoData />}</Bullseye>
36+
) : (
37+
children
38+
)}
39+
</>
40+
);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { ConditionalDataListBody } from "./ConditionalDataListBody";
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import type React from "react";
2+
3+
import {
4+
Button,
5+
Card,
6+
CardBody,
7+
CardTitle,
8+
DescriptionList,
9+
DescriptionListDescription,
10+
DescriptionListGroup,
11+
DescriptionListTerm,
12+
Label,
13+
Stack,
14+
StackItem,
15+
} from "@patternfly/react-core";
16+
import ExternalLinkAltIcon from "@patternfly/react-icons/dist/esm/icons/external-link-alt-icon";
17+
18+
import type { SbomModel } from "@app/client";
19+
20+
export interface ModelProperties {
21+
version?: string;
22+
licenses?: string;
23+
bomFormat?: string;
24+
suppliedBy?: string;
25+
specVersion?: string;
26+
typeOfModel?: string;
27+
serialNumber?: string;
28+
primaryPurpose?: string;
29+
downloadLocation?: string;
30+
external_references?: string;
31+
limitation?: string;
32+
safetyRiskAssessment?: string;
33+
}
34+
35+
export interface ExternalReference {
36+
type: string;
37+
url: string;
38+
comment?: string;
39+
}
40+
41+
export const getModelProperties = (properties: unknown): ModelProperties => {
42+
if (properties && typeof properties === "object") {
43+
return properties as ModelProperties;
44+
}
45+
return {};
46+
};
47+
48+
const parseExternalReferences = (json?: string): ExternalReference[] => {
49+
if (!json) return [];
50+
try {
51+
const parsed = JSON.parse(json);
52+
return Array.isArray(parsed) ? parsed : [];
53+
} catch {
54+
return [];
55+
}
56+
};
57+
58+
interface ModelDetailDrawerProps {
59+
model: SbomModel;
60+
}
61+
62+
export const ModelDetailDrawer: React.FC<ModelDetailDrawerProps> = ({
63+
model,
64+
}) => {
65+
const props = getModelProperties(model.properties);
66+
const externalRefs = parseExternalReferences(props.external_references);
67+
68+
return (
69+
<Stack hasGutter>
70+
<StackItem>
71+
<Card isCompact>
72+
<CardTitle>Identity & Purpose</CardTitle>
73+
<CardBody>
74+
<DescriptionList
75+
isCompact
76+
columnModifier={{
77+
default: "2Col",
78+
}}
79+
>
80+
{props.typeOfModel && (
81+
<DescriptionListGroup>
82+
<DescriptionListTerm>Model type</DescriptionListTerm>
83+
<DescriptionListDescription>
84+
{props.typeOfModel}
85+
</DescriptionListDescription>
86+
</DescriptionListGroup>
87+
)}
88+
{props.primaryPurpose && (
89+
<DescriptionListGroup>
90+
<DescriptionListTerm>Primary purpose</DescriptionListTerm>
91+
<DescriptionListDescription>
92+
<Label color="blue">{props.primaryPurpose}</Label>
93+
</DescriptionListDescription>
94+
</DescriptionListGroup>
95+
)}
96+
{props.licenses && (
97+
<DescriptionListGroup>
98+
<DescriptionListTerm>License</DescriptionListTerm>
99+
<DescriptionListDescription>
100+
{props.licenses}
101+
</DescriptionListDescription>
102+
</DescriptionListGroup>
103+
)}
104+
{props.suppliedBy && (
105+
<DescriptionListGroup>
106+
<DescriptionListTerm>Supplied by</DescriptionListTerm>
107+
<DescriptionListDescription>
108+
{props.suppliedBy}
109+
</DescriptionListDescription>
110+
</DescriptionListGroup>
111+
)}
112+
</DescriptionList>
113+
</CardBody>
114+
</Card>
115+
</StackItem>
116+
117+
<StackItem>
118+
<Card isCompact>
119+
<CardTitle>SBOM Metadata</CardTitle>
120+
<CardBody>
121+
<DescriptionList
122+
isCompact
123+
columnModifier={{
124+
default: "2Col",
125+
}}
126+
>
127+
{props.bomFormat && (
128+
<DescriptionListGroup>
129+
<DescriptionListTerm>Format</DescriptionListTerm>
130+
<DescriptionListDescription>
131+
{props.bomFormat}
132+
</DescriptionListDescription>
133+
</DescriptionListGroup>
134+
)}
135+
{props.specVersion && (
136+
<DescriptionListGroup>
137+
<DescriptionListTerm>Spec version</DescriptionListTerm>
138+
<DescriptionListDescription>
139+
{props.specVersion}
140+
</DescriptionListDescription>
141+
</DescriptionListGroup>
142+
)}
143+
{props.serialNumber && (
144+
<DescriptionListGroup>
145+
<DescriptionListTerm>Serial number</DescriptionListTerm>
146+
<DescriptionListDescription>
147+
{props.serialNumber}
148+
</DescriptionListDescription>
149+
</DescriptionListGroup>
150+
)}
151+
{props.version && (
152+
<DescriptionListGroup>
153+
<DescriptionListTerm>Manifest version</DescriptionListTerm>
154+
<DescriptionListDescription>
155+
{props.version}
156+
</DescriptionListDescription>
157+
</DescriptionListGroup>
158+
)}
159+
</DescriptionList>
160+
</CardBody>
161+
</Card>
162+
</StackItem>
163+
164+
{externalRefs.length > 0 && (
165+
<StackItem>
166+
<Card isCompact>
167+
<CardTitle>External References</CardTitle>
168+
<CardBody>
169+
<DescriptionList isCompact>
170+
{externalRefs.map((ref) => (
171+
<DescriptionListGroup key={`${ref.type}-${ref.url}`}>
172+
<DescriptionListTerm>{ref.type}</DescriptionListTerm>
173+
<DescriptionListDescription>
174+
<a
175+
href={ref.url}
176+
target="_blank"
177+
rel="noopener noreferrer"
178+
>
179+
{ref.comment || ref.url} <ExternalLinkAltIcon />
180+
</a>
181+
</DescriptionListDescription>
182+
</DescriptionListGroup>
183+
))}
184+
</DescriptionList>
185+
</CardBody>
186+
</Card>
187+
</StackItem>
188+
)}
189+
190+
{props.downloadLocation && (
191+
<StackItem>
192+
<Button
193+
variant="secondary"
194+
component="a"
195+
href={props.downloadLocation}
196+
target="_blank"
197+
rel="noopener noreferrer"
198+
icon={<ExternalLinkAltIcon />}
199+
iconPosition="end"
200+
>
201+
Download
202+
</Button>
203+
</StackItem>
204+
)}
205+
</Stack>
206+
);
207+
};

0 commit comments

Comments
 (0)