Initial commit: breakpilot-compliance - Compliance SDK Platform

Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-11 23:47:28 +01:00
commit 4435e7ea0a
734 changed files with 251369 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
/**
* Vue 3 Composables for BreakPilot Compliance SDK
*/
export { useCompliance, type UseComplianceReturn } from './useCompliance'
export { useDSGVO, type UseDSGVOReturn } from './useDSGVO'
export { useRAG, type UseRAGReturn } from './useRAG'
export { useControls, type UseControlsReturn } from './useControls'
export { useSecurity, type UseSecurityReturn, type ScanOptions } from './useSecurity'

View File

@@ -0,0 +1,140 @@
/**
* Main compliance composable
*/
import { computed, type ComputedRef } from 'vue'
import { useComplianceStore, type ComplianceStore } from '../plugin'
import type { SDKState, SDKAction, SDKStep } from '@breakpilot/compliance-sdk-types'
import { SDK_STEPS } from '@breakpilot/compliance-sdk-types'
export interface UseComplianceReturn {
// Store
state: SDKState
dispatch: (action: SDKAction) => void
store: ComplianceStore
// Status
isReady: ComputedRef<boolean>
error: ComputedRef<Error | null>
// Progress
completionPercentage: ComputedRef<number>
phase1Completion: ComputedRef<number>
phase2Completion: ComputedRef<number>
completedSteps: ComputedRef<string[]>
// Navigation
currentStep: ComputedRef<SDKStep | undefined>
nextStep: ComputedRef<SDKStep | undefined>
previousStep: ComputedRef<SDKStep | undefined>
canGoNext: ComputedRef<boolean>
canGoPrevious: ComputedRef<boolean>
goToStep: (stepId: string) => void
goNext: () => void
goPrevious: () => void
// Actions
completeStep: (stepId: string) => void
resetProgress: () => void
saveState: () => Promise<void>
}
export function useCompliance(): UseComplianceReturn {
const store = useComplianceStore()
const { state, dispatch, client } = store
// Status
const isReady = computed(() => store.isReady)
const error = computed(() => store.error)
// Progress calculations
const completedSteps = computed(() => state.completedSteps)
const completionPercentage = computed(() => {
return Math.round((state.completedSteps.length / SDK_STEPS.length) * 100)
})
const phase1Completion = computed(() => {
const phase1Steps = SDK_STEPS.filter(s => s.phase === 1)
const completed = phase1Steps.filter(s => state.completedSteps.includes(s.id))
return Math.round((completed.length / phase1Steps.length) * 100)
})
const phase2Completion = computed(() => {
const phase2Steps = SDK_STEPS.filter(s => s.phase === 2)
const completed = phase2Steps.filter(s => state.completedSteps.includes(s.id))
return Math.round((completed.length / phase2Steps.length) * 100)
})
// Navigation
const currentStep = computed(() => SDK_STEPS.find(s => s.id === state.currentStep))
const currentIndex = computed(() => SDK_STEPS.findIndex(s => s.id === state.currentStep))
const nextStep = computed(() => {
const idx = currentIndex.value
return idx < SDK_STEPS.length - 1 ? SDK_STEPS[idx + 1] : undefined
})
const previousStep = computed(() => {
const idx = currentIndex.value
return idx > 0 ? SDK_STEPS[idx - 1] : undefined
})
const canGoNext = computed(() => currentIndex.value < SDK_STEPS.length - 1)
const canGoPrevious = computed(() => currentIndex.value > 0)
const goToStep = (stepId: string): void => {
dispatch({ type: 'SET_CURRENT_STEP', payload: stepId })
}
const goNext = (): void => {
if (nextStep.value) {
goToStep(nextStep.value.id)
}
}
const goPrevious = (): void => {
if (previousStep.value) {
goToStep(previousStep.value.id)
}
}
// Actions
const completeStep = (stepId: string): void => {
if (!state.completedSteps.includes(stepId)) {
dispatch({ type: 'COMPLETE_STEP', payload: stepId })
}
}
const resetProgress = (): void => {
dispatch({ type: 'RESET_PROGRESS' })
}
const saveState = async (): Promise<void> => {
await client.saveState(state)
}
return {
state,
dispatch,
store,
isReady,
error,
completionPercentage,
phase1Completion,
phase2Completion,
completedSteps,
currentStep,
nextStep,
previousStep,
canGoNext,
canGoPrevious,
goToStep,
goNext,
goPrevious,
completeStep,
resetProgress,
saveState,
}
}

View File

@@ -0,0 +1,258 @@
/**
* Controls composable for compliance controls management
*/
import { computed, type ComputedRef, type Ref, ref } from 'vue'
import { useComplianceStore } from '../plugin'
import type {
Control,
Evidence,
Risk,
ControlDomain,
ImplementationStatus,
EvidenceType,
RiskLikelihood,
RiskImpact,
} from '@breakpilot/compliance-sdk-types'
export interface UseControlsReturn {
// Controls
controls: ComputedRef<Control[]>
controlsByDomain: ComputedRef<Record<ControlDomain, Control[]>>
implementedControls: ComputedRef<Control[]>
pendingControls: ComputedRef<Control[]>
getControl: (id: string) => Control | undefined
addControl: (control: Omit<Control, 'id' | 'createdAt' | 'updatedAt'>) => void
updateControl: (id: string, updates: Partial<Control>) => void
deleteControl: (id: string) => void
setControlStatus: (id: string, status: ImplementationStatus) => void
// Evidence
evidence: ComputedRef<Evidence[]>
validEvidence: ComputedRef<Evidence[]>
expiredEvidence: ComputedRef<Evidence[]>
getEvidenceForControl: (controlId: string) => Evidence[]
addEvidence: (evidence: Omit<Evidence, 'id' | 'uploadedAt'>) => void
updateEvidence: (id: string, updates: Partial<Evidence>) => void
deleteEvidence: (id: string) => void
// Risks
risks: ComputedRef<Risk[]>
criticalRisks: ComputedRef<Risk[]>
unmitigatedRisks: ComputedRef<Risk[]>
getRisksForControl: (controlId: string) => Risk[]
addRisk: (risk: Omit<Risk, 'id' | 'createdAt' | 'updatedAt'>) => void
updateRisk: (id: string, updates: Partial<Risk>) => void
deleteRisk: (id: string) => void
assessRisk: (id: string, likelihood: RiskLikelihood, impact: RiskImpact) => void
// Compliance Score
complianceScore: ComputedRef<number>
scoreByDomain: ComputedRef<Record<ControlDomain, number>>
}
export function useControls(): UseControlsReturn {
const store = useComplianceStore()
const { state, dispatch, compliance } = store
// Controls
const controls = computed(() => state.controls)
const controlsByDomain = computed(() => {
const domains: ControlDomain[] = [
'ACCESS_CONTROL',
'DATA_PROTECTION',
'NETWORK_SECURITY',
'INCIDENT_RESPONSE',
'BUSINESS_CONTINUITY',
'COMPLIANCE',
'RISK_MANAGEMENT',
'ASSET_MANAGEMENT',
'HUMAN_RESOURCES',
]
return domains.reduce(
(acc, domain) => {
acc[domain] = state.controls.filter(c => c.domain === domain)
return acc
},
{} as Record<ControlDomain, Control[]>
)
})
const implementedControls = computed(() =>
state.controls.filter(c => c.implementationStatus === 'IMPLEMENTED')
)
const pendingControls = computed(() =>
state.controls.filter(c => c.implementationStatus === 'NOT_IMPLEMENTED')
)
const getControl = (id: string): Control | undefined => {
return state.controls.find(c => c.id === id)
}
const addControl = (control: Omit<Control, 'id' | 'createdAt' | 'updatedAt'>): void => {
dispatch({ type: 'ADD_CONTROL', payload: control })
}
const updateControl = (id: string, updates: Partial<Control>): void => {
dispatch({ type: 'UPDATE_CONTROL', payload: { id, updates } })
}
const deleteControl = (id: string): void => {
dispatch({ type: 'DELETE_CONTROL', payload: id })
}
const setControlStatus = (id: string, status: ImplementationStatus): void => {
dispatch({
type: 'UPDATE_CONTROL',
payload: { id, updates: { implementationStatus: status } },
})
}
// Evidence
const evidence = computed(() => state.evidence)
const validEvidence = computed(() => {
const now = new Date()
return state.evidence.filter(e => !e.validUntil || new Date(e.validUntil) > now)
})
const expiredEvidence = computed(() => {
const now = new Date()
return state.evidence.filter(e => e.validUntil && new Date(e.validUntil) <= now)
})
const getEvidenceForControl = (controlId: string): Evidence[] => {
return state.evidence.filter(e => e.controlIds.includes(controlId))
}
const addEvidence = (ev: Omit<Evidence, 'id' | 'uploadedAt'>): void => {
dispatch({ type: 'ADD_EVIDENCE', payload: ev })
}
const updateEvidence = (id: string, updates: Partial<Evidence>): void => {
dispatch({ type: 'UPDATE_EVIDENCE', payload: { id, updates } })
}
const deleteEvidence = (id: string): void => {
dispatch({ type: 'DELETE_EVIDENCE', payload: id })
}
// Risks
const risks = computed(() => state.risks)
const criticalRisks = computed(() => compliance.getCriticalRisks())
const unmitigatedRisks = computed(() =>
state.risks.filter(r => r.status === 'IDENTIFIED' || r.status === 'ANALYZED')
)
const getRisksForControl = (controlId: string): Risk[] => {
return state.risks.filter(r => r.controlIds?.includes(controlId))
}
const addRisk = (risk: Omit<Risk, 'id' | 'createdAt' | 'updatedAt'>): void => {
dispatch({ type: 'ADD_RISK', payload: risk })
}
const updateRisk = (id: string, updates: Partial<Risk>): void => {
dispatch({ type: 'UPDATE_RISK', payload: { id, updates } })
}
const deleteRisk = (id: string): void => {
dispatch({ type: 'DELETE_RISK', payload: id })
}
const assessRisk = (id: string, likelihood: RiskLikelihood, impact: RiskImpact): void => {
const severity = likelihood * impact
let severityLevel: Risk['severity']
if (severity >= 20) severityLevel = 'CRITICAL'
else if (severity >= 12) severityLevel = 'HIGH'
else if (severity >= 6) severityLevel = 'MEDIUM'
else severityLevel = 'LOW'
dispatch({
type: 'UPDATE_RISK',
payload: {
id,
updates: {
likelihood,
impact,
severity: severityLevel,
status: 'ANALYZED',
},
},
})
}
// Compliance Score
const complianceScore = computed(() => {
const score = compliance.calculateComplianceScore()
return score.overall
})
const scoreByDomain = computed(() => {
const domains: ControlDomain[] = [
'ACCESS_CONTROL',
'DATA_PROTECTION',
'NETWORK_SECURITY',
'INCIDENT_RESPONSE',
'BUSINESS_CONTINUITY',
'COMPLIANCE',
'RISK_MANAGEMENT',
'ASSET_MANAGEMENT',
'HUMAN_RESOURCES',
]
return domains.reduce(
(acc, domain) => {
const domainControls = state.controls.filter(c => c.domain === domain)
if (domainControls.length === 0) {
acc[domain] = 0
return acc
}
const implemented = domainControls.filter(c => c.implementationStatus === 'IMPLEMENTED')
const partial = domainControls.filter(c => c.implementationStatus === 'PARTIAL')
acc[domain] = Math.round(
((implemented.length + partial.length * 0.5) / domainControls.length) * 100
)
return acc
},
{} as Record<ControlDomain, number>
)
})
return {
controls,
controlsByDomain,
implementedControls,
pendingControls,
getControl,
addControl,
updateControl,
deleteControl,
setControlStatus,
evidence,
validEvidence,
expiredEvidence,
getEvidenceForControl,
addEvidence,
updateEvidence,
deleteEvidence,
risks,
criticalRisks,
unmitigatedRisks,
getRisksForControl,
addRisk,
updateRisk,
deleteRisk,
assessRisk,
complianceScore,
scoreByDomain,
}
}

View File

@@ -0,0 +1,246 @@
/**
* DSGVO/GDPR composable
*/
import { computed, type ComputedRef, type Ref, ref } from 'vue'
import { useComplianceStore } from '../plugin'
import type {
DSRRequest,
DSRRequestType,
ConsentRecord,
ConsentPurpose,
ProcessingActivity,
TOM,
TOMCategory,
DSFA,
RetentionPolicy,
} from '@breakpilot/compliance-sdk-types'
export interface UseDSGVOReturn {
// DSR
dsrRequests: ComputedRef<DSRRequest[]>
pendingDSRCount: ComputedRef<number>
submitDSR: (type: DSRRequestType, email: string, name: string) => Promise<DSRRequest>
updateDSRStatus: (
requestId: string,
status: DSRRequest['status'],
notes?: string
) => Promise<void>
// Consent
consents: ComputedRef<ConsentRecord[]>
activeConsents: ComputedRef<ConsentRecord[]>
hasConsent: (purpose: ConsentPurpose) => boolean
recordConsent: (
purpose: ConsentPurpose,
granted: boolean,
source: string
) => Promise<ConsentRecord>
revokeConsent: (consentId: string) => Promise<void>
// VVT (Processing Activities)
processingActivities: ComputedRef<ProcessingActivity[]>
addProcessingActivity: (activity: Omit<ProcessingActivity, 'id' | 'createdAt'>) => void
updateProcessingActivity: (id: string, updates: Partial<ProcessingActivity>) => void
deleteProcessingActivity: (id: string) => void
// TOM
toms: ComputedRef<TOM[]>
tomsByCategory: ComputedRef<Record<TOMCategory, TOM[]>>
addTOM: (tom: Omit<TOM, 'id' | 'createdAt' | 'updatedAt'>) => void
updateTOM: (id: string, updates: Partial<TOM>) => void
deleteTOM: (id: string) => void
// DSFA
dsfas: ComputedRef<DSFA[]>
createDSFA: (title: string, description: string) => DSFA
updateDSFA: (id: string, updates: Partial<DSFA>) => void
// Retention
retentionPolicies: ComputedRef<RetentionPolicy[]>
addRetentionPolicy: (policy: Omit<RetentionPolicy, 'id'>) => void
updateRetentionPolicy: (id: string, updates: Partial<RetentionPolicy>) => void
deleteRetentionPolicy: (id: string) => void
// Loading state
isLoading: Ref<boolean>
}
export function useDSGVO(): UseDSGVOReturn {
const store = useComplianceStore()
const { state, dispatch, dsgvo } = store
const isLoading = ref(false)
// DSR
const dsrRequests = computed(() => state.dsrRequests)
const pendingDSRCount = computed(
() => state.dsrRequests.filter(r => r.status === 'PENDING' || r.status === 'IN_PROGRESS').length
)
const submitDSR = async (
type: DSRRequestType,
email: string,
name: string
): Promise<DSRRequest> => {
isLoading.value = true
try {
return await dsgvo.submitDSR(type, email, name)
} finally {
isLoading.value = false
}
}
const updateDSRStatus = async (
requestId: string,
status: DSRRequest['status'],
notes?: string
): Promise<void> => {
isLoading.value = true
try {
await dsgvo.updateDSRStatus(requestId, status, notes)
} finally {
isLoading.value = false
}
}
// Consent
const consents = computed(() => state.consents)
const activeConsents = computed(() =>
state.consents.filter(c => c.status === 'ACTIVE' && !c.revokedAt)
)
const hasConsent = (purpose: ConsentPurpose): boolean => {
return dsgvo.hasActiveConsent(purpose)
}
const recordConsent = async (
purpose: ConsentPurpose,
granted: boolean,
source: string
): Promise<ConsentRecord> => {
isLoading.value = true
try {
return await dsgvo.recordConsent(purpose, granted, source)
} finally {
isLoading.value = false
}
}
const revokeConsent = async (consentId: string): Promise<void> => {
isLoading.value = true
try {
await dsgvo.revokeConsent(consentId)
} finally {
isLoading.value = false
}
}
// VVT
const processingActivities = computed(() => state.processingActivities)
const addProcessingActivity = (activity: Omit<ProcessingActivity, 'id' | 'createdAt'>): void => {
dispatch({ type: 'ADD_PROCESSING_ACTIVITY', payload: activity })
}
const updateProcessingActivity = (id: string, updates: Partial<ProcessingActivity>): void => {
dispatch({ type: 'UPDATE_PROCESSING_ACTIVITY', payload: { id, updates } })
}
const deleteProcessingActivity = (id: string): void => {
dispatch({ type: 'DELETE_PROCESSING_ACTIVITY', payload: id })
}
// TOM
const toms = computed(() => state.toms)
const tomsByCategory = computed(() => {
const categories: TOMCategory[] = [
'ZUTRITTSKONTROLLE',
'ZUGANGSKONTROLLE',
'ZUGRIFFSKONTROLLE',
'WEITERGABEKONTROLLE',
'EINGABEKONTROLLE',
'AUFTRAGSKONTROLLE',
'VERFUEGBARKEITSKONTROLLE',
'TRENNUNGSGEBOT',
]
return categories.reduce(
(acc, cat) => {
acc[cat] = state.toms.filter(t => t.category === cat)
return acc
},
{} as Record<TOMCategory, TOM[]>
)
})
const addTOM = (tom: Omit<TOM, 'id' | 'createdAt' | 'updatedAt'>): void => {
dispatch({ type: 'ADD_TOM', payload: tom })
}
const updateTOM = (id: string, updates: Partial<TOM>): void => {
dispatch({ type: 'UPDATE_TOM', payload: { id, updates } })
}
const deleteTOM = (id: string): void => {
dispatch({ type: 'DELETE_TOM', payload: id })
}
// DSFA
const dsfas = computed(() => state.dsfas)
const createDSFA = (title: string, description: string): DSFA => {
return dsgvo.createDSFA(title, description)
}
const updateDSFA = (id: string, updates: Partial<DSFA>): void => {
dispatch({ type: 'UPDATE_DSFA', payload: { id, updates } })
}
// Retention
const retentionPolicies = computed(() => state.retentionPolicies)
const addRetentionPolicy = (policy: Omit<RetentionPolicy, 'id'>): void => {
dispatch({ type: 'ADD_RETENTION_POLICY', payload: policy })
}
const updateRetentionPolicy = (id: string, updates: Partial<RetentionPolicy>): void => {
dispatch({ type: 'UPDATE_RETENTION_POLICY', payload: { id, updates } })
}
const deleteRetentionPolicy = (id: string): void => {
dispatch({ type: 'DELETE_RETENTION_POLICY', payload: id })
}
return {
dsrRequests,
pendingDSRCount,
submitDSR,
updateDSRStatus,
consents,
activeConsents,
hasConsent,
recordConsent,
revokeConsent,
processingActivities,
addProcessingActivity,
updateProcessingActivity,
deleteProcessingActivity,
toms,
tomsByCategory,
addTOM,
updateTOM,
deleteTOM,
dsfas,
createDSFA,
updateDSFA,
retentionPolicies,
addRetentionPolicy,
updateRetentionPolicy,
deleteRetentionPolicy,
isLoading,
}
}

View File

@@ -0,0 +1,136 @@
/**
* RAG (Retrieval-Augmented Generation) composable
*/
import { computed, type ComputedRef, type Ref, ref, reactive } from 'vue'
import { useComplianceStore } from '../plugin'
import type {
SearchResponse,
AssistantResponse,
ChatMessage,
LegalDocument,
} from '@breakpilot/compliance-sdk-types'
export interface UseRAGReturn {
// Search
search: (query: string, regulationCodes?: string[]) => Promise<SearchResponse>
searchResults: Ref<SearchResponse | null>
// Chat
ask: (question: string, context?: string) => Promise<AssistantResponse>
chatHistory: Ref<ChatMessage[]>
clearChat: () => void
isTyping: Ref<boolean>
// Documents
documents: ComputedRef<LegalDocument[]>
availableRegulations: ComputedRef<string[]>
// Loading state
isLoading: Ref<boolean>
error: Ref<Error | null>
}
export function useRAG(): UseRAGReturn {
const store = useComplianceStore()
const { state, rag } = store
const isLoading = ref(false)
const isTyping = ref(false)
const error = ref<Error | null>(null)
const searchResults = ref<SearchResponse | null>(null)
const chatHistory = ref<ChatMessage[]>([])
// Search
const search = async (query: string, regulationCodes?: string[]): Promise<SearchResponse> => {
isLoading.value = true
error.value = null
try {
const results = await rag.search(query, regulationCodes)
searchResults.value = results
return results
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err))
throw err
} finally {
isLoading.value = false
}
}
// Chat
const ask = async (question: string, context?: string): Promise<AssistantResponse> => {
isLoading.value = true
isTyping.value = true
error.value = null
// Add user message to history
chatHistory.value.push({
id: `msg_${Date.now()}`,
role: 'user',
content: question,
timestamp: new Date().toISOString(),
})
try {
const response = await rag.ask(question, context)
// Add assistant response to history
chatHistory.value.push({
id: `msg_${Date.now()}`,
role: 'assistant',
content: response.answer,
timestamp: new Date().toISOString(),
citations: response.citations,
})
return response
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err))
// Add error message to history
chatHistory.value.push({
id: `msg_${Date.now()}`,
role: 'assistant',
content: 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.',
timestamp: new Date().toISOString(),
})
throw err
} finally {
isLoading.value = false
isTyping.value = false
}
}
const clearChat = (): void => {
chatHistory.value = []
rag.clearHistory()
}
// Documents
const documents = computed(() => state.legalDocuments)
const availableRegulations = computed(() => {
const codes = new Set<string>()
state.legalDocuments.forEach(doc => {
if (doc.regulationCode) {
codes.add(doc.regulationCode)
}
})
return Array.from(codes).sort()
})
return {
search,
searchResults,
ask,
chatHistory,
clearChat,
isTyping,
documents,
availableRegulations,
isLoading,
error,
}
}

View File

@@ -0,0 +1,185 @@
/**
* Security composable for SBOM and security scanning
*/
import { computed, type ComputedRef, type Ref, ref } from 'vue'
import { useComplianceStore } from '../plugin'
import type {
SBOM,
SBOMComponent,
SecurityIssue,
Vulnerability,
SecurityScanResult,
} from '@breakpilot/compliance-sdk-types'
export interface UseSecurityReturn {
// SBOM
sbom: ComputedRef<SBOM | null>
components: ComputedRef<SBOMComponent[]>
componentsByCategory: ComputedRef<Record<string, SBOMComponent[]>>
vulnerableComponents: ComputedRef<SBOMComponent[]>
generateSBOM: (source?: string) => Promise<SBOM>
exportSBOM: (format: 'cyclonedx' | 'spdx') => Promise<Blob>
// Security Issues
securityIssues: ComputedRef<SecurityIssue[]>
criticalIssues: ComputedRef<SecurityIssue[]>
openIssues: ComputedRef<SecurityIssue[]>
issuesBySeverity: ComputedRef<Record<string, SecurityIssue[]>>
// Scanning
scan: (options?: ScanOptions) => Promise<SecurityScanResult>
lastScanResult: Ref<SecurityScanResult | null>
isScanning: Ref<boolean>
// Vulnerabilities
vulnerabilities: ComputedRef<Vulnerability[]>
getVulnerabilitiesForComponent: (componentId: string) => Vulnerability[]
// Loading state
isLoading: Ref<boolean>
error: Ref<Error | null>
}
export interface ScanOptions {
tools?: string[]
targetPath?: string
excludePaths?: string[]
}
export function useSecurity(): UseSecurityReturn {
const store = useComplianceStore()
const { state, security } = store
const isLoading = ref(false)
const isScanning = ref(false)
const error = ref<Error | null>(null)
const lastScanResult = ref<SecurityScanResult | null>(null)
// SBOM
const sbom = computed(() => state.sbom)
const components = computed(() => state.sbom?.components || [])
const componentsByCategory = computed(() => {
const comps = state.sbom?.components || []
return comps.reduce(
(acc, comp) => {
const cat = comp.category || 'other'
if (!acc[cat]) acc[cat] = []
acc[cat].push(comp)
return acc
},
{} as Record<string, SBOMComponent[]>
)
})
const vulnerableComponents = computed(() => {
return components.value.filter(c => c.vulnerabilities && c.vulnerabilities.length > 0)
})
const generateSBOM = async (source?: string): Promise<SBOM> => {
isLoading.value = true
error.value = null
try {
return await security.generateSBOM(source)
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err))
throw err
} finally {
isLoading.value = false
}
}
const exportSBOM = async (format: 'cyclonedx' | 'spdx'): Promise<Blob> => {
isLoading.value = true
error.value = null
try {
return await security.exportSBOM(format)
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err))
throw err
} finally {
isLoading.value = false
}
}
// Security Issues
const securityIssues = computed(() => state.securityIssues)
const criticalIssues = computed(() =>
state.securityIssues.filter(i => i.severity === 'CRITICAL' || i.severity === 'HIGH')
)
const openIssues = computed(() =>
state.securityIssues.filter(i => i.status === 'OPEN' || i.status === 'IN_PROGRESS')
)
const issuesBySeverity = computed(() => {
return state.securityIssues.reduce(
(acc, issue) => {
if (!acc[issue.severity]) acc[issue.severity] = []
acc[issue.severity].push(issue)
return acc
},
{} as Record<string, SecurityIssue[]>
)
})
// Scanning
const scan = async (options?: ScanOptions): Promise<SecurityScanResult> => {
isScanning.value = true
isLoading.value = true
error.value = null
try {
const result = await security.scan(options)
lastScanResult.value = result
return result
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err))
throw err
} finally {
isScanning.value = false
isLoading.value = false
}
}
// Vulnerabilities
const vulnerabilities = computed(() => {
const vulns: Vulnerability[] = []
components.value.forEach(comp => {
if (comp.vulnerabilities) {
vulns.push(...comp.vulnerabilities)
}
})
return vulns
})
const getVulnerabilitiesForComponent = (componentId: string): Vulnerability[] => {
const comp = components.value.find(c => c.name === componentId)
return comp?.vulnerabilities || []
}
return {
sbom,
components,
componentsByCategory,
vulnerableComponents,
generateSBOM,
exportSBOM,
securityIssues,
criticalIssues,
openIssues,
issuesBySeverity,
scan,
lastScanResult,
isScanning,
vulnerabilities,
getVulnerabilitiesForComponent,
isLoading,
error,
}
}