Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
adfbafd
feat: add generate modal yaml and yaml display
gaspergrom Sep 29, 2025
6e8571c
feat: merge branch 'main' into feat/yaml-generation
gaspergrom Sep 30, 2025
f180663
feat: add steps usage and flow generation
gaspergrom Sep 30, 2025
f5e9d19
feat: steps flow and validations
gaspergrom Oct 1, 2025
77b8838
fix: lint
gaspergrom Oct 1, 2025
2cc7809
Merge branch 'main' into feat/yaml-generation
gaspergrom Oct 3, 2025
d1d41a6
feat: steps template
gaspergrom Oct 3, 2025
7d1659f
feat: basic flow
gaspergrom Oct 4, 2025
8af315c
feat: forms
gaspergrom Oct 6, 2025
fd469ee
feat: finalize basic yaml generation
gaspergrom Oct 6, 2025
05bf860
fix: lint errors
gaspergrom Oct 6, 2025
c1a0900
Merge branch 'main' into feat/yaml-generation
gaspergrom Oct 6, 2025
b6a0661
feat: child repository yaml generation
gaspergrom Oct 7, 2025
14a9176
Merge branch 'main' into feat/yaml-generation
gaspergrom Oct 7, 2025
e8d1043
feat: comprehensive project settings
gaspergrom Oct 8, 2025
f75ebcc
Merge branch 'main' into feat/yaml-generation
gaspergrom Oct 8, 2025
c302f7e
fix: main comment
gaspergrom Oct 8, 2025
30d414c
feat: comprehensive project settings
gaspergrom Oct 8, 2025
fce992a
fix: lint issues
gaspergrom Oct 8, 2025
dea0672
feat: enable steps in comprehensive
gaspergrom Oct 8, 2025
91705d0
Merge branch 'main' into feat/yaml-generation
gaspergrom Oct 9, 2025
ded3101
Merge branch 'main' into feat/yaml-generation
gaspergrom Oct 9, 2025
30a9a63
feat: add flow optional display and design fixes
gaspergrom Oct 10, 2025
1528604
feat: fix pnpm lock
gaspergrom Oct 10, 2025
560bed6
fix: pr fixes
gaspergrom Oct 10, 2025
d3d9118
fix: responsiveness
gaspergrom Oct 10, 2025
e220756
fix: pr comment fixes
gaspergrom Oct 10, 2025
499c1c1
feat: add datepicker and modal body scroll lock
gaspergrom Oct 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,8 @@ robots.txt

# SSL
certs/
certs/*
certs/*

# Claude
CLAUDE.md
.claude
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
<!--
Copyright (c) 2025 The Linux Foundation and each contributor.
SPDX-License-Identifier: MIT
-->
<template>
<lfx-modal
v-model="isModalOpen"
width="1200px"
height="900px"
class="!justify-center"
content-class="!h-full"
>
<div class="flex h-full">
<lf-security-generate-yaml-sidebar />

<!-- Main content area -->
<div class="w-2/3 flex flex-col">
<!-- Header -->
<div class="border-b border-neutral-200 px-6 pt-4 pb-5">
<div class="flex justify-between items-center">
<h1 class="text-2xl font-secondary font-bold text-neutral-900 leading-8">
Generate YAML security file
</h1>
<lfx-icon-button
icon="close"
@click="isModalOpen = false"
/>
</div>
<div
v-if="type && step >= 0"
class="flex flex-col gap-2 mt-3"
>
<div class="flex items-center gap-3">
<lfx-tooltip
placement="top"
:content="config?.description || ''"
:disabled="!config?.description"
class="flex items-center"
>
<lfx-tag
size="small"
class="bg-neutral-200 text-neutral-600"
>
{{ config?.label }}
</lfx-tag>
</lfx-tooltip>
<p class="text-xs text-neutral-600 !leading-6">
<span v-if="type && step >= 0">Step {{ step + 1 }}/{{ steps.length + 1 }} - </span>
<span v-if="!type || step < 0">Choose YAML file template</span>
<span v-else-if="step < steps.length">{{ currentStep?.label }}</span>
<span v-else>Preview & Download</span>
</p>
</div>
<div class="relative">
<div class="bg-brand-50 h-1.5 rounded-full w-full" />
<div
class="h-1.5 rounded-full absolute top-0 left-0 transition-all"
:style="{ width: `${(type ? (step + 1) / (steps.length + 1) : 0) * 100}%` }"
:class="step < steps.length ? 'bg-brand-500' : 'bg-positive-500'"
/>
</div>
</div>
</div>

<!-- Form content -->
<div class="flex-1 p-6 overflow-auto">
<lfx-security-generate-yaml-type
v-if="!type || step < 0"
v-model="type"
/>
<template v-else>
<lf-security-generate-yaml-preview
v-if="step >= steps.length"
:data="form"
/>
<component
:is="steps[step]?.component"
v-else-if="!!steps[step]"
v-model="form"
/>
</template>
</div>

<!-- Footer -->
<div class="border-t border-neutral-200 px-6 py-4">
<div class="flex items-center justify-between">
<lfx-button
v-if="step >= 0 && type"
type="tertiary"
button-style="pill"
@click="step -= 1"
>
<lfx-icon name="angle-left" />
Previous
</lfx-button>
<div class="flex-grow" />
<div class="flex items-center gap-3">
<template v-if="step < steps.length">
<lfx-button
button-style="pill"
type="primary"
:disabled="!type || $v.$invalid"
@click="step += 1"
>
Next
<lfx-icon name="angle-right" />
</lfx-button>
</template>
<template v-else-if="type">
<lfx-button
type="tertiary"
button-style="pill"
@click="copyToClipboard"
>
<lfx-icon name="clone" />
Copy to clipboard
</lfx-button>
<lfx-button
button-style="pill"
@click="downloadYamlFile"
>
<lfx-icon name="arrow-down-to-bracket" />
Download YAML file
</lfx-button>
</template>
</div>
</div>
</div>
</div>
</div>
</lfx-modal>
</template>

<script setup lang="ts">
import { computed, onMounted, watch } from 'vue'
import useVuelidate from '@vuelidate/core'
import LfxModal from '~/components/uikit/modal/modal.vue'
import LfxButton from '~/components/uikit/button/button.vue'
import LfxIconButton from '~/components/uikit/icon-button/icon-button.vue'
import LfSecurityGenerateYamlSidebar from
'~/components/modules/project/components/security/yaml/generate-yaml-sidebar.vue'
import LfxIcon from '~/components/uikit/icon/icon.vue'
import LfSecurityGenerateYamlPreview from
'~/components/modules/project/components/security/yaml/generate-yaml-preview.vue'
import LfxSecurityGenerateYamlType from '~/components/modules/project/components/security/yaml/generate-yaml-type.vue'
import {
type YamlGenerationConfig,
yamlGenerationConfig,
type YamlGenerationStep,
} from '~/components/modules/project/config/yaml-generation/yaml-generation.config'
import { getYaml } from '~/components/modules/project/services/js-yaml'
import LfxTag from '~/components/uikit/tag/tag.vue'
import LfxTooltip from '~/components/uikit/tooltip/tooltip.vue'
import useToastService from '~/components/uikit/toast/toast.service'
import { ToastTypesEnum } from '~/components/uikit/toast/types/toast.types'

const props = withDefaults(
defineProps<{
modelValue: boolean
}>(),
{
modelValue: false,
},
)

const emit = defineEmits<{
'update:modelValue': [value: boolean]
}>()

const isModalOpen = computed({
get: () => props.modelValue,
set: (value: boolean) => emit('update:modelValue', value),
})

const { showToast } = useToastService()

const type = ref('')
const step = ref(-1)
const form = ref({})

const copyToClipboard = async () => {
if (navigator.clipboard) {
const yamlContent = getYaml(form.value)
await navigator.clipboard.writeText(yamlContent)
showToast('YAML file content copied to clipboard', ToastTypesEnum.positive, 'circle-check')
}
}

const downloadYamlFile = () => {
try {
const yamlContent = getYaml(form.value)
// eslint-disable-next-line no-undef
const blob = new Blob([yamlContent], { type: 'application/x-yaml' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = 'security.yaml'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
showToast('YAML file successfully downloaded', ToastTypesEnum.positive, 'circle-check')
} catch (error) {
console.error('Failed to download YAML file:', error)
}
}

const $v = useVuelidate({}, form)

const config = computed<YamlGenerationConfig | null>(() => {
return yamlGenerationConfig[type.value] || null
})

const steps = computed<YamlGenerationStep[]>(() => {
if (!config.value) return []
return config.value.steps || []
})

const currentStep = computed<YamlGenerationStep | null>(() => {
return steps.value[step.value] || null
})

watch(type, (newType: string) => {
form.value = { ...(yamlGenerationConfig[newType]?.template || {}) }
})

onMounted(() => {
form.value = {}
})
</script>

<script lang="ts">
export default {
name: 'LfSecurityGenerateYamlModal',
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!--
Copyright (c) 2025 The Linux Foundation and each contributor.
SPDX-License-Identifier: MIT
-->
<template>
<div class="h-full flex flex-col">
<h2 class="text-lg font-semibold mb-1 leading-7">YAML file preview</h2>
<p class="text-body-2 leading-4 text-neutral-500">
Review your generated YAML file before downloading. You can copy the content or download it as
a file.
</p>

<div class="mt-6 flex items-center gap-2 rounded-lg border border-brand-200 bg-brand-50 p-3">
<lfx-icon
name="circle-info"
type="solid"
class="shrink-0 text-brand-600"
/>
<p class="text-xs leading-4 text-brand-800">
Add the YAML file as <span class="font-semibold font-mono">security.yaml</span> in your
repository root and commit to enable security assessments.
</p>
</div>

<pre
lang="yaml"
class="mt-6 p-4 bg-white border border-neutral-200 rounded-xl overflow-auto text-sm font-mono flex-grow"
>{{ yaml }}</pre>
</div>
</template>

<script lang="ts" setup>
import { computed } from 'vue'
import { getYaml } from '~/components/modules/project/services/js-yaml'
import LfxIcon from '~/components/uikit/icon/icon.vue'

const props = defineProps<{
data: object
}>()

const yaml = computed(() => getYaml(props.data))
</script>

<script lang="ts">
export default {
name: 'LfSecurityGenerateYamlPreview',
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<!--
Copyright (c) 2025 The Linux Foundation and each contributor.
SPDX-License-Identifier: MIT
-->
<template>
<div class="flex flex-col bg-neutral-50 w-1/3 h-full relative border-r border-neutral-200">
<div class="flex flex-col gap-6 p-6">
<!-- Why generate a YAML file section -->
<div>
<h2 class="text-lg font-semibold text-neutral-900 leading-7 mb-6">
Why generate a YAML file?
</h2>

<!-- Purpose -->
<div class="mb-6">
<h3 class="text-sm font-semibold text-neutral-900 leading-5 mb-2">Purpose</h3>
<p class="text-sm text-neutral-600 leading-5">
YAML security file provides a standardised way to document, assess, and share a
project's security practices, ensuring consistency and alignment with the specification
across repositories.
<a
href="https://github.com/ossf/security-insights"
rel="noopener noreferrer"
target="_blank"
class="text-brand-500"
>
Learn more
</a>
</p>
</div>

<!-- Requirements -->
<div class="mb-6">
<div class="flex items-center gap-2 mb-2">
<h3 class="text-sm font-semibold text-neutral-900 leading-5">Requirements</h3>
<lfx-tag
variation="warning"
size="small"
type="solid"
>
Admin access
</lfx-tag>
</div>
<p class="text-sm text-neutral-600 leading-5">
Repository owner or admin. You'll need write permissions to upload the generated file to
your GitHub repository.
</p>
</div>

<!-- Information collected -->
<div>
<h3 class="text-sm font-semibold text-neutral-900 leading-5 mb-2">
Information collected
</h3>
<ul class="text-sm text-neutral-600 leading-5 list-disc ml-5">
<li>Project details</li>
<li>Repository details</li>
<li>Administrator contacts</li>
<li>Core team members</li>
<li>License information</li>
<li>Vulnerability reporting</li>
<li>Security self-assessment</li>
</ul>
</div>
</div>
</div>
<div class="flex-grow" />

<!-- Bottom info box -->
<div class="flex flex-col gap-2 items-start p-6">
<lfx-icon
name="info-circle"
type="light"
class="text-neutral-500"
:size="16"
/>
<p class="text-body-1 text-neutral-600">
YAML security file specifications are optional, and we don't run strict
validation on them, but your project will still benefit from documenting
as many as possible.
</p>
</div>
</div>
</template>

<script lang="ts" setup>
import LfxTag from '~/components/uikit/tag/tag.vue'
import LfxIcon from '~/components/uikit/icon/icon.vue'
</script>

<script lang="ts">
export default {
name: 'LfSecurityGenerateYamlSidebar',
}
</script>
Loading
Loading