Skip to content

Commit 6ba1d38

Browse files
authored
feat: security yaml generation (#716)
Signed-off-by: Gašper Grom <[email protected]>
1 parent 4bf846d commit 6ba1d38

File tree

60 files changed

+5266
-124
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+5266
-124
lines changed

frontend/.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@ robots.txt
3636

3737
# SSL
3838
certs/
39-
certs/*
39+
certs/*
40+
41+
# Claude
42+
CLAUDE.md
43+
.claude
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
<!--
2+
Copyright (c) 2025 The Linux Foundation and each contributor.
3+
SPDX-License-Identifier: MIT
4+
-->
5+
<template>
6+
<lfx-modal
7+
v-model="isModalOpen"
8+
width="1200px"
9+
height="900px"
10+
class="!justify-center"
11+
content-class="!h-full !overflow-hidden"
12+
>
13+
<div class="flex h-full">
14+
<lf-security-generate-yaml-sidebar />
15+
16+
<!-- Main content area -->
17+
<div class="w-2/3 flex flex-col">
18+
<!-- Header -->
19+
<div class="border-b border-neutral-200 px-6 pt-4 pb-5">
20+
<div class="flex justify-between items-center">
21+
<h1 class="text-2xl font-secondary font-bold text-neutral-900 leading-8">
22+
Generate YAML security file
23+
</h1>
24+
<lfx-icon-button
25+
icon="close"
26+
@click="isModalOpen = false"
27+
/>
28+
</div>
29+
<div
30+
v-if="type && step >= 0"
31+
class="flex flex-col gap-2 mt-3"
32+
>
33+
<div class="flex items-center gap-3">
34+
<lfx-tooltip
35+
placement="top"
36+
:content="config?.description || ''"
37+
:disabled="!config?.description"
38+
class="flex items-center"
39+
>
40+
<lfx-tag
41+
size="small"
42+
class="bg-neutral-200 text-neutral-600"
43+
>
44+
{{ config?.label }}
45+
</lfx-tag>
46+
</lfx-tooltip>
47+
<p class="text-xs text-neutral-600 !leading-6">
48+
<span v-if="type && step >= 0">Step {{ step + 1 }}/{{ steps.length + 1 }} - </span>
49+
<span v-if="!type || step < 0">Choose YAML file template</span>
50+
<span v-else-if="step < steps.length">{{ currentStep?.label }}</span>
51+
<span v-else>Preview & Download</span>
52+
</p>
53+
</div>
54+
<div class="relative">
55+
<div class="bg-brand-50 h-1.5 rounded-full w-full" />
56+
<div
57+
class="h-1.5 rounded-full absolute top-0 left-0 transition-all"
58+
:style="{ width: `${(type ? (step + 1) / (steps.length + 1) : 0) * 100}%` }"
59+
:class="step < steps.length ? 'bg-brand-500' : 'bg-positive-500'"
60+
/>
61+
</div>
62+
</div>
63+
</div>
64+
65+
<!-- Form content -->
66+
<div class="flex-1 p-6 overflow-auto">
67+
<lfx-security-generate-yaml-type
68+
v-if="!type || step < 0"
69+
v-model="type"
70+
/>
71+
<template v-else>
72+
<lf-security-generate-yaml-preview
73+
v-if="step >= steps.length"
74+
:data="form"
75+
/>
76+
<component
77+
:is="steps[step]?.component"
78+
v-else-if="!!steps[step]"
79+
v-model="form"
80+
/>
81+
</template>
82+
</div>
83+
84+
<!-- Footer -->
85+
<div class="border-t border-neutral-200 px-6 py-4">
86+
<div class="flex items-center justify-between">
87+
<lfx-button
88+
v-if="step >= 0 && type"
89+
type="tertiary"
90+
button-style="pill"
91+
@click="step -= 1"
92+
>
93+
<lfx-icon name="angle-left" />
94+
Previous
95+
</lfx-button>
96+
<div class="flex-grow" />
97+
<div class="flex items-center gap-3">
98+
<template v-if="step < steps.length">
99+
<lfx-button
100+
button-style="pill"
101+
type="primary"
102+
:disabled="!type || $v.$invalid"
103+
@click="step += 1"
104+
>
105+
Next
106+
<lfx-icon name="angle-right" />
107+
</lfx-button>
108+
</template>
109+
<template v-else-if="type">
110+
<lfx-button
111+
type="tertiary"
112+
button-style="pill"
113+
@click="copyToClipboard"
114+
>
115+
<lfx-icon name="clone" />
116+
Copy to clipboard
117+
</lfx-button>
118+
<lfx-button
119+
button-style="pill"
120+
@click="downloadYamlFile"
121+
>
122+
<lfx-icon name="arrow-down-to-bracket" />
123+
Download YAML file
124+
</lfx-button>
125+
</template>
126+
</div>
127+
</div>
128+
</div>
129+
</div>
130+
</div>
131+
</lfx-modal>
132+
</template>
133+
134+
<script setup lang="ts">
135+
import { computed, onMounted, watch } from 'vue'
136+
import useVuelidate from '@vuelidate/core'
137+
import LfxModal from '~/components/uikit/modal/modal.vue'
138+
import LfxButton from '~/components/uikit/button/button.vue'
139+
import LfxIconButton from '~/components/uikit/icon-button/icon-button.vue'
140+
import LfSecurityGenerateYamlSidebar from
141+
'~/components/modules/project/components/security/yaml/generate-yaml-sidebar.vue'
142+
import LfxIcon from '~/components/uikit/icon/icon.vue'
143+
import LfSecurityGenerateYamlPreview from
144+
'~/components/modules/project/components/security/yaml/generate-yaml-preview.vue'
145+
import LfxSecurityGenerateYamlType from '~/components/modules/project/components/security/yaml/generate-yaml-type.vue'
146+
import {
147+
type YamlGenerationConfig,
148+
yamlGenerationConfig,
149+
type YamlGenerationStep,
150+
} from '~/components/modules/project/config/yaml-generation/yaml-generation.config'
151+
import { getYaml } from '~/components/modules/project/services/js-yaml'
152+
import LfxTag from '~/components/uikit/tag/tag.vue'
153+
import LfxTooltip from '~/components/uikit/tooltip/tooltip.vue'
154+
import useToastService from '~/components/uikit/toast/toast.service'
155+
import { ToastTypesEnum } from '~/components/uikit/toast/types/toast.types'
156+
157+
const props = withDefaults(
158+
defineProps<{
159+
modelValue: boolean
160+
}>(),
161+
{
162+
modelValue: false,
163+
},
164+
)
165+
166+
const emit = defineEmits<{
167+
'update:modelValue': [value: boolean]
168+
}>()
169+
170+
const isModalOpen = computed({
171+
get: () => props.modelValue,
172+
set: (value: boolean) => emit('update:modelValue', value),
173+
})
174+
175+
const { showToast } = useToastService()
176+
177+
const type = ref('')
178+
const step = ref(-1)
179+
const form = ref({})
180+
181+
const copyToClipboard = async () => {
182+
if (navigator.clipboard) {
183+
const yamlContent = getYaml(form.value)
184+
await navigator.clipboard.writeText(yamlContent)
185+
showToast('YAML file content copied to clipboard', ToastTypesEnum.positive, 'circle-check')
186+
}
187+
}
188+
189+
const downloadYamlFile = () => {
190+
let url: string | null = null
191+
try {
192+
const yamlContent = getYaml(form.value)
193+
// eslint-disable-next-line no-undef
194+
const blob = new Blob([yamlContent], { type: 'application/x-yaml' })
195+
url = URL.createObjectURL(blob)
196+
const link = document.createElement('a')
197+
link.href = url
198+
link.download = 'security.yaml'
199+
document.body.appendChild(link)
200+
link.click()
201+
document.body.removeChild(link)
202+
URL.revokeObjectURL(url)
203+
showToast('YAML file successfully downloaded', ToastTypesEnum.positive, 'circle-check')
204+
} catch (error) {
205+
console.error('Failed to download YAML file:', error)
206+
if (url) {
207+
URL.revokeObjectURL(url)
208+
}
209+
showToast('Failed to download YAML file', ToastTypesEnum.negative)
210+
}
211+
}
212+
213+
const $v = useVuelidate({}, form)
214+
215+
const config = computed<YamlGenerationConfig | null>(() => {
216+
return yamlGenerationConfig[type.value] || null
217+
})
218+
219+
const steps = computed<YamlGenerationStep[]>(() => {
220+
if (!config.value) return []
221+
return config.value.steps || []
222+
})
223+
224+
const currentStep = computed<YamlGenerationStep | null>(() => {
225+
return steps.value[step.value] || null
226+
})
227+
228+
watch(type, (newType: string) => {
229+
form.value = { ...(yamlGenerationConfig[newType]?.template || {}) }
230+
})
231+
232+
onMounted(() => {
233+
form.value = {}
234+
})
235+
</script>
236+
237+
<script lang="ts">
238+
export default {
239+
name: 'LfSecurityGenerateYamlModal',
240+
}
241+
</script>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!--
2+
Copyright (c) 2025 The Linux Foundation and each contributor.
3+
SPDX-License-Identifier: MIT
4+
-->
5+
<template>
6+
<div class="h-full flex flex-col">
7+
<h2 class="text-lg font-semibold mb-1 leading-7">YAML file preview</h2>
8+
<p class="text-body-2 leading-4 text-neutral-500">
9+
Review your generated YAML file before downloading. You can copy the content or download it as
10+
a file.
11+
</p>
12+
13+
<div class="mt-6 flex items-center gap-2 rounded-lg border border-brand-200 bg-brand-50 p-3">
14+
<lfx-icon
15+
name="circle-info"
16+
type="solid"
17+
class="shrink-0 text-brand-600"
18+
/>
19+
<p class="text-xs leading-4 text-brand-800">
20+
Add the YAML file as <span class="font-semibold font-mono">security.yaml</span> in your
21+
repository root and commit to enable security assessments.
22+
</p>
23+
</div>
24+
25+
<pre
26+
lang="yaml"
27+
class="mt-6 p-4 bg-white border border-neutral-200 rounded-xl overflow-auto text-sm font-mono flex-grow"
28+
>{{ yaml }}</pre>
29+
</div>
30+
</template>
31+
32+
<script lang="ts" setup>
33+
import { computed } from 'vue'
34+
import { getYaml } from '~/components/modules/project/services/js-yaml'
35+
import LfxIcon from '~/components/uikit/icon/icon.vue'
36+
37+
const props = defineProps<{
38+
data: object
39+
}>()
40+
41+
const yaml = computed(() => getYaml(props.data))
42+
</script>
43+
44+
<script lang="ts">
45+
export default {
46+
name: 'LfSecurityGenerateYamlPreview',
47+
}
48+
</script>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<!--
2+
Copyright (c) 2025 The Linux Foundation and each contributor.
3+
SPDX-License-Identifier: MIT
4+
-->
5+
<template>
6+
<div class="flex flex-col bg-neutral-50 w-1/3 h-full relative border-r border-neutral-200 overflow-auto">
7+
<div class="flex flex-col gap-6 p-6">
8+
<!-- Why generate a YAML file section -->
9+
<div>
10+
<h2 class="text-lg font-semibold text-neutral-900 leading-7 mb-6">
11+
Why generate a YAML file?
12+
</h2>
13+
14+
<!-- Purpose -->
15+
<div class="mb-6">
16+
<h3 class="text-sm font-semibold text-neutral-900 leading-5 mb-2">Purpose</h3>
17+
<p class="text-sm text-neutral-600 leading-5">
18+
YAML security file provides a standardised way to document, assess, and share a
19+
project's security practices, ensuring consistency and alignment with the specification
20+
across repositories.
21+
<a
22+
href="https://github.com/ossf/security-insights"
23+
rel="noopener noreferrer"
24+
target="_blank"
25+
class="text-brand-500"
26+
>
27+
Learn more
28+
</a>
29+
</p>
30+
</div>
31+
32+
<!-- Requirements -->
33+
<div class="mb-6">
34+
<div class="flex items-center gap-2 mb-2">
35+
<h3 class="text-sm font-semibold text-neutral-900 leading-5">Requirements</h3>
36+
<lfx-tag
37+
variation="warning"
38+
size="small"
39+
type="solid"
40+
>
41+
Admin access
42+
</lfx-tag>
43+
</div>
44+
<p class="text-sm text-neutral-600 leading-5">
45+
Repository owner or admin. You'll need write permissions to upload the generated file to
46+
your GitHub repository.
47+
</p>
48+
</div>
49+
50+
<!-- Information collected -->
51+
<div>
52+
<h3 class="text-sm font-semibold text-neutral-900 leading-5 mb-2">
53+
Information collected
54+
</h3>
55+
<ul class="text-sm text-neutral-600 leading-5 list-disc ml-5">
56+
<li>Project details</li>
57+
<li>Repository details</li>
58+
<li>Administrator contacts</li>
59+
<li>Core team members</li>
60+
<li>License information</li>
61+
<li>Vulnerability reporting</li>
62+
<li>Security self-assessment</li>
63+
</ul>
64+
</div>
65+
</div>
66+
</div>
67+
<div class="flex-grow" />
68+
69+
<!-- Bottom info box -->
70+
<div class="flex flex-col gap-2 items-start p-6">
71+
<lfx-icon
72+
name="info-circle"
73+
type="light"
74+
class="text-neutral-500"
75+
:size="16"
76+
/>
77+
<p class="text-body-1 text-neutral-600">
78+
YAML security file specifications are optional, and we don't run strict
79+
validation on them, but your project will still benefit from documenting
80+
as many as possible.
81+
</p>
82+
</div>
83+
</div>
84+
</template>
85+
86+
<script lang="ts" setup>
87+
import LfxTag from '~/components/uikit/tag/tag.vue'
88+
import LfxIcon from '~/components/uikit/icon/icon.vue'
89+
</script>
90+
91+
<script lang="ts">
92+
export default {
93+
name: 'LfSecurityGenerateYamlSidebar',
94+
}
95+
</script>

0 commit comments

Comments
 (0)