Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c5c05a5

Browse files
committedMar 20, 2025·
WIP
1 parent 64ca982 commit c5c05a5

File tree

7 files changed

+120
-158
lines changed

7 files changed

+120
-158
lines changed
 

‎src/lib/components/WorkloadDeploy.svelte

+49
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
<script lang="ts">
22
import { fragment, graphql, type WorkloadDeploy } from '$houdini';
33
import Time from '$lib/Time.svelte';
4+
import { getImageDisplayName } from '$lib/utils/image';
45
import { isValidSha } from '$lib/utils/isValidSha';
56
import { BodyShort, Heading, Link, Tag } from '@nais/ds-svelte-community';
67
import { ExternalLinkIcon } from '@nais/ds-svelte-community/icons';
8+
import WorkloadLink from './WorkloadLink.svelte';
79
810
interface Props {
911
workload: WorkloadDeploy;
@@ -17,7 +19,29 @@
1719
graphql(`
1820
fragment WorkloadDeploy on Workload {
1921
__typename
22+
id
2023
name
24+
image {
25+
name
26+
tag
27+
workloadReferences {
28+
nodes {
29+
workload {
30+
id
31+
__typename
32+
team {
33+
slug
34+
}
35+
teamEnvironment {
36+
environment {
37+
name
38+
}
39+
}
40+
name
41+
}
42+
}
43+
}
44+
}
2145
deployments(first: 1) {
2246
nodes {
2347
deployerUsername
@@ -40,6 +64,12 @@
4064
let deploymentInfo = $derived(
4165
$data.deployments.nodes.length > 0 ? $data.deployments.nodes[0] : null
4266
);
67+
68+
const relatedWorkloads = $derived(
69+
$data.image.workloadReferences.nodes
70+
.map((node) => node.workload)
71+
.filter((workload) => workload.id !== $data.id)
72+
);
4373
</script>
4474

4575
<div class="wrapper">
@@ -69,6 +99,25 @@
6999
<BodyShort>No deployment metadata found for workload.</BodyShort>
70100
{/if}
71101
</div>
102+
<div class="wrapper">
103+
<Heading level="3" size="small">Image</Heading>
104+
{#if $data.image.name.startsWith('europe-north1-docker.pkg.dev')}
105+
<a href="https://{$data.image.name + ':' + $data.image.tag}">
106+
<span
107+
>{getImageDisplayName($data.image.name)}:{$data.image.tag}
108+
<ExternalLinkIcon /></span
109+
>
110+
</a>
111+
{:else}
112+
{$data.image.name}:{$data.image.tag}
113+
{/if}
114+
{#if relatedWorkloads.length > 0}
115+
<Heading level="4" size="xsmall">Other workloads using this image</Heading>
116+
{#each relatedWorkloads as workload (workload.id)}
117+
<WorkloadLink {workload} />
118+
{/each}
119+
{/if}
120+
</div>
72121

73122
<style>
74123
.wrapper {

‎src/lib/components/errors/ErrorMessage.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
export const supportedErrorTypes = [
33
'WorkloadStatusInvalidNaisYaml',
44
'WorkloadStatusSynchronizationFailing',
5-
'WorkloadStatusDeprecatedRegistry',
65
'WorkloadStatusNoRunningInstances',
76
'WorkloadStatusFailedRun',
7+
'WorkloadStatusDeprecatedRegistry',
88
'WorkloadStatusVulnerable'
99
] as const;
1010
</script>
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
<script lang="ts">
22
import { fragment, graphql, type ImageWorkloadReferences } from '$houdini';
3-
import Time from '$lib/Time.svelte';
4-
5-
import { Heading, Table, Tbody, Td, Th, Thead, Tr } from '@nais/ds-svelte-community';
63
import WorkloadLink from '../WorkloadLink.svelte';
74
85
interface Props {
@@ -30,12 +27,6 @@
3027
}
3128
}
3229
name
33-
deployments(first: 1) {
34-
nodes {
35-
triggerUrl
36-
createdAt
37-
}
38-
}
3930
}
4031
}
4132
}
@@ -45,48 +36,6 @@
4536
);
4637
</script>
4738

48-
<div class="workloads">
49-
<Heading level="4" size="small" spacing>Workloads using image</Heading>
50-
<Table size="small">
51-
<Thead>
52-
<Tr>
53-
<Th>Workload</Th>
54-
<Th>Deploy ref</Th>
55-
<Th>Age</Th>
56-
</Tr>
57-
</Thead>
58-
<Tbody>
59-
{#each $workloadRefs.workloadReferences.nodes as node (node.workload.id)}
60-
{@const { workload } = node}
61-
{@const deployInfo =
62-
workload.deployments.nodes.length > 0 ? workload.deployments.nodes[0] : null}
63-
<Tr>
64-
<Td>
65-
<WorkloadLink {workload} />
66-
</Td>
67-
<Td>
68-
{#if deployInfo?.triggerUrl}
69-
<a href={deployInfo?.triggerUrl} target="_blank">Run</a>
70-
{/if}
71-
</Td>
72-
<Td
73-
>{#if deployInfo?.createdAt}
74-
<Time distance time={deployInfo.createdAt} />
75-
{/if}
76-
</Td>
77-
</Tr>
78-
{:else}
79-
<Tr>
80-
<Td colspan={5}>No workloads found using this image in Dependency-Track</Td>
81-
</Tr>
82-
{/each}
83-
</Tbody>
84-
</Table>
85-
</div>
86-
87-
<style>
88-
.workloads {
89-
display: flex;
90-
flex-direction: column;
91-
}
92-
</style>
39+
{#each $workloadRefs.workloadReferences.nodes as node (node.workload.id)}
40+
<WorkloadLink workload={node.workload} />
41+
{/each}

‎src/lib/utils/image.test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { getImageDisplayName } from './image';
2+
3+
test.each([
4+
[
5+
'europe-north1-docker.pkg.dev/nais-management-233d/klage/kabal-e2e-tests',
6+
'klage/kabal-e2e-tests'
7+
],
8+
[
9+
'europe-north1-docker.pkg.dev/nais-io/nais/images/dataproduct-topics',
10+
'nais/images/dataproduct-topics'
11+
]
12+
])('getImageDisplayName(%s)', (name, expected) => {
13+
expect(getImageDisplayName(name)).toBe(expected);
14+
});

‎src/lib/utils/image.ts

+4
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ export const parseImage = (image: string) => {
88
}
99
return { registry, repository, name, tag };
1010
};
11+
12+
export const getImageDisplayName = (name: string): string => {
13+
return name.split('/').slice(2).join('/');
14+
};

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

+22-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ query JobImageDetails($team: Slug!, $env: String!, $job: String!) @cache(policy:
66
workload(name: $job) {
77
__typename
88
name
9+
status {
10+
errors {
11+
__typename
12+
}
13+
}
914
image {
1015
name
1116
tag
@@ -18,7 +23,23 @@ query JobImageDetails($team: Slug!, $env: String!, $job: String!) @cache(policy:
1823
unassigned
1924
riskScore
2025
}
21-
...ImageWorkloadReferences
26+
workloadReferences {
27+
nodes {
28+
workload {
29+
id
30+
__typename
31+
team {
32+
slug
33+
}
34+
teamEnvironment {
35+
environment {
36+
name
37+
}
38+
}
39+
name
40+
}
41+
}
42+
}
2243
}
2344
}
2445
}
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,35 @@
11
<script lang="ts">
2-
import Card from '$lib/Card.svelte';
32
import ImageVulnerabilities from '$lib/components/image/ImageVulnerabilities.svelte';
4-
import ImageWorkloadReferences from '$lib/components/image/ImageWorkloadReferences.svelte';
53
import VulnerabilityBadges from '$lib/components/VulnerabilityBadges.svelte';
64
import { docURL } from '$lib/doc';
75
import GraphErrors from '$lib/GraphErrors.svelte';
86
import WarningIcon from '$lib/icons/WarningIcon.svelte';
9-
import { parseImage } from '$lib/utils/image';
10-
import { CopyButton, Heading } from '@nais/ds-svelte-community';
7+
import { Heading } from '@nais/ds-svelte-community';
118
import type { PageProps } from './$houdini';
129
1310
let { data }: PageProps = $props();
1411
1512
let { JobImageDetails, viewerIsMember } = $derived(data);
1613
17-
let registry: string = $state('');
18-
let repository: string = $state('');
19-
let name: string = $state('');
20-
21-
$effect(() => {
22-
if ($JobImageDetails.data?.team.environment.workload.image) {
23-
({ registry, repository, name } = parseImage(
24-
$JobImageDetails.data.team.environment.workload.image.name
25-
));
26-
}
27-
});
14+
const error = $derived(
15+
$JobImageDetails.data?.team.environment.workload.status.errors.find(
16+
(e) => e.__typename === 'WorkloadStatusVulnerable'
17+
)
18+
);
2819
</script>
2920

3021
<GraphErrors errors={$JobImageDetails.errors} />
22+
3123
{#if $JobImageDetails.data}
3224
{@const image = $JobImageDetails.data.team.environment.workload.image}
3325
<div class="grid">
34-
<Card columns={8}>
35-
<div class="details">
36-
<Heading level="4" size="small" spacing>Image</Heading>
37-
<CopyButton
38-
size="xsmall"
39-
variant="action"
40-
text="Copy image name"
41-
activeText="Image name copied"
42-
copyText={image.name + ':' + image.tag}
43-
/>
44-
</div>
45-
<div class="imageGrid">
46-
<div class="registry">
47-
<h5>Registry</h5>
48-
<code>{registry}</code>
49-
</div>
50-
<div class="repository">
51-
<h5>Repository</h5>
52-
<code>{repository}</code>
53-
</div>
54-
<div class="imageName">
55-
<h5>Name</h5>
56-
<code>{name}</code>
57-
</div>
58-
<div class="tag">
59-
<h5>Tag</h5>
60-
<code>{image.tag ? image.tag : ''}</code>
61-
</div>
62-
</div>
63-
</Card>
64-
65-
<Card columns={3}>
26+
<div>
27+
{#if error}
28+
<!-- TODO -->
29+
<!-- <ErrorMessage {error} /> -->
30+
{/if}
31+
</div>
32+
<div class="card">
6633
<Heading level="4" size="small" spacing>Summary</Heading>
6734
{#if image.vulnerabilitySummary}
6835
<VulnerabilityBadges summary={image.vulnerabilitySummary} />
@@ -76,67 +43,25 @@
7643
No data found.
7744
<a href={docURL('/services/vulnerabilities/how-to/sbom/')}> How to fix</a>
7845
{/if}
79-
</Card>
80-
<Card columns={11}>
81-
<ImageVulnerabilities
82-
team={$JobImageDetails.data?.team.slug}
83-
environment={$JobImageDetails.data?.team.environment.name}
84-
workload={$JobImageDetails.data?.team.environment.workload.name}
85-
authorized={viewerIsMember}
86-
/>
87-
</Card>
88-
89-
<Card columns={11}>
90-
<ImageWorkloadReferences {image} />
91-
</Card>
46+
</div>
9247
</div>
48+
<ImageVulnerabilities
49+
team={$JobImageDetails.data?.team.slug}
50+
environment={$JobImageDetails.data?.team.environment.name}
51+
workload={$JobImageDetails.data?.team.environment.workload.name}
52+
authorized={viewerIsMember}
53+
/>
9354
{/if}
9455

9556
<style>
96-
.details {
97-
display: flex;
98-
justify-content: space-between;
99-
}
100-
101-
code {
102-
font-size: 1rem;
103-
}
104-
10557
.grid {
10658
display: grid;
107-
grid-template-columns: repeat(12, 1fr);
108-
column-gap: 1rem;
109-
row-gap: 1rem;
59+
grid-template-columns: 1fr 300px;
60+
gap: 1rem;
11061
}
111-
112-
.imageGrid {
113-
display: grid;
114-
grid-template-columns: repeat(2, 1fr);
115-
column-gap: 0.2rem;
116-
row-gap: 0.2rem;
117-
}
118-
119-
code {
120-
font-size: 0.8rem;
121-
}
122-
123-
.registry {
124-
grid-column: 1;
125-
grid-row: 2;
126-
}
127-
128-
.repository {
129-
grid-column: 2;
130-
grid-row: 2;
131-
}
132-
133-
.imageName {
134-
grid-column: 1;
135-
grid-row: 1;
136-
}
137-
138-
.tag {
139-
grid-column: 2;
140-
grid-row: 1;
62+
.card {
63+
background-color: var(--a-surface-subtle);
64+
padding: var(--a-spacing-5);
65+
border-radius: 12px;
14166
}
14267
</style>

0 commit comments

Comments
 (0)
Please sign in to comment.