Skip to content

Commit 88d8c7b

Browse files
committed
WIP
1 parent e3a6024 commit 88d8c7b

File tree

6 files changed

+243
-165
lines changed

6 files changed

+243
-165
lines changed

src/lib/components/VulnerabilityBadges.svelte

+39-58
Original file line numberDiff line numberDiff line change
@@ -26,48 +26,52 @@
2626
const categories = ['critical', 'high', 'medium', 'low', 'unassigned'] as const;
2727
</script>
2828

29-
<div class="vulnerability-summary">
29+
<div class="wrapper">
30+
<div class="vulnerability-summary">
31+
{#if summary !== PendingValue}
32+
{#each categories as category (category)}
33+
<BodyShort
34+
class="vulnerability-count"
35+
style="background-color: {severityToColor(category)}"
36+
>
37+
{summary[category]}
38+
</BodyShort>
39+
<div class="category">{category}</div>
40+
{/each}
41+
{:else}
42+
<Loader />
43+
{/if}
44+
</div>
45+
3046
{#if summary !== PendingValue}
31-
{#each categories as category (category)}
32-
<BodyShort class="vulnerability-count" style="background-color: {severityToColor(category)}">
33-
{summary[category]}
34-
</BodyShort>
35-
<div class="category">{category}</div>
36-
{/each}
37-
{:else}
38-
<Loader />
39-
{/if}
40-
</div>
41-
{#if summary !== PendingValue}
42-
<div class="container">
43-
<dl>
47+
<BodyShort>
4448
{#if summary['riskScore']}
45-
<dt>Risk score:</dt>
46-
<dd>
47-
<span class={summary['riskScore'] > 100 ? 'red' : 'green'}>{summary['riskScore']}</span>
48-
</dd>
49-
<!--HelpText title="Risk score"
50-
>The risk score is a calculated value based on the severity of the vulnerabilities
51-
discovered within the workloads. A higher risk score indicates a higher risk of
52-
exploitation. Algorithms may vary, but a common approach is to assign a score based on the
53-
severity of the vulnerabilities found. The Score is calculated: "((critical * 10) + (high *
54-
5) + (medium * 3) + (low * 1) + (unassigned * 5))".
55-
</HelpText-->
49+
<strong>Risk score:</strong>
50+
{#if summary['coverage']}
51+
{summary['riskScore']}
52+
{:else if summary['riskScore'] > 100}
53+
<span class="red">{summary['riskScore']}</span> (above defined threshold of 100)
54+
{:else}
55+
<span class="green">{summary['riskScore']}</span>
56+
{/if}
5657
{/if}
57-
58+
</BodyShort>
59+
<BodyShort>
5860
{#if summary['coverage']}
59-
<dt>Coverage:</dt>
60-
<dd>
61-
<span class={summary['coverage'] < 100 ? 'red' : 'green'}
62-
>{percentageFormatter(summary['coverage'] ? summary['coverage'] : 0, 0)}</span
63-
>
64-
</dd>
61+
<strong>Coverage:</strong>
62+
<span class={summary['coverage'] < 100 ? 'red' : 'green'}
63+
>{percentageFormatter(summary['coverage'] ? summary['coverage'] : 0, 0)}</span
64+
>
6565
{/if}
66-
</dl>
67-
</div>
68-
{/if}
66+
</BodyShort>
67+
{/if}
68+
</div>
6969

7070
<style>
71+
.wrapper {
72+
display: grid;
73+
row-gap: var(--a-spacing-2);
74+
}
7175
.category {
7276
text-transform: capitalize;
7377
}
@@ -95,34 +99,11 @@
9599
}
96100
}
97101
98-
.container {
99-
display: flex;
100-
gap: 0.5rem;
101-
justify-content: space-between;
102-
}
103-
104102
.red {
105103
color: var(--a-surface-danger);
106104
}
107105
108106
.green {
109107
color: var(--a-surface-success);
110108
}
111-
112-
dl {
113-
display: grid;
114-
grid-template-columns: 90px auto;
115-
margin-block-start: 0;
116-
margin-block-end: 0;
117-
padding-top: var(--a-spacing-4);
118-
}
119-
120-
dt {
121-
font-weight: bold;
122-
text-align: left;
123-
}
124-
125-
dd {
126-
margin: 0;
127-
}
128109
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<script lang="ts">
2+
import { docURL } from '$lib/doc';
3+
import SuccessIcon from '$lib/icons/SuccessIcon.svelte';
4+
import WarningIcon from '$lib/icons/WarningIcon.svelte';
5+
import { BodyShort, Heading, Link } from '@nais/ds-svelte-community';
6+
import VulnerabilityBadges from './VulnerabilityBadges.svelte';
7+
8+
interface Props {
9+
workload: {
10+
__typename: string;
11+
name: string;
12+
team: {
13+
slug: string;
14+
};
15+
teamEnvironmnet: {
16+
environment: {
17+
name: string;
18+
};
19+
};
20+
image: {
21+
hasSBOM: boolean;
22+
vulnerabilitySummary: {
23+
critical: number;
24+
high: number;
25+
medium: number;
26+
low: number;
27+
unassigned: number;
28+
riskScore: number;
29+
};
30+
};
31+
};
32+
}
33+
34+
const { workload }: Props = $props();
35+
36+
const { image } = workload;
37+
38+
const imageDetailsUrl = $derived(
39+
`/team/${workload.team.slug}/${workload.teamEnvironmnet.environment.name}/${workload.__typename === 'Application' ? 'app' : 'job'}/${workload.name}/vulnerability-report`
40+
);
41+
42+
const categories = ['critical', 'high', 'medium', 'low', 'unassigned'] as const;
43+
const hasFindings = categories.some(
44+
(severity) => (image.vulnerabilitySummary?.[severity] ?? 0) > 0
45+
);
46+
</script>
47+
48+
<div class="wrapper">
49+
<Heading level="3" size="small">Vulnerabilities</Heading>
50+
51+
{#if !image.hasSBOM && image.vulnerabilitySummary !== null}
52+
<BodyShort>
53+
<WarningIcon class="text-aligned-icon" /> Data was discovered, but the SBOM was not rendered. Refer
54+
to the <Link href={docURL('/services/vulnerabilities/')}>Nais documentation</Link> for further
55+
assistance.
56+
</BodyShort>
57+
{:else if image.vulnerabilitySummary === null}
58+
<BodyShort>
59+
<WarningIcon class="text-aligned-icon" /> No data found.
60+
<a href={docURL('/services/vulnerabilities/how-to/sbom/')} target="_blank">How to fix</a>
61+
</BodyShort>
62+
{:else if image.hasSBOM && image.vulnerabilitySummary && hasFindings}
63+
<VulnerabilityBadges summary={image.vulnerabilitySummary} />
64+
{:else if image.hasSBOM}
65+
<BodyShort>
66+
<SuccessIcon class="text-aligned-icon" /> No vulnerabilities found. Good work!
67+
</BodyShort>
68+
{/if}
69+
70+
<Link href={imageDetailsUrl}>View vulnerability report</Link>
71+
</div>
72+
73+
<style>
74+
.wrapper {
75+
display: flex;
76+
flex-direction: column;
77+
gap: var(--a-spacing-1);
78+
align-items: start;
79+
}
80+
</style>

src/lib/components/image/ImageVulnerabilities.svelte

+45-30
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,17 @@
33
import Pagination from '$lib/Pagination.svelte';
44
import { changeParams } from '$lib/utils/searchparams';
55
import { severityToColor } from '$lib/utils/vulnerabilities';
6-
import { Button, Heading, Table, Tbody, Td, Th, Thead, Tr } from '@nais/ds-svelte-community';
6+
import {
7+
Button,
8+
Heading,
9+
Loader,
10+
Table,
11+
Tbody,
12+
Td,
13+
Th,
14+
Thead,
15+
Tr
16+
} from '@nais/ds-svelte-community';
717
import { CheckmarkIcon } from '@nais/ds-svelte-community/icons';
818
import { untrack } from 'svelte';
919
import type { ImageVulnerabilitiesVariables } from './$houdini';
@@ -147,32 +157,32 @@
147157
};
148158
</script>
149159

150-
<Heading level="2" size="medium">Vulnerabilities</Heading>
151-
<Table
152-
size="small"
153-
sort={{
154-
orderBy: tableSort.orderBy || ImageVulnerabilityOrderField.SEVERITY,
155-
direction: tableSort.direction === 'ASC' ? 'ascending' : 'descending'
156-
}}
157-
onsortchange={tableSortChange}
158-
>
159-
<Thead>
160-
<Tr>
161-
<Th style="width: 13rem" sortable={true} sortKey={ImageVulnerabilityOrderField.IDENTIFIER}
162-
>ID</Th
163-
>
164-
<Th sortable={true} sortKey={ImageVulnerabilityOrderField.PACKAGE}>Package</Th>
165-
<Th style="width: 7rem " sortable={true} sortKey={ImageVulnerabilityOrderField.SEVERITY}
166-
>Severity</Th
167-
>
168-
<Th style="width: 3rem" sortable={true} sortKey={ImageVulnerabilityOrderField.SUPPRESSED}
169-
>Suppressed</Th
170-
>
171-
<Th sortable={true} sortKey={ImageVulnerabilityOrderField.STATE}>State</Th>
172-
</Tr>
173-
</Thead>
174-
<Tbody>
175-
{#if $vulnerabilities.data}
160+
<Heading level="2" size="medium" spacing>Vulnerabilities</Heading>
161+
{#if $vulnerabilities.data}
162+
<Table
163+
size="small"
164+
sort={{
165+
orderBy: tableSort.orderBy || ImageVulnerabilityOrderField.SEVERITY,
166+
direction: tableSort.direction === 'ASC' ? 'ascending' : 'descending'
167+
}}
168+
onsortchange={tableSortChange}
169+
>
170+
<Thead>
171+
<Tr>
172+
<Th style="width: 13rem" sortable={true} sortKey={ImageVulnerabilityOrderField.IDENTIFIER}
173+
>ID</Th
174+
>
175+
<Th sortable={true} sortKey={ImageVulnerabilityOrderField.PACKAGE}>Package</Th>
176+
<Th style="width: 7rem " sortable={true} sortKey={ImageVulnerabilityOrderField.SEVERITY}
177+
>Severity</Th
178+
>
179+
<Th style="width: 3rem" sortable={true} sortKey={ImageVulnerabilityOrderField.SUPPRESSED}
180+
>Suppressed</Th
181+
>
182+
<Th sortable={true} sortKey={ImageVulnerabilityOrderField.STATE}>State</Th>
183+
</Tr>
184+
</Thead>
185+
<Tbody>
176186
{@const vulnz = $vulnerabilities.data.team.environment.workload.image.vulnerabilities.nodes}
177187
{#each vulnz as v (v)}
178188
<Tr>
@@ -222,9 +232,14 @@
222232
<Td colspan={999}>No vulnerabilities</Td>
223233
</Tr>
224234
{/each}
225-
{/if}
226-
</Tbody>
227-
</Table>
235+
</Tbody>
236+
</Table>
237+
{:else}
238+
<div style="display: flex; justify-content: center; align-items: center; height: 200px;">
239+
<Loader size="2xlarge" />
240+
</div>
241+
{/if}
242+
228243
{#if image}
229244
<Pagination
230245
page={image.vulnerabilities.pageInfo}

src/lib/components/image/TrailFinding.svelte

-45
Original file line numberDiff line numberDiff line change
@@ -111,51 +111,6 @@
111111
<Td><Time time={node.timestamp} distance={true} /></Td>
112112
</Tr>
113113
{/if}
114-
{#if node}
115-
<Tr>
116-
<Td>{node.onBehalfOf}</Td>
117-
<Td>{node.state}</Td>
118-
<Td>{node.suppressed}</Td>
119-
<Td>{node.comment}</Td>
120-
<Td><Time time={node.timestamp} distance={true} /></Td>
121-
</Tr>
122-
{/if}
123-
{#if node}
124-
<Tr>
125-
<Td>{node.onBehalfOf}</Td>
126-
<Td>{node.state}</Td>
127-
<Td>{node.suppressed}</Td>
128-
<Td>{node.comment}</Td>
129-
<Td><Time time={node.timestamp} distance={true} /></Td>
130-
</Tr>
131-
{/if}
132-
{#if node}
133-
<Tr>
134-
<Td>{node.onBehalfOf}</Td>
135-
<Td>{node.state}</Td>
136-
<Td>{node.suppressed}</Td>
137-
<Td>{node.comment}</Td>
138-
<Td><Time time={node.timestamp} distance={true} /></Td>
139-
</Tr>
140-
{/if}
141-
{#if node}
142-
<Tr>
143-
<Td>{node.onBehalfOf}</Td>
144-
<Td>{node.state}</Td>
145-
<Td>{node.suppressed}</Td>
146-
<Td>{node.comment}</Td>
147-
<Td><Time time={node.timestamp} distance={true} /></Td>
148-
</Tr>
149-
{/if}
150-
{#if node}
151-
<Tr>
152-
<Td>{node.onBehalfOf}</Td>
153-
<Td>{node.state}</Td>
154-
<Td>{node.suppressed}</Td>
155-
<Td>{node.comment}</Td>
156-
<Td><Time time={node.timestamp} distance={true} /></Td>
157-
</Tr>
158-
{/if}
159114
{/each}
160115
{/if}
161116
</Tbody>

src/routes/team/[team]/[env]/job/[job]/vulnerability-report/+page.gql

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ query JobImageDetails($team: Slug!, $env: String!, $job: String!) @cache(policy:
22
team(slug: $team) {
33
slug
44
environment(name: $env) {
5-
name
65
environment {
76
name
87
}
@@ -22,6 +21,7 @@ query JobImageDetails($team: Slug!, $env: String!, $job: String!) @cache(policy:
2221
}
2322
}
2423
image {
24+
id
2525
name
2626
tag
2727
hasSBOM

0 commit comments

Comments
 (0)