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:
49
breakpilot-compliance-sdk/packages/cli/package.json
Normal file
49
breakpilot-compliance-sdk/packages/cli/package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@breakpilot/compliance-sdk-cli",
|
||||
"version": "0.0.1",
|
||||
"description": "BreakPilot Compliance SDK - CLI Tool",
|
||||
"bin": {
|
||||
"breakpilot": "./dist/cli.js",
|
||||
"bp": "./dist/cli.js"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"lint": "eslint src/",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@breakpilot/compliance-sdk-core": "workspace:*",
|
||||
"@breakpilot/compliance-sdk-types": "workspace:*",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.0.0",
|
||||
"inquirer": "^9.2.0",
|
||||
"ora": "^8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/inquirer": "^9.0.0",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/breakpilot/compliance-sdk.git",
|
||||
"directory": "packages/cli"
|
||||
},
|
||||
"keywords": [
|
||||
"compliance",
|
||||
"gdpr",
|
||||
"dsgvo",
|
||||
"cli"
|
||||
]
|
||||
}
|
||||
35
breakpilot-compliance-sdk/packages/cli/src/cli.ts
Normal file
35
breakpilot-compliance-sdk/packages/cli/src/cli.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* BreakPilot Compliance SDK CLI
|
||||
*
|
||||
* Commands:
|
||||
* - init: Initialize a new compliance project
|
||||
* - deploy: Deploy to hardware (Mac Mini/Mac Studio)
|
||||
* - scan: Run security scans
|
||||
* - export: Export compliance reports
|
||||
* - status: Check compliance status
|
||||
*/
|
||||
|
||||
import { Command } from 'commander'
|
||||
import { initCommand } from './commands/init'
|
||||
import { deployCommand } from './commands/deploy'
|
||||
import { scanCommand } from './commands/scan'
|
||||
import { exportCommand } from './commands/export'
|
||||
import { statusCommand } from './commands/status'
|
||||
|
||||
const program = new Command()
|
||||
|
||||
program
|
||||
.name('breakpilot')
|
||||
.description('BreakPilot Compliance SDK CLI')
|
||||
.version('0.0.1')
|
||||
|
||||
// Register commands
|
||||
program.addCommand(initCommand)
|
||||
program.addCommand(deployCommand)
|
||||
program.addCommand(scanCommand)
|
||||
program.addCommand(exportCommand)
|
||||
program.addCommand(statusCommand)
|
||||
|
||||
program.parse()
|
||||
217
breakpilot-compliance-sdk/packages/cli/src/commands/deploy.ts
Normal file
217
breakpilot-compliance-sdk/packages/cli/src/commands/deploy.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* Deploy command - Deploy to hardware (Mac Mini/Mac Studio)
|
||||
*/
|
||||
|
||||
import { Command } from 'commander'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
interface DeployOptions {
|
||||
target?: string
|
||||
config?: string
|
||||
dryRun?: boolean
|
||||
}
|
||||
|
||||
export const deployCommand = new Command('deploy')
|
||||
.description('Deploy compliance services to hardware')
|
||||
.option('-t, --target <target>', 'Deployment target (mac-mini, mac-studio, cloud)', 'mac-mini')
|
||||
.option('-c, --config <path>', 'Path to config file', './breakpilot.config.json')
|
||||
.option('--dry-run', 'Show what would be deployed without actually deploying', false)
|
||||
.action(async (options: DeployOptions) => {
|
||||
const chalk = (await import('chalk')).default
|
||||
const ora = (await import('ora')).default
|
||||
const inquirer = (await import('inquirer')).default
|
||||
|
||||
console.log(chalk.bold.blue('\n🚀 BreakPilot Compliance SDK - Deployment\n'))
|
||||
|
||||
// Check for config file
|
||||
const configPath = path.resolve(options.config || './breakpilot.config.json')
|
||||
if (!fs.existsSync(configPath)) {
|
||||
console.log(chalk.yellow('⚠️ No config file found. Run `breakpilot init` first.'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
||||
|
||||
// Prompt for deployment details
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'target',
|
||||
message: 'Select deployment target:',
|
||||
choices: [
|
||||
{ name: 'Mac Mini M4 Pro (64GB RAM, Qwen 2.5 32B)', value: 'mac-mini' },
|
||||
{ name: 'Mac Studio M2 Ultra (512GB RAM, Qwen 2.5 40B)', value: 'mac-studio' },
|
||||
{ name: 'Cloud (BreakPilot Servers)', value: 'cloud' },
|
||||
],
|
||||
default: options.target,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'hostname',
|
||||
message: 'Target hostname/IP:',
|
||||
when: (answers) => answers.target !== 'cloud',
|
||||
default: 'localhost',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'sshUser',
|
||||
message: 'SSH username:',
|
||||
when: (answers) => answers.target !== 'cloud',
|
||||
default: process.env.USER || 'admin',
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'installLLM',
|
||||
message: 'Install/update LLM model?',
|
||||
when: (answers) => answers.target !== 'cloud',
|
||||
default: true,
|
||||
},
|
||||
])
|
||||
|
||||
if (options.dryRun) {
|
||||
console.log(chalk.yellow('\n📋 Dry run - no changes will be made\n'))
|
||||
}
|
||||
|
||||
const spinner = ora('Preparing deployment...').start()
|
||||
|
||||
try {
|
||||
// Generate docker-compose.yml
|
||||
const dockerCompose = generateDockerCompose(answers.target, config)
|
||||
|
||||
if (options.dryRun) {
|
||||
spinner.info('Would generate docker-compose.yml:')
|
||||
console.log(chalk.gray(dockerCompose))
|
||||
return
|
||||
}
|
||||
|
||||
// For actual deployment, we'd:
|
||||
// 1. SSH to target machine
|
||||
// 2. Copy docker-compose.yml
|
||||
// 3. Run docker-compose up -d
|
||||
// 4. Install LLM if requested
|
||||
|
||||
spinner.text = 'Connecting to target...'
|
||||
await sleep(1000) // Simulated
|
||||
|
||||
spinner.text = 'Deploying services...'
|
||||
await sleep(2000) // Simulated
|
||||
|
||||
if (answers.installLLM) {
|
||||
spinner.text = 'Installing LLM model (this may take a while)...'
|
||||
await sleep(2000) // Simulated
|
||||
}
|
||||
|
||||
spinner.succeed('Deployment complete!')
|
||||
|
||||
console.log(chalk.green('\n✅ Services deployed successfully'))
|
||||
console.log(chalk.gray('\nDeployed services:'))
|
||||
console.log(chalk.gray(' - API Gateway (port 443)'))
|
||||
console.log(chalk.gray(' - Compliance Engine'))
|
||||
console.log(chalk.gray(' - RAG Service'))
|
||||
console.log(chalk.gray(' - Security Scanner'))
|
||||
console.log(chalk.gray(' - PostgreSQL'))
|
||||
console.log(chalk.gray(' - Qdrant'))
|
||||
console.log(chalk.gray(' - MinIO'))
|
||||
if (answers.installLLM) {
|
||||
const model = answers.target === 'mac-studio' ? 'qwen2.5:40b' : 'qwen2.5:32b'
|
||||
console.log(chalk.gray(` - Ollama (${model})`))
|
||||
}
|
||||
|
||||
console.log(chalk.blue('\n🔗 Access your services at:'))
|
||||
console.log(chalk.white(` https://${answers.hostname || 'localhost'}/api/v1`))
|
||||
} catch (error) {
|
||||
spinner.fail('Deployment failed')
|
||||
console.error(chalk.red('Error:'), error)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
function generateDockerCompose(target: string, config: Record<string, unknown>): string {
|
||||
const llmModel = target === 'mac-studio' ? 'qwen2.5:40b' : 'qwen2.5:32b'
|
||||
|
||||
return `# Generated by BreakPilot CLI
|
||||
# Target: ${target}
|
||||
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
api-gateway:
|
||||
image: ghcr.io/breakpilot/compliance-sdk-gateway:latest
|
||||
ports:
|
||||
- "443:443"
|
||||
- "80:80"
|
||||
environment:
|
||||
- DATABASE_URL=postgres://breakpilot:breakpilot@postgres:5432/compliance
|
||||
- QDRANT_URL=http://qdrant:6333
|
||||
- OLLAMA_URL=http://host.docker.internal:11434
|
||||
- MINIO_URL=http://minio:9000
|
||||
depends_on:
|
||||
- postgres
|
||||
- qdrant
|
||||
- minio
|
||||
restart: unless-stopped
|
||||
|
||||
compliance-engine:
|
||||
image: ghcr.io/breakpilot/compliance-engine:latest
|
||||
environment:
|
||||
- DATABASE_URL=postgres://breakpilot:breakpilot@postgres:5432/compliance
|
||||
depends_on:
|
||||
- postgres
|
||||
restart: unless-stopped
|
||||
|
||||
rag-service:
|
||||
image: ghcr.io/breakpilot/rag-service:latest
|
||||
environment:
|
||||
- QDRANT_URL=http://qdrant:6333
|
||||
- OLLAMA_URL=http://host.docker.internal:11434
|
||||
- EMBEDDING_MODEL=bge-m3
|
||||
depends_on:
|
||||
- qdrant
|
||||
restart: unless-stopped
|
||||
|
||||
security-scanner:
|
||||
image: ghcr.io/breakpilot/security-scanner:latest
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=breakpilot
|
||||
- POSTGRES_PASSWORD=breakpilot
|
||||
- POSTGRES_DB=compliance
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
|
||||
qdrant:
|
||||
image: qdrant/qdrant:v1.12.1
|
||||
volumes:
|
||||
- qdrant_data:/qdrant/storage
|
||||
restart: unless-stopped
|
||||
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
command: server /data --console-address ":9001"
|
||||
environment:
|
||||
- MINIO_ROOT_USER=breakpilot
|
||||
- MINIO_ROOT_PASSWORD=breakpilot123
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
qdrant_data:
|
||||
minio_data:
|
||||
|
||||
# Note: Ollama runs on the host system
|
||||
# Install with: ollama pull ${llmModel}
|
||||
`
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
222
breakpilot-compliance-sdk/packages/cli/src/commands/export.ts
Normal file
222
breakpilot-compliance-sdk/packages/cli/src/commands/export.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Export command - Export compliance reports
|
||||
*/
|
||||
|
||||
import { Command } from 'commander'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
interface ExportOptions {
|
||||
format?: string
|
||||
output?: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
export const exportCommand = new Command('export')
|
||||
.description('Export compliance reports and documentation')
|
||||
.option('-f, --format <format>', 'Export format (pdf, docx, json, csv)', 'pdf')
|
||||
.option('-o, --output <path>', 'Output file path')
|
||||
.option('-t, --type <type>', 'Report type (full, summary, vvt, tom, dsfa, controls, risks)', 'summary')
|
||||
.action(async (options: ExportOptions) => {
|
||||
const chalk = (await import('chalk')).default
|
||||
const ora = (await import('ora')).default
|
||||
const inquirer = (await import('inquirer')).default
|
||||
|
||||
console.log(chalk.bold.blue('\n📄 BreakPilot Compliance Export\n'))
|
||||
|
||||
// Prompt for export details
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'type',
|
||||
message: 'Select report type:',
|
||||
choices: [
|
||||
{ name: 'Full Compliance Report', value: 'full' },
|
||||
{ name: 'Executive Summary', value: 'summary' },
|
||||
{ name: 'Verarbeitungsverzeichnis (VVT)', value: 'vvt' },
|
||||
{ name: 'Technische & Organisatorische Maßnahmen (TOM)', value: 'tom' },
|
||||
{ name: 'Datenschutz-Folgenabschätzung (DSFA)', value: 'dsfa' },
|
||||
{ name: 'Controls Overview', value: 'controls' },
|
||||
{ name: 'Risk Register', value: 'risks' },
|
||||
{ name: 'SBOM (Software Bill of Materials)', value: 'sbom' },
|
||||
],
|
||||
default: options.type,
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'format',
|
||||
message: 'Select export format:',
|
||||
choices: [
|
||||
{ name: 'PDF', value: 'pdf' },
|
||||
{ name: 'Word Document (DOCX)', value: 'docx' },
|
||||
{ name: 'JSON', value: 'json' },
|
||||
{ name: 'CSV', value: 'csv' },
|
||||
{ name: 'CycloneDX (SBOM only)', value: 'cyclonedx' },
|
||||
{ name: 'SPDX (SBOM only)', value: 'spdx' },
|
||||
],
|
||||
default: options.format,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'output',
|
||||
message: 'Output file path:',
|
||||
default: (answers: { type: string; format: string }) =>
|
||||
`compliance-${answers.type}-${new Date().toISOString().split('T')[0]}.${answers.format}`,
|
||||
},
|
||||
])
|
||||
|
||||
const spinner = ora('Generating report...').start()
|
||||
|
||||
try {
|
||||
// In a real implementation, this would:
|
||||
// 1. Connect to the compliance API
|
||||
// 2. Fetch all relevant data
|
||||
// 3. Generate the report in the requested format
|
||||
|
||||
spinner.text = 'Fetching compliance data...'
|
||||
await sleep(1000)
|
||||
|
||||
spinner.text = 'Generating document...'
|
||||
await sleep(1500)
|
||||
|
||||
const outputPath = path.resolve(answers.output)
|
||||
|
||||
// Generate mock output
|
||||
const content = generateMockReport(answers.type, answers.format)
|
||||
fs.writeFileSync(outputPath, content)
|
||||
|
||||
spinner.succeed('Report generated successfully!')
|
||||
|
||||
console.log(chalk.green('\n✅ Export complete'))
|
||||
console.log(chalk.gray(` File: ${outputPath}`))
|
||||
console.log(chalk.gray(` Type: ${answers.type}`))
|
||||
console.log(chalk.gray(` Format: ${answers.format}`))
|
||||
|
||||
// Show report preview for JSON
|
||||
if (answers.format === 'json') {
|
||||
console.log(chalk.bold('\n📋 Preview:\n'))
|
||||
const preview = JSON.parse(content)
|
||||
console.log(chalk.gray(JSON.stringify(preview, null, 2).substring(0, 500) + '...'))
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail('Export failed')
|
||||
console.error(chalk.red('Error:'), error)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
function generateMockReport(type: string, format: string): string {
|
||||
const reportData = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
reportType: type,
|
||||
format: format,
|
||||
organization: 'Example Organization',
|
||||
complianceScore: 78,
|
||||
summary: {
|
||||
totalControls: 44,
|
||||
implementedControls: 35,
|
||||
partialControls: 6,
|
||||
openControls: 3,
|
||||
totalRisks: 12,
|
||||
criticalRisks: 1,
|
||||
highRisks: 2,
|
||||
regulations: ['DSGVO', 'NIS2', 'AI Act'],
|
||||
},
|
||||
sections: getSectionsForType(type),
|
||||
}
|
||||
|
||||
if (format === 'json' || format === 'cyclonedx' || format === 'spdx') {
|
||||
return JSON.stringify(reportData, null, 2)
|
||||
}
|
||||
|
||||
// For PDF/DOCX, we'd use a proper document generation library
|
||||
// For now, return a placeholder
|
||||
return `[${format.toUpperCase()} Report - ${type}]\n\n${JSON.stringify(reportData, null, 2)}`
|
||||
}
|
||||
|
||||
function getSectionsForType(type: string): Record<string, unknown>[] {
|
||||
switch (type) {
|
||||
case 'vvt':
|
||||
return [
|
||||
{
|
||||
id: 'vvt-1',
|
||||
name: 'Kundenmanagement',
|
||||
purpose: 'Verwaltung von Kundenbeziehungen',
|
||||
legalBasis: 'Art. 6 Abs. 1 lit. b DSGVO',
|
||||
dataCategories: ['Kontaktdaten', 'Vertragsdetails', 'Kommunikationshistorie'],
|
||||
retentionPeriod: '10 Jahre nach Vertragsende',
|
||||
},
|
||||
{
|
||||
id: 'vvt-2',
|
||||
name: 'Personalverwaltung',
|
||||
purpose: 'Verwaltung von Mitarbeiterdaten',
|
||||
legalBasis: 'Art. 6 Abs. 1 lit. b, c DSGVO',
|
||||
dataCategories: ['Personaldaten', 'Gehaltsdaten', 'Leistungsdaten'],
|
||||
retentionPeriod: '10 Jahre nach Beendigung',
|
||||
},
|
||||
]
|
||||
case 'tom':
|
||||
return [
|
||||
{
|
||||
category: 'Zutrittskontrolle',
|
||||
measures: [
|
||||
'Zutrittskontrollsystem mit Chipkarten',
|
||||
'Videoüberwachung der Eingänge',
|
||||
'Besucherregistrierung',
|
||||
],
|
||||
},
|
||||
{
|
||||
category: 'Zugangskontrolle',
|
||||
measures: [
|
||||
'Passwort-Policy (min. 12 Zeichen)',
|
||||
'Multi-Faktor-Authentifizierung',
|
||||
'Automatische Sperrung nach 5 Fehlversuchen',
|
||||
],
|
||||
},
|
||||
]
|
||||
case 'controls':
|
||||
return [
|
||||
{
|
||||
id: 'ctrl-1',
|
||||
domain: 'ACCESS_CONTROL',
|
||||
title: 'Benutzerauthentifizierung',
|
||||
status: 'IMPLEMENTED',
|
||||
evidence: ['auth-policy.pdf', 'mfa-config.png'],
|
||||
},
|
||||
{
|
||||
id: 'ctrl-2',
|
||||
domain: 'DATA_PROTECTION',
|
||||
title: 'Datenverschlüsselung',
|
||||
status: 'IMPLEMENTED',
|
||||
evidence: ['encryption-certificate.pdf'],
|
||||
},
|
||||
]
|
||||
case 'risks':
|
||||
return [
|
||||
{
|
||||
id: 'risk-1',
|
||||
title: 'Datenverlust durch Cyberangriff',
|
||||
likelihood: 3,
|
||||
impact: 5,
|
||||
severity: 'HIGH',
|
||||
mitigation: 'Backup-Strategie, Incident Response Plan',
|
||||
status: 'MITIGATED',
|
||||
},
|
||||
{
|
||||
id: 'risk-2',
|
||||
title: 'DSGVO-Bußgeld wegen unzureichender Dokumentation',
|
||||
likelihood: 2,
|
||||
impact: 4,
|
||||
severity: 'MEDIUM',
|
||||
mitigation: 'Regelmäßige Dokumentationsaudits',
|
||||
status: 'MONITORING',
|
||||
},
|
||||
]
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
221
breakpilot-compliance-sdk/packages/cli/src/commands/init.ts
Normal file
221
breakpilot-compliance-sdk/packages/cli/src/commands/init.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* Init command - Initialize a new compliance project
|
||||
*/
|
||||
|
||||
import { Command } from 'commander'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
interface InitOptions {
|
||||
template?: string
|
||||
force?: boolean
|
||||
}
|
||||
|
||||
export const initCommand = new Command('init')
|
||||
.description('Initialize a new compliance project')
|
||||
.argument('[directory]', 'Directory to initialize', '.')
|
||||
.option('-t, --template <template>', 'Project template (react, vue, vanilla)', 'react')
|
||||
.option('-f, --force', 'Overwrite existing files', false)
|
||||
.action(async (directory: string, options: InitOptions) => {
|
||||
const chalk = (await import('chalk')).default
|
||||
const ora = (await import('ora')).default
|
||||
const inquirer = (await import('inquirer')).default
|
||||
|
||||
console.log(chalk.bold.blue('\n🚀 BreakPilot Compliance SDK - Project Setup\n'))
|
||||
|
||||
// Prompt for configuration
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'projectName',
|
||||
message: 'Project name:',
|
||||
default: path.basename(path.resolve(directory)),
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'template',
|
||||
message: 'Select a template:',
|
||||
choices: [
|
||||
{ name: 'React (Recommended)', value: 'react' },
|
||||
{ name: 'Vue 3', value: 'vue' },
|
||||
{ name: 'Vanilla JS', value: 'vanilla' },
|
||||
],
|
||||
default: options.template,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'apiEndpoint',
|
||||
message: 'API Endpoint:',
|
||||
default: 'https://compliance.breakpilot.app/api/v1',
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'includeComponents',
|
||||
message: 'Include pre-built UI components?',
|
||||
default: true,
|
||||
},
|
||||
])
|
||||
|
||||
const spinner = ora('Setting up project...').start()
|
||||
|
||||
try {
|
||||
const targetDir = path.resolve(directory)
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true })
|
||||
}
|
||||
|
||||
// Create config file
|
||||
const configPath = path.join(targetDir, 'breakpilot.config.json')
|
||||
const config = {
|
||||
projectName: answers.projectName,
|
||||
template: answers.template,
|
||||
apiEndpoint: answers.apiEndpoint,
|
||||
version: '0.0.1',
|
||||
features: {
|
||||
dsgvo: true,
|
||||
compliance: true,
|
||||
rag: true,
|
||||
security: true,
|
||||
},
|
||||
}
|
||||
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
|
||||
|
||||
// Create .env.example
|
||||
const envExamplePath = path.join(targetDir, '.env.example')
|
||||
const envContent = `# BreakPilot Compliance SDK Configuration
|
||||
BREAKPILOT_API_ENDPOINT=${answers.apiEndpoint}
|
||||
BREAKPILOT_API_KEY=pk_live_xxx
|
||||
BREAKPILOT_TENANT_ID=your_tenant_id
|
||||
`
|
||||
fs.writeFileSync(envExamplePath, envContent)
|
||||
|
||||
// Create example usage file based on template
|
||||
const examplePath = path.join(targetDir, `compliance-example.${getExtension(answers.template)}`)
|
||||
fs.writeFileSync(examplePath, getExampleCode(answers.template, answers.apiEndpoint))
|
||||
|
||||
spinner.succeed('Project initialized successfully!')
|
||||
|
||||
console.log(chalk.green('\n✅ Project created at:'), chalk.white(targetDir))
|
||||
console.log(chalk.gray('\nNext steps:'))
|
||||
console.log(chalk.gray(' 1. Copy .env.example to .env and fill in your API key'))
|
||||
console.log(chalk.gray(` 2. Install the SDK: npm install @breakpilot/compliance-sdk-${answers.template}`))
|
||||
console.log(chalk.gray(' 3. Import and use the SDK in your application'))
|
||||
console.log(chalk.gray('\nDocumentation: https://docs.breakpilot.app/sdk'))
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to initialize project')
|
||||
console.error(chalk.red('Error:'), error)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
function getExtension(template: string): string {
|
||||
switch (template) {
|
||||
case 'react':
|
||||
return 'tsx'
|
||||
case 'vue':
|
||||
return 'vue'
|
||||
default:
|
||||
return 'js'
|
||||
}
|
||||
}
|
||||
|
||||
function getExampleCode(template: string, apiEndpoint: string): string {
|
||||
switch (template) {
|
||||
case 'react':
|
||||
return `// Example React integration
|
||||
import {
|
||||
ComplianceProvider,
|
||||
ConsentBanner,
|
||||
DSRPortal,
|
||||
useCompliance
|
||||
} from '@breakpilot/compliance-sdk-react';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ComplianceProvider
|
||||
apiEndpoint="${apiEndpoint}"
|
||||
apiKey={process.env.BREAKPILOT_API_KEY}
|
||||
tenantId={process.env.BREAKPILOT_TENANT_ID}
|
||||
>
|
||||
<ConsentBanner position="BOTTOM" theme="LIGHT" />
|
||||
<MyApp />
|
||||
</ComplianceProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function MyApp() {
|
||||
const { state, compliance, rag } = useCompliance();
|
||||
|
||||
const askQuestion = async () => {
|
||||
const answer = await rag.ask('Was ist bei Art. 9 DSGVO zu beachten?');
|
||||
console.log(answer);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Compliance Score: {compliance.calculateComplianceScore().overall}%</h1>
|
||||
<button onClick={askQuestion}>Frage stellen</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
`
|
||||
case 'vue':
|
||||
return `<!-- Example Vue 3 integration -->
|
||||
<script setup lang="ts">
|
||||
import { useCompliance, useRAG } from '@breakpilot/compliance-sdk-vue';
|
||||
|
||||
const { state, complianceScore } = useCompliance();
|
||||
const { ask, chatHistory, isLoading } = useRAG();
|
||||
|
||||
const askQuestion = async () => {
|
||||
await ask('Was ist bei Art. 9 DSGVO zu beachten?');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Compliance Score: {{ complianceScore }}%</h1>
|
||||
<button @click="askQuestion" :disabled="isLoading">
|
||||
{{ isLoading ? 'Lädt...' : 'Frage stellen' }}
|
||||
</button>
|
||||
<div v-for="msg in chatHistory" :key="msg.id">
|
||||
<strong>{{ msg.role }}:</strong> {{ msg.content }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
`
|
||||
default:
|
||||
return `// Example Vanilla JS integration
|
||||
import { BreakPilotSDK } from '@breakpilot/compliance-sdk-vanilla';
|
||||
|
||||
BreakPilotSDK.init({
|
||||
apiEndpoint: '${apiEndpoint}',
|
||||
apiKey: 'pk_live_xxx',
|
||||
autoInjectBanner: true,
|
||||
bannerConfig: {
|
||||
position: 'BOTTOM',
|
||||
theme: 'LIGHT',
|
||||
language: 'de'
|
||||
},
|
||||
onConsentChange: (consents) => {
|
||||
console.log('Consents updated:', consents);
|
||||
if (consents.ANALYTICS) {
|
||||
// Load analytics
|
||||
}
|
||||
},
|
||||
onReady: () => {
|
||||
console.log('SDK ready!');
|
||||
}
|
||||
});
|
||||
|
||||
// Or use Web Components:
|
||||
// <breakpilot-consent-banner api-key="pk_live_xxx" theme="light" position="bottom">
|
||||
// </breakpilot-consent-banner>
|
||||
`
|
||||
}
|
||||
}
|
||||
228
breakpilot-compliance-sdk/packages/cli/src/commands/scan.ts
Normal file
228
breakpilot-compliance-sdk/packages/cli/src/commands/scan.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* Scan command - Run security scans
|
||||
*/
|
||||
|
||||
import { Command } from 'commander'
|
||||
import * as path from 'path'
|
||||
|
||||
interface ScanOptions {
|
||||
tools?: string
|
||||
output?: string
|
||||
format?: string
|
||||
severity?: string
|
||||
}
|
||||
|
||||
export const scanCommand = new Command('scan')
|
||||
.description('Run security scans on your codebase')
|
||||
.argument('[path]', 'Path to scan', '.')
|
||||
.option('-t, --tools <tools>', 'Comma-separated list of tools (gitleaks,semgrep,bandit,trivy,grype,syft)', 'all')
|
||||
.option('-o, --output <path>', 'Output file path')
|
||||
.option('-f, --format <format>', 'Output format (json, sarif, table)', 'table')
|
||||
.option('-s, --severity <level>', 'Minimum severity to report (low, medium, high, critical)', 'low')
|
||||
.action(async (scanPath: string, options: ScanOptions) => {
|
||||
const chalk = (await import('chalk')).default
|
||||
const ora = (await import('ora')).default
|
||||
|
||||
console.log(chalk.bold.blue('\n🔒 BreakPilot Security Scanner\n'))
|
||||
|
||||
const targetPath = path.resolve(scanPath)
|
||||
console.log(chalk.gray(`Scanning: ${targetPath}\n`))
|
||||
|
||||
const tools = options.tools === 'all'
|
||||
? ['gitleaks', 'semgrep', 'bandit', 'trivy', 'grype', 'syft']
|
||||
: options.tools!.split(',').map(t => t.trim())
|
||||
|
||||
const results: ScanResult[] = []
|
||||
|
||||
for (const tool of tools) {
|
||||
const spinner = ora(`Running ${tool}...`).start()
|
||||
|
||||
try {
|
||||
// Simulate running the tool
|
||||
await sleep(1500)
|
||||
|
||||
const toolResult = simulateToolResult(tool)
|
||||
results.push(toolResult)
|
||||
|
||||
if (toolResult.findings.length > 0) {
|
||||
spinner.warn(`${tool}: ${toolResult.findings.length} findings`)
|
||||
} else {
|
||||
spinner.succeed(`${tool}: No issues found`)
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail(`${tool}: Failed`)
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log(chalk.bold('\n📊 Scan Summary\n'))
|
||||
|
||||
const allFindings = results.flatMap(r => r.findings)
|
||||
const bySeverity = {
|
||||
critical: allFindings.filter(f => f.severity === 'CRITICAL'),
|
||||
high: allFindings.filter(f => f.severity === 'HIGH'),
|
||||
medium: allFindings.filter(f => f.severity === 'MEDIUM'),
|
||||
low: allFindings.filter(f => f.severity === 'LOW'),
|
||||
}
|
||||
|
||||
if (bySeverity.critical.length > 0) {
|
||||
console.log(chalk.red(` 🔴 Critical: ${bySeverity.critical.length}`))
|
||||
}
|
||||
if (bySeverity.high.length > 0) {
|
||||
console.log(chalk.red(` 🟠 High: ${bySeverity.high.length}`))
|
||||
}
|
||||
if (bySeverity.medium.length > 0) {
|
||||
console.log(chalk.yellow(` 🟡 Medium: ${bySeverity.medium.length}`))
|
||||
}
|
||||
if (bySeverity.low.length > 0) {
|
||||
console.log(chalk.gray(` 🟢 Low: ${bySeverity.low.length}`))
|
||||
}
|
||||
|
||||
if (allFindings.length === 0) {
|
||||
console.log(chalk.green(' ✅ No security issues found!'))
|
||||
} else {
|
||||
console.log(chalk.gray(`\n Total: ${allFindings.length} findings`))
|
||||
|
||||
// Show top findings
|
||||
if (options.format === 'table') {
|
||||
console.log(chalk.bold('\n📋 Top Findings\n'))
|
||||
|
||||
const topFindings = allFindings
|
||||
.sort((a, b) => severityOrder(b.severity) - severityOrder(a.severity))
|
||||
.slice(0, 10)
|
||||
|
||||
for (const finding of topFindings) {
|
||||
const severityColor = getSeverityColor(finding.severity)
|
||||
console.log(
|
||||
` ${severityColor(finding.severity.padEnd(8))} ` +
|
||||
`${chalk.gray(finding.tool.padEnd(10))} ` +
|
||||
`${finding.title}`
|
||||
)
|
||||
if (finding.file) {
|
||||
console.log(chalk.gray(` └─ ${finding.file}:${finding.line || '?'}`))
|
||||
}
|
||||
}
|
||||
|
||||
if (allFindings.length > 10) {
|
||||
console.log(chalk.gray(`\n ... and ${allFindings.length - 10} more findings`))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write output if requested
|
||||
if (options.output) {
|
||||
const fs = await import('fs')
|
||||
const output = {
|
||||
scanDate: new Date().toISOString(),
|
||||
targetPath,
|
||||
tools,
|
||||
summary: {
|
||||
total: allFindings.length,
|
||||
critical: bySeverity.critical.length,
|
||||
high: bySeverity.high.length,
|
||||
medium: bySeverity.medium.length,
|
||||
low: bySeverity.low.length,
|
||||
},
|
||||
findings: allFindings,
|
||||
}
|
||||
|
||||
fs.writeFileSync(options.output, JSON.stringify(output, null, 2))
|
||||
console.log(chalk.gray(`\nResults written to: ${options.output}`))
|
||||
}
|
||||
|
||||
// Exit with error if critical findings
|
||||
if (bySeverity.critical.length > 0 || bySeverity.high.length > 0) {
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
interface Finding {
|
||||
id: string
|
||||
tool: string
|
||||
severity: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW'
|
||||
title: string
|
||||
description?: string
|
||||
file?: string
|
||||
line?: number
|
||||
recommendation?: string
|
||||
}
|
||||
|
||||
interface ScanResult {
|
||||
tool: string
|
||||
findings: Finding[]
|
||||
}
|
||||
|
||||
function simulateToolResult(tool: string): ScanResult {
|
||||
// Simulate some findings for demonstration
|
||||
const findings: Finding[] = []
|
||||
|
||||
switch (tool) {
|
||||
case 'gitleaks':
|
||||
// Secrets detection - usually finds nothing in clean repos
|
||||
break
|
||||
case 'semgrep':
|
||||
findings.push({
|
||||
id: 'semgrep-1',
|
||||
tool: 'semgrep',
|
||||
severity: 'MEDIUM',
|
||||
title: 'Potential SQL injection',
|
||||
description: 'User input used in SQL query without parameterization',
|
||||
file: 'src/db/queries.ts',
|
||||
line: 42,
|
||||
recommendation: 'Use parameterized queries instead of string concatenation',
|
||||
})
|
||||
break
|
||||
case 'bandit':
|
||||
// Python security - skip if not a Python project
|
||||
break
|
||||
case 'trivy':
|
||||
findings.push({
|
||||
id: 'trivy-1',
|
||||
tool: 'trivy',
|
||||
severity: 'HIGH',
|
||||
title: 'CVE-2024-1234 in lodash@4.17.20',
|
||||
description: 'Prototype pollution vulnerability',
|
||||
recommendation: 'Upgrade to lodash@4.17.21 or higher',
|
||||
})
|
||||
break
|
||||
case 'grype':
|
||||
findings.push({
|
||||
id: 'grype-1',
|
||||
tool: 'grype',
|
||||
severity: 'LOW',
|
||||
title: 'Outdated dependency: axios@0.21.0',
|
||||
recommendation: 'Update to latest version',
|
||||
})
|
||||
break
|
||||
case 'syft':
|
||||
// SBOM generation - no findings, just metadata
|
||||
break
|
||||
}
|
||||
|
||||
return { tool, findings }
|
||||
}
|
||||
|
||||
function severityOrder(severity: string): number {
|
||||
switch (severity) {
|
||||
case 'CRITICAL': return 4
|
||||
case 'HIGH': return 3
|
||||
case 'MEDIUM': return 2
|
||||
case 'LOW': return 1
|
||||
default: return 0
|
||||
}
|
||||
}
|
||||
|
||||
function getSeverityColor(severity: string): (text: string) => string {
|
||||
const chalk = require('chalk')
|
||||
switch (severity) {
|
||||
case 'CRITICAL': return chalk.red.bold
|
||||
case 'HIGH': return chalk.red
|
||||
case 'MEDIUM': return chalk.yellow
|
||||
case 'LOW': return chalk.gray
|
||||
default: return chalk.white
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
161
breakpilot-compliance-sdk/packages/cli/src/commands/status.ts
Normal file
161
breakpilot-compliance-sdk/packages/cli/src/commands/status.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Status command - Check compliance status
|
||||
*/
|
||||
|
||||
import { Command } from 'commander'
|
||||
|
||||
interface StatusOptions {
|
||||
json?: boolean
|
||||
verbose?: boolean
|
||||
}
|
||||
|
||||
export const statusCommand = new Command('status')
|
||||
.description('Check current compliance status')
|
||||
.option('-j, --json', 'Output as JSON', false)
|
||||
.option('-v, --verbose', 'Show detailed status', false)
|
||||
.action(async (options: StatusOptions) => {
|
||||
const chalk = (await import('chalk')).default
|
||||
const ora = (await import('ora')).default
|
||||
|
||||
if (!options.json) {
|
||||
console.log(chalk.bold.blue('\n📊 BreakPilot Compliance Status\n'))
|
||||
}
|
||||
|
||||
const spinner = options.json ? null : ora('Fetching status...').start()
|
||||
|
||||
try {
|
||||
// Simulate fetching status from API
|
||||
await sleep(1000)
|
||||
|
||||
const status = {
|
||||
lastUpdated: new Date().toISOString(),
|
||||
overallScore: 78,
|
||||
trend: 'UP',
|
||||
regulations: {
|
||||
DSGVO: { score: 85, status: 'COMPLIANT' },
|
||||
NIS2: { score: 72, status: 'PARTIAL' },
|
||||
'AI Act': { score: 65, status: 'IN_PROGRESS' },
|
||||
},
|
||||
controls: {
|
||||
total: 44,
|
||||
implemented: 35,
|
||||
partial: 6,
|
||||
notImplemented: 3,
|
||||
},
|
||||
risks: {
|
||||
total: 12,
|
||||
critical: 1,
|
||||
high: 2,
|
||||
medium: 5,
|
||||
low: 4,
|
||||
},
|
||||
evidence: {
|
||||
total: 28,
|
||||
valid: 24,
|
||||
expiring: 3,
|
||||
expired: 1,
|
||||
},
|
||||
dsrRequests: {
|
||||
pending: 2,
|
||||
inProgress: 1,
|
||||
completed: 15,
|
||||
},
|
||||
nextActions: [
|
||||
{
|
||||
priority: 'HIGH',
|
||||
action: 'Address critical risk: Data breach potential',
|
||||
dueDate: '2024-02-15',
|
||||
},
|
||||
{
|
||||
priority: 'MEDIUM',
|
||||
action: 'Update expired evidence: Security audit report',
|
||||
dueDate: '2024-02-20',
|
||||
},
|
||||
{
|
||||
priority: 'MEDIUM',
|
||||
action: 'Complete NIS2 gap assessment',
|
||||
dueDate: '2024-03-01',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(status, null, 2))
|
||||
return
|
||||
}
|
||||
|
||||
spinner?.succeed('Status retrieved')
|
||||
|
||||
// Overall Score
|
||||
const scoreColor = status.overallScore >= 80 ? chalk.green :
|
||||
status.overallScore >= 60 ? chalk.yellow : chalk.red
|
||||
const trendIcon = status.trend === 'UP' ? '↑' :
|
||||
status.trend === 'DOWN' ? '↓' : '→'
|
||||
|
||||
console.log(chalk.bold('\n🎯 Overall Compliance Score'))
|
||||
console.log(` ${scoreColor.bold(status.overallScore + '%')} ${chalk.gray(trendIcon)}`)
|
||||
|
||||
// Regulations
|
||||
console.log(chalk.bold('\n📜 Regulations'))
|
||||
Object.entries(status.regulations).forEach(([reg, data]) => {
|
||||
const color = data.score >= 80 ? chalk.green :
|
||||
data.score >= 60 ? chalk.yellow : chalk.red
|
||||
const statusIcon = data.status === 'COMPLIANT' ? '✓' :
|
||||
data.status === 'PARTIAL' ? '◐' : '○'
|
||||
console.log(` ${statusIcon} ${reg.padEnd(12)} ${color(data.score + '%')} ${chalk.gray(data.status)}`)
|
||||
})
|
||||
|
||||
// Controls
|
||||
console.log(chalk.bold('\n🔧 Controls'))
|
||||
console.log(` ${chalk.green('●')} Implemented: ${status.controls.implemented}`)
|
||||
console.log(` ${chalk.yellow('●')} Partial: ${status.controls.partial}`)
|
||||
console.log(` ${chalk.red('●')} Not Implemented: ${status.controls.notImplemented}`)
|
||||
console.log(chalk.gray(` Total: ${status.controls.total}`))
|
||||
|
||||
// Risks
|
||||
console.log(chalk.bold('\n⚠️ Risks'))
|
||||
if (status.risks.critical > 0) {
|
||||
console.log(` ${chalk.red.bold('🔴')} Critical: ${status.risks.critical}`)
|
||||
}
|
||||
if (status.risks.high > 0) {
|
||||
console.log(` ${chalk.red('🟠')} High: ${status.risks.high}`)
|
||||
}
|
||||
console.log(` ${chalk.yellow('🟡')} Medium: ${status.risks.medium}`)
|
||||
console.log(` ${chalk.gray('🟢')} Low: ${status.risks.low}`)
|
||||
|
||||
// Evidence
|
||||
if (options.verbose) {
|
||||
console.log(chalk.bold('\n📎 Evidence'))
|
||||
console.log(` ${chalk.green('●')} Valid: ${status.evidence.valid}`)
|
||||
console.log(` ${chalk.yellow('●')} Expiring: ${status.evidence.expiring}`)
|
||||
console.log(` ${chalk.red('●')} Expired: ${status.evidence.expired}`)
|
||||
}
|
||||
|
||||
// DSR Requests
|
||||
if (options.verbose) {
|
||||
console.log(chalk.bold('\n📬 DSR Requests'))
|
||||
console.log(` Pending: ${status.dsrRequests.pending}`)
|
||||
console.log(` In Progress: ${status.dsrRequests.inProgress}`)
|
||||
console.log(` Completed: ${status.dsrRequests.completed}`)
|
||||
}
|
||||
|
||||
// Next Actions
|
||||
console.log(chalk.bold('\n📋 Next Actions'))
|
||||
status.nextActions.forEach(action => {
|
||||
const priorityColor = action.priority === 'HIGH' ? chalk.red :
|
||||
action.priority === 'MEDIUM' ? chalk.yellow : chalk.gray
|
||||
console.log(` ${priorityColor('●')} ${action.action}`)
|
||||
console.log(chalk.gray(` Due: ${action.dueDate}`))
|
||||
})
|
||||
|
||||
console.log('')
|
||||
} catch (error) {
|
||||
spinner?.fail('Failed to fetch status')
|
||||
console.error(chalk.red('Error:'), error)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
18
breakpilot-compliance-sdk/packages/cli/src/index.ts
Normal file
18
breakpilot-compliance-sdk/packages/cli/src/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @breakpilot/compliance-sdk-cli
|
||||
*
|
||||
* CLI tool for BreakPilot Compliance SDK
|
||||
*
|
||||
* Commands:
|
||||
* - init: Initialize a new compliance project
|
||||
* - deploy: Deploy to hardware (Mac Mini/Mac Studio)
|
||||
* - scan: Run security scans
|
||||
* - export: Export compliance reports
|
||||
* - status: Check compliance status
|
||||
*/
|
||||
|
||||
export { initCommand } from './commands/init'
|
||||
export { deployCommand } from './commands/deploy'
|
||||
export { scanCommand } from './commands/scan'
|
||||
export { exportCommand } from './commands/export'
|
||||
export { statusCommand } from './commands/status'
|
||||
11
breakpilot-compliance-sdk/packages/cli/tsconfig.json
Normal file
11
breakpilot-compliance-sdk/packages/cli/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
18
breakpilot-compliance-sdk/packages/cli/tsup.config.ts
Normal file
18
breakpilot-compliance-sdk/packages/cli/tsup.config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
cli: 'src/cli.ts',
|
||||
},
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
treeshake: true,
|
||||
shims: true,
|
||||
banner: {
|
||||
js: '#!/usr/bin/env node',
|
||||
},
|
||||
})
|
||||
52
breakpilot-compliance-sdk/packages/core/package.json
Normal file
52
breakpilot-compliance-sdk/packages/core/package.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@breakpilot/compliance-sdk-core",
|
||||
"version": "1.0.0",
|
||||
"description": "Core functionality for BreakPilot Compliance SDK",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./client": {
|
||||
"import": "./dist/client.mjs",
|
||||
"require": "./dist/client.js",
|
||||
"types": "./dist/client.d.ts"
|
||||
},
|
||||
"./state": {
|
||||
"import": "./dist/state.mjs",
|
||||
"require": "./dist/state.js",
|
||||
"types": "./dist/state.d.ts"
|
||||
},
|
||||
"./sync": {
|
||||
"import": "./dist/sync.mjs",
|
||||
"require": "./dist/sync.js",
|
||||
"types": "./dist/sync.d.ts"
|
||||
}
|
||||
},
|
||||
"files": ["dist"],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@breakpilot/compliance-sdk-types": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"tsup": "^8.0.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vitest": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
}
|
||||
}
|
||||
295
breakpilot-compliance-sdk/packages/core/src/auth.ts
Normal file
295
breakpilot-compliance-sdk/packages/core/src/auth.ts
Normal file
@@ -0,0 +1,295 @@
|
||||
/**
|
||||
* Authentication Provider
|
||||
*
|
||||
* Manages authentication state and token lifecycle
|
||||
*/
|
||||
|
||||
import type { AuthTokenResponse } from '@breakpilot/compliance-sdk-types'
|
||||
import { ComplianceClient } from './client'
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface AuthProviderOptions {
|
||||
client: ComplianceClient
|
||||
clientId: string
|
||||
clientSecret?: string
|
||||
storage?: Storage
|
||||
onAuthStateChange?: (state: AuthState) => void
|
||||
}
|
||||
|
||||
export interface AuthState {
|
||||
isAuthenticated: boolean
|
||||
accessToken: string | null
|
||||
refreshToken: string | null
|
||||
expiresAt: Date | null
|
||||
user?: {
|
||||
id: string
|
||||
email: string
|
||||
name: string
|
||||
role: string
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONSTANTS
|
||||
// =============================================================================
|
||||
|
||||
const STORAGE_KEY = 'breakpilot-sdk-auth'
|
||||
const TOKEN_REFRESH_BUFFER = 5 * 60 * 1000 // 5 minutes before expiry
|
||||
|
||||
// =============================================================================
|
||||
// AUTH PROVIDER
|
||||
// =============================================================================
|
||||
|
||||
export class AuthProvider {
|
||||
private client: ComplianceClient
|
||||
private clientId: string
|
||||
private clientSecret?: string
|
||||
private storage: Storage | null
|
||||
private state: AuthState
|
||||
private refreshTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
private onAuthStateChange?: (state: AuthState) => void
|
||||
|
||||
constructor(options: AuthProviderOptions) {
|
||||
this.client = options.client
|
||||
this.clientId = options.clientId
|
||||
this.clientSecret = options.clientSecret
|
||||
this.storage = options.storage ?? (typeof localStorage !== 'undefined' ? localStorage : null)
|
||||
this.onAuthStateChange = options.onAuthStateChange
|
||||
|
||||
this.state = {
|
||||
isAuthenticated: false,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
expiresAt: null,
|
||||
}
|
||||
|
||||
this.loadFromStorage()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private loadFromStorage(): void {
|
||||
if (!this.storage) return
|
||||
|
||||
try {
|
||||
const stored = this.storage.getItem(STORAGE_KEY)
|
||||
if (stored) {
|
||||
const data = JSON.parse(stored)
|
||||
if (data.expiresAt) {
|
||||
data.expiresAt = new Date(data.expiresAt)
|
||||
}
|
||||
|
||||
// Check if token is still valid
|
||||
if (data.expiresAt && data.expiresAt > new Date()) {
|
||||
this.state = data
|
||||
this.client.setAccessToken(data.accessToken)
|
||||
this.scheduleTokenRefresh()
|
||||
this.notifyStateChange()
|
||||
} else if (data.refreshToken) {
|
||||
// Try to refresh
|
||||
this.refreshToken().catch(() => {
|
||||
this.clearAuth()
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load auth from storage:', error)
|
||||
}
|
||||
}
|
||||
|
||||
private saveToStorage(): void {
|
||||
if (!this.storage) return
|
||||
|
||||
try {
|
||||
this.storage.setItem(STORAGE_KEY, JSON.stringify(this.state))
|
||||
} catch (error) {
|
||||
console.error('Failed to save auth to storage:', error)
|
||||
}
|
||||
}
|
||||
|
||||
private clearStorage(): void {
|
||||
if (!this.storage) return
|
||||
|
||||
try {
|
||||
this.storage.removeItem(STORAGE_KEY)
|
||||
} catch (error) {
|
||||
console.error('Failed to clear auth from storage:', error)
|
||||
}
|
||||
}
|
||||
|
||||
private updateState(tokenResponse: AuthTokenResponse): void {
|
||||
const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1000)
|
||||
|
||||
this.state = {
|
||||
isAuthenticated: true,
|
||||
accessToken: tokenResponse.accessToken,
|
||||
refreshToken: tokenResponse.refreshToken ?? this.state.refreshToken,
|
||||
expiresAt,
|
||||
}
|
||||
|
||||
this.client.setAccessToken(tokenResponse.accessToken)
|
||||
this.saveToStorage()
|
||||
this.scheduleTokenRefresh()
|
||||
this.notifyStateChange()
|
||||
}
|
||||
|
||||
private scheduleTokenRefresh(): void {
|
||||
if (this.refreshTimeout) {
|
||||
clearTimeout(this.refreshTimeout)
|
||||
}
|
||||
|
||||
if (!this.state.expiresAt || !this.state.refreshToken) return
|
||||
|
||||
const timeUntilRefresh = this.state.expiresAt.getTime() - Date.now() - TOKEN_REFRESH_BUFFER
|
||||
|
||||
if (timeUntilRefresh > 0) {
|
||||
this.refreshTimeout = setTimeout(() => {
|
||||
this.refreshToken().catch(() => {
|
||||
this.clearAuth()
|
||||
})
|
||||
}, timeUntilRefresh)
|
||||
}
|
||||
}
|
||||
|
||||
private notifyStateChange(): void {
|
||||
this.onAuthStateChange?.(this.state)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Authenticate using client credentials (server-to-server)
|
||||
*/
|
||||
async authenticateWithCredentials(): Promise<void> {
|
||||
if (!this.clientSecret) {
|
||||
throw new Error('Client secret is required for credentials authentication')
|
||||
}
|
||||
|
||||
const response = await this.client.authenticate({
|
||||
grantType: 'client_credentials',
|
||||
clientId: this.clientId,
|
||||
clientSecret: this.clientSecret,
|
||||
})
|
||||
|
||||
this.updateState(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate using authorization code (OAuth flow)
|
||||
*/
|
||||
async authenticateWithCode(code: string, redirectUri: string): Promise<void> {
|
||||
const response = await this.client.authenticate({
|
||||
grantType: 'authorization_code',
|
||||
clientId: this.clientId,
|
||||
clientSecret: this.clientSecret,
|
||||
code,
|
||||
redirectUri,
|
||||
})
|
||||
|
||||
this.updateState(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the access token
|
||||
*/
|
||||
async refreshToken(): Promise<void> {
|
||||
if (!this.state.refreshToken) {
|
||||
throw new Error('No refresh token available')
|
||||
}
|
||||
|
||||
const response = await this.client.refreshToken(this.state.refreshToken)
|
||||
this.updateState(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set access token manually (e.g., from API key)
|
||||
*/
|
||||
setAccessToken(token: string, expiresIn?: number): void {
|
||||
const expiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1000) : null
|
||||
|
||||
this.state = {
|
||||
isAuthenticated: true,
|
||||
accessToken: token,
|
||||
refreshToken: null,
|
||||
expiresAt,
|
||||
}
|
||||
|
||||
this.client.setAccessToken(token)
|
||||
this.saveToStorage()
|
||||
this.notifyStateChange()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear authentication state
|
||||
*/
|
||||
clearAuth(): void {
|
||||
if (this.refreshTimeout) {
|
||||
clearTimeout(this.refreshTimeout)
|
||||
}
|
||||
|
||||
this.state = {
|
||||
isAuthenticated: false,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
expiresAt: null,
|
||||
}
|
||||
|
||||
this.client.clearAccessToken()
|
||||
this.clearStorage()
|
||||
this.notifyStateChange()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current authentication state
|
||||
*/
|
||||
getState(): AuthState {
|
||||
return { ...this.state }
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if currently authenticated
|
||||
*/
|
||||
isAuthenticated(): boolean {
|
||||
if (!this.state.isAuthenticated || !this.state.accessToken) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.state.expiresAt && this.state.expiresAt <= new Date()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the authorization URL for OAuth flow
|
||||
*/
|
||||
getAuthorizationUrl(redirectUri: string, scope?: string, state?: string): string {
|
||||
const params = new URLSearchParams({
|
||||
client_id: this.clientId,
|
||||
redirect_uri: redirectUri,
|
||||
response_type: 'code',
|
||||
...(scope && { scope }),
|
||||
...(state && { state }),
|
||||
})
|
||||
|
||||
// This would be configured based on the API endpoint
|
||||
return `/oauth/authorize?${params.toString()}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the auth provider
|
||||
*/
|
||||
destroy(): void {
|
||||
if (this.refreshTimeout) {
|
||||
clearTimeout(this.refreshTimeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
521
breakpilot-compliance-sdk/packages/core/src/client.ts
Normal file
521
breakpilot-compliance-sdk/packages/core/src/client.ts
Normal file
@@ -0,0 +1,521 @@
|
||||
/**
|
||||
* Compliance Client
|
||||
*
|
||||
* Main entry point for the SDK. Handles API communication with
|
||||
* retry logic, timeout handling, and optimistic locking.
|
||||
*/
|
||||
|
||||
import type {
|
||||
APIResponse,
|
||||
StateResponse,
|
||||
CheckpointValidationResult,
|
||||
AuthTokenRequest,
|
||||
AuthTokenResponse,
|
||||
RAGSearchRequest,
|
||||
RAGSearchResponse,
|
||||
RAGAskRequest,
|
||||
RAGAskResponse,
|
||||
ExportFormat,
|
||||
SDKState,
|
||||
CheckpointStatus,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface ComplianceClientOptions {
|
||||
apiEndpoint: string
|
||||
apiKey?: string
|
||||
tenantId: string
|
||||
timeout?: number
|
||||
maxRetries?: number
|
||||
onError?: (error: Error) => void
|
||||
onAuthError?: () => void
|
||||
}
|
||||
|
||||
interface APIError extends Error {
|
||||
status?: number
|
||||
code?: string
|
||||
retryable: boolean
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONSTANTS
|
||||
// =============================================================================
|
||||
|
||||
const DEFAULT_TIMEOUT = 30000
|
||||
const DEFAULT_MAX_RETRIES = 3
|
||||
const RETRY_DELAYS = [1000, 2000, 4000]
|
||||
|
||||
// =============================================================================
|
||||
// COMPLIANCE CLIENT
|
||||
// =============================================================================
|
||||
|
||||
export class ComplianceClient {
|
||||
private apiEndpoint: string
|
||||
private apiKey: string | null
|
||||
private tenantId: string
|
||||
private timeout: number
|
||||
private maxRetries: number
|
||||
private accessToken: string | null = null
|
||||
private abortControllers: Map<string, AbortController> = new Map()
|
||||
private onError?: (error: Error) => void
|
||||
private onAuthError?: () => void
|
||||
|
||||
constructor(options: ComplianceClientOptions) {
|
||||
this.apiEndpoint = options.apiEndpoint.replace(/\/$/, '')
|
||||
this.apiKey = options.apiKey ?? null
|
||||
this.tenantId = options.tenantId
|
||||
this.timeout = options.timeout ?? DEFAULT_TIMEOUT
|
||||
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES
|
||||
this.onError = options.onError
|
||||
this.onAuthError = options.onAuthError
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private createError(message: string, status?: number, retryable = false): APIError {
|
||||
const error = new Error(message) as APIError
|
||||
error.status = status
|
||||
error.retryable = retryable
|
||||
return error
|
||||
}
|
||||
|
||||
private getHeaders(): Record<string, string> {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Tenant-ID': this.tenantId,
|
||||
}
|
||||
|
||||
if (this.accessToken) {
|
||||
headers['Authorization'] = `Bearer ${this.accessToken}`
|
||||
} else if (this.apiKey) {
|
||||
headers['Authorization'] = `Bearer ${this.apiKey}`
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
private async fetchWithTimeout(
|
||||
url: string,
|
||||
options: RequestInit,
|
||||
requestId: string
|
||||
): Promise<Response> {
|
||||
const controller = new AbortController()
|
||||
this.abortControllers.set(requestId, controller)
|
||||
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.timeout)
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
signal: controller.signal,
|
||||
})
|
||||
return response
|
||||
} finally {
|
||||
clearTimeout(timeoutId)
|
||||
this.abortControllers.delete(requestId)
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchWithRetry<T>(
|
||||
url: string,
|
||||
options: RequestInit,
|
||||
retries = this.maxRetries
|
||||
): Promise<T> {
|
||||
const requestId = `${Date.now()}-${Math.random()}`
|
||||
let lastError: Error | null = null
|
||||
|
||||
for (let attempt = 0; attempt <= retries; attempt++) {
|
||||
try {
|
||||
const response = await this.fetchWithTimeout(url, options, requestId)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text()
|
||||
let errorMessage = `HTTP ${response.status}`
|
||||
|
||||
try {
|
||||
const errorJson = JSON.parse(errorBody)
|
||||
errorMessage = errorJson.error || errorJson.message || errorMessage
|
||||
} catch {
|
||||
// Keep HTTP status message
|
||||
}
|
||||
|
||||
// Handle auth errors
|
||||
if (response.status === 401) {
|
||||
this.onAuthError?.()
|
||||
throw this.createError(errorMessage, response.status, false)
|
||||
}
|
||||
|
||||
// Don't retry client errors (4xx) except 429
|
||||
const retryable = response.status >= 500 || response.status === 429
|
||||
|
||||
if (!retryable || attempt === retries) {
|
||||
throw this.createError(errorMessage, response.status, retryable)
|
||||
}
|
||||
} else {
|
||||
const data = await response.json()
|
||||
return data as T
|
||||
}
|
||||
} catch (error) {
|
||||
lastError = error as Error
|
||||
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
throw this.createError('Request timeout', 408, true)
|
||||
}
|
||||
|
||||
const apiError = error as APIError
|
||||
if (!apiError.retryable || attempt === retries) {
|
||||
this.onError?.(error as Error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Exponential backoff
|
||||
if (attempt < retries) {
|
||||
await this.sleep(RETRY_DELAYS[attempt] || RETRY_DELAYS[RETRY_DELAYS.length - 1])
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || this.createError('Unknown error', 500, false)
|
||||
}
|
||||
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Authentication
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async authenticate(request: AuthTokenRequest): Promise<AuthTokenResponse> {
|
||||
const response = await this.fetchWithRetry<APIResponse<AuthTokenResponse>>(
|
||||
`${this.apiEndpoint}/auth/token`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(request),
|
||||
}
|
||||
)
|
||||
|
||||
if (response.success && response.data) {
|
||||
this.accessToken = response.data.accessToken
|
||||
return response.data
|
||||
}
|
||||
|
||||
throw this.createError(response.error || 'Authentication failed', 401, false)
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string): Promise<AuthTokenResponse> {
|
||||
return this.authenticate({
|
||||
grantType: 'refresh_token',
|
||||
clientId: '',
|
||||
refreshToken,
|
||||
})
|
||||
}
|
||||
|
||||
setAccessToken(token: string): void {
|
||||
this.accessToken = token
|
||||
}
|
||||
|
||||
clearAccessToken(): void {
|
||||
this.accessToken = null
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// State Management
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getState(): Promise<StateResponse | null> {
|
||||
try {
|
||||
const response = await this.fetchWithRetry<APIResponse<StateResponse>>(
|
||||
`${this.apiEndpoint}/state?tenantId=${encodeURIComponent(this.tenantId)}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: this.getHeaders(),
|
||||
}
|
||||
)
|
||||
|
||||
if (response.success && response.data) {
|
||||
return response.data
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (error) {
|
||||
const apiError = error as APIError
|
||||
if (apiError.status === 404) {
|
||||
return null
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async saveState(state: SDKState, version?: number): Promise<StateResponse> {
|
||||
const response = await this.fetchWithRetry<APIResponse<StateResponse>>(
|
||||
`${this.apiEndpoint}/state`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...this.getHeaders(),
|
||||
...(version !== undefined && { 'If-Match': String(version) }),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tenantId: this.tenantId,
|
||||
state,
|
||||
version,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.success) {
|
||||
throw this.createError(response.error || 'Failed to save state', 500, true)
|
||||
}
|
||||
|
||||
return response.data!
|
||||
}
|
||||
|
||||
async deleteState(): Promise<void> {
|
||||
await this.fetchWithRetry<APIResponse<void>>(
|
||||
`${this.apiEndpoint}/state?tenantId=${encodeURIComponent(this.tenantId)}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: this.getHeaders(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Checkpoints
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async validateCheckpoint(
|
||||
checkpointId: string,
|
||||
data?: unknown
|
||||
): Promise<CheckpointValidationResult> {
|
||||
const response = await this.fetchWithRetry<APIResponse<CheckpointValidationResult>>(
|
||||
`${this.apiEndpoint}/checkpoints/validate`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
tenantId: this.tenantId,
|
||||
checkpointId,
|
||||
data,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
throw this.createError(response.error || 'Checkpoint validation failed', 500, true)
|
||||
}
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
async getCheckpoints(): Promise<Record<string, CheckpointStatus>> {
|
||||
const response = await this.fetchWithRetry<APIResponse<Record<string, CheckpointStatus>>>(
|
||||
`${this.apiEndpoint}/checkpoints?tenantId=${encodeURIComponent(this.tenantId)}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: this.getHeaders(),
|
||||
}
|
||||
)
|
||||
|
||||
return response.data || {}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RAG
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async searchRAG(request: RAGSearchRequest): Promise<RAGSearchResponse> {
|
||||
const response = await this.fetchWithRetry<APIResponse<RAGSearchResponse>>(
|
||||
`${this.apiEndpoint}/rag/search`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify(request),
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
throw this.createError(response.error || 'RAG search failed', 500, true)
|
||||
}
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
async askRAG(request: RAGAskRequest): Promise<RAGAskResponse> {
|
||||
const response = await this.fetchWithRetry<APIResponse<RAGAskResponse>>(
|
||||
`${this.apiEndpoint}/rag/ask`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify(request),
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
throw this.createError(response.error || 'RAG query failed', 500, true)
|
||||
}
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Export
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async exportState(format: ExportFormat): Promise<Blob> {
|
||||
const response = await this.fetchWithTimeout(
|
||||
`${this.apiEndpoint}/export?tenantId=${encodeURIComponent(this.tenantId)}&format=${format}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
...this.getHeaders(),
|
||||
Accept:
|
||||
format === 'json'
|
||||
? 'application/json'
|
||||
: format === 'pdf'
|
||||
? 'application/pdf'
|
||||
: 'application/octet-stream',
|
||||
},
|
||||
},
|
||||
`export-${Date.now()}`
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw this.createError(`Export failed: ${response.statusText}`, response.status, true)
|
||||
}
|
||||
|
||||
return response.blob()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Document Generation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async generateDocument(
|
||||
type: 'dsfa' | 'tom' | 'vvt' | 'gutachten' | 'privacy_policy' | 'cookie_banner',
|
||||
options?: Record<string, unknown>
|
||||
): Promise<{ id: string; status: string; content?: string }> {
|
||||
const response = await this.fetchWithRetry<
|
||||
APIResponse<{ id: string; status: string; content?: string }>
|
||||
>(
|
||||
`${this.apiEndpoint}/generate/${type}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
tenantId: this.tenantId,
|
||||
options,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
throw this.createError(response.error || 'Document generation failed', 500, true)
|
||||
}
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Security Scan
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async startSecurityScan(options?: {
|
||||
tools?: string[]
|
||||
targetPath?: string
|
||||
severityThreshold?: string
|
||||
generateSBOM?: boolean
|
||||
}): Promise<{ id: string; status: string }> {
|
||||
const response = await this.fetchWithRetry<APIResponse<{ id: string; status: string }>>(
|
||||
`${this.apiEndpoint}/security/scan`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
tenantId: this.tenantId,
|
||||
...options,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
throw this.createError(response.error || 'Security scan failed', 500, true)
|
||||
}
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
async getSecurityScanResult(scanId: string): Promise<unknown> {
|
||||
const response = await this.fetchWithRetry<APIResponse<unknown>>(
|
||||
`${this.apiEndpoint}/security/scan/${scanId}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: this.getHeaders(),
|
||||
}
|
||||
)
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Utility
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
cancelAllRequests(): void {
|
||||
this.abortControllers.forEach(controller => controller.abort())
|
||||
this.abortControllers.clear()
|
||||
}
|
||||
|
||||
setTenantId(tenantId: string): void {
|
||||
this.tenantId = tenantId
|
||||
}
|
||||
|
||||
getTenantId(): string {
|
||||
return this.tenantId
|
||||
}
|
||||
|
||||
async healthCheck(): Promise<boolean> {
|
||||
try {
|
||||
const response = await this.fetchWithTimeout(
|
||||
`${this.apiEndpoint}/health`,
|
||||
{ method: 'GET' },
|
||||
`health-${Date.now()}`
|
||||
)
|
||||
return response.ok
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FACTORY
|
||||
// =============================================================================
|
||||
|
||||
let clientInstance: ComplianceClient | null = null
|
||||
|
||||
export function getComplianceClient(options?: ComplianceClientOptions): ComplianceClient {
|
||||
if (!clientInstance && !options) {
|
||||
throw new Error('ComplianceClient not initialized. Provide options on first call.')
|
||||
}
|
||||
|
||||
if (!clientInstance && options) {
|
||||
clientInstance = new ComplianceClient(options)
|
||||
}
|
||||
|
||||
return clientInstance!
|
||||
}
|
||||
|
||||
export function resetComplianceClient(): void {
|
||||
if (clientInstance) {
|
||||
clientInstance.cancelAllRequests()
|
||||
}
|
||||
clientInstance = null
|
||||
}
|
||||
41
breakpilot-compliance-sdk/packages/core/src/index.ts
Normal file
41
breakpilot-compliance-sdk/packages/core/src/index.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @breakpilot/compliance-sdk-core
|
||||
*
|
||||
* Core functionality for BreakPilot Compliance SDK
|
||||
*/
|
||||
|
||||
// Client
|
||||
export { ComplianceClient, type ComplianceClientOptions } from './client'
|
||||
|
||||
// State Management
|
||||
export {
|
||||
createStore,
|
||||
sdkReducer,
|
||||
initialState,
|
||||
type SDKStore,
|
||||
type SDKStoreOptions,
|
||||
} from './state'
|
||||
|
||||
// Sync
|
||||
export {
|
||||
StateSyncManager,
|
||||
createStateSyncManager,
|
||||
type SyncOptions,
|
||||
type SyncCallbacks,
|
||||
} from './sync'
|
||||
|
||||
// Auth
|
||||
export {
|
||||
AuthProvider,
|
||||
type AuthProviderOptions,
|
||||
type AuthState,
|
||||
} from './auth'
|
||||
|
||||
// Modules
|
||||
export * from './modules/dsgvo'
|
||||
export * from './modules/compliance'
|
||||
export * from './modules/rag'
|
||||
export * from './modules/security'
|
||||
|
||||
// Utils
|
||||
export * from './utils'
|
||||
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* Compliance Module
|
||||
*
|
||||
* General compliance functionality including controls, evidence,
|
||||
* AI Act, NIS2, and regulatory obligations
|
||||
*/
|
||||
|
||||
import type {
|
||||
Control,
|
||||
Evidence,
|
||||
Requirement,
|
||||
Obligation,
|
||||
AIActResult,
|
||||
AIActRiskCategory,
|
||||
RegulationCode,
|
||||
ComplianceScore,
|
||||
SDKState,
|
||||
RiskSeverity,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
import { ComplianceClient } from '../client'
|
||||
|
||||
export class ComplianceModule {
|
||||
private client: ComplianceClient
|
||||
private getState: () => SDKState
|
||||
|
||||
constructor(client: ComplianceClient, getState: () => SDKState) {
|
||||
this.client = client
|
||||
this.getState = getState
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Controls
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getControls(): Control[] {
|
||||
return this.getState().controls
|
||||
}
|
||||
|
||||
getControlById(id: string): Control | undefined {
|
||||
return this.getState().controls.find(c => c.id === id)
|
||||
}
|
||||
|
||||
getControlsByDomain(domain: string): Control[] {
|
||||
return this.getState().controls.filter(c => c.domain === domain)
|
||||
}
|
||||
|
||||
getControlsByStatus(status: string): Control[] {
|
||||
return this.getState().controls.filter(c => c.implementationStatus === status)
|
||||
}
|
||||
|
||||
getControlComplianceRate(): number {
|
||||
const controls = this.getControls()
|
||||
if (controls.length === 0) return 0
|
||||
|
||||
const implemented = controls.filter(c => c.implementationStatus === 'IMPLEMENTED').length
|
||||
return Math.round((implemented / controls.length) * 100)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Evidence
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getEvidence(): Evidence[] {
|
||||
return this.getState().evidence
|
||||
}
|
||||
|
||||
getEvidenceById(id: string): Evidence | undefined {
|
||||
return this.getState().evidence.find(e => e.id === id)
|
||||
}
|
||||
|
||||
getEvidenceByControlId(controlId: string): Evidence[] {
|
||||
return this.getState().evidence.filter(e => e.controlId === controlId)
|
||||
}
|
||||
|
||||
getExpiringEvidence(days: number = 30): Evidence[] {
|
||||
const cutoff = new Date()
|
||||
cutoff.setDate(cutoff.getDate() + days)
|
||||
|
||||
return this.getState().evidence.filter(e => {
|
||||
if (!e.validUntil) return false
|
||||
const validUntil = new Date(e.validUntil)
|
||||
return validUntil <= cutoff && e.status === 'ACTIVE'
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Requirements
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getRequirements(): Requirement[] {
|
||||
return this.getState().requirements
|
||||
}
|
||||
|
||||
getRequirementsByRegulation(regulation: RegulationCode): Requirement[] {
|
||||
return this.getState().requirements.filter(r => r.regulationCode === regulation)
|
||||
}
|
||||
|
||||
getRequirementComplianceRate(regulation?: RegulationCode): number {
|
||||
let requirements = this.getRequirements()
|
||||
if (regulation) {
|
||||
requirements = requirements.filter(r => r.regulationCode === regulation)
|
||||
}
|
||||
if (requirements.length === 0) return 0
|
||||
|
||||
const implemented = requirements.filter(
|
||||
r => r.status === 'IMPLEMENTED' || r.status === 'VERIFIED'
|
||||
).length
|
||||
return Math.round((implemented / requirements.length) * 100)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Obligations
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getObligations(): Obligation[] {
|
||||
return this.getState().obligations
|
||||
}
|
||||
|
||||
getUpcomingObligations(days: number = 30): Obligation[] {
|
||||
const cutoff = new Date()
|
||||
cutoff.setDate(cutoff.getDate() + days)
|
||||
|
||||
return this.getState().obligations.filter(o => {
|
||||
if (!o.deadline || o.status === 'COMPLETED') return false
|
||||
const deadline = new Date(o.deadline)
|
||||
return deadline <= cutoff
|
||||
})
|
||||
}
|
||||
|
||||
getOverdueObligations(): Obligation[] {
|
||||
const now = new Date()
|
||||
|
||||
return this.getState().obligations.filter(o => {
|
||||
if (!o.deadline || o.status === 'COMPLETED') return false
|
||||
const deadline = new Date(o.deadline)
|
||||
return deadline < now
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AI Act
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getAIActClassification(): AIActResult | null {
|
||||
return this.getState().aiActClassification
|
||||
}
|
||||
|
||||
getAIActRiskCategory(): AIActRiskCategory | null {
|
||||
return this.getState().aiActClassification?.riskCategory ?? null
|
||||
}
|
||||
|
||||
isHighRiskAI(): boolean {
|
||||
const category = this.getAIActRiskCategory()
|
||||
return category === 'HIGH' || category === 'UNACCEPTABLE'
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compliance Score
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
calculateComplianceScore(): ComplianceScore {
|
||||
const state = this.getState()
|
||||
|
||||
// Calculate overall score based on controls, requirements, and evidence
|
||||
const controlScore = this.getControlComplianceRate()
|
||||
const requirementScore = this.getRequirementComplianceRate()
|
||||
const evidenceCoverage = this.calculateEvidenceCoverage()
|
||||
|
||||
const overall = Math.round((controlScore + requirementScore + evidenceCoverage) / 3)
|
||||
|
||||
// Calculate scores by regulation
|
||||
const byRegulation: Record<string, number> = {}
|
||||
const regulations = new Set(state.requirements.map(r => r.regulationCode))
|
||||
regulations.forEach(reg => {
|
||||
byRegulation[reg] = this.getRequirementComplianceRate(reg as RegulationCode)
|
||||
})
|
||||
|
||||
// Calculate scores by domain
|
||||
const byDomain: Record<string, number> = {}
|
||||
const domains = new Set(state.controls.map(c => c.domain))
|
||||
domains.forEach(domain => {
|
||||
const domainControls = state.controls.filter(c => c.domain === domain)
|
||||
const implemented = domainControls.filter(c => c.implementationStatus === 'IMPLEMENTED').length
|
||||
byDomain[domain] = domainControls.length > 0
|
||||
? Math.round((implemented / domainControls.length) * 100)
|
||||
: 0
|
||||
})
|
||||
|
||||
return {
|
||||
overall,
|
||||
byRegulation: byRegulation as Record<RegulationCode, number>,
|
||||
byDomain: byDomain as Record<string, number>,
|
||||
trend: 'STABLE', // Would need historical data to calculate
|
||||
lastCalculated: new Date(),
|
||||
}
|
||||
}
|
||||
|
||||
private calculateEvidenceCoverage(): number {
|
||||
const controls = this.getControls()
|
||||
const implementedControls = controls.filter(c => c.implementationStatus === 'IMPLEMENTED')
|
||||
|
||||
if (implementedControls.length === 0) return 0
|
||||
|
||||
const controlsWithEvidence = implementedControls.filter(c => {
|
||||
const evidence = this.getEvidenceByControlId(c.id)
|
||||
return evidence.some(e => e.status === 'ACTIVE')
|
||||
})
|
||||
|
||||
return Math.round((controlsWithEvidence.length / implementedControls.length) * 100)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Risks
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getRisks() {
|
||||
return this.getState().risks
|
||||
}
|
||||
|
||||
getRisksByStatus(status: string) {
|
||||
return this.getRisks().filter(r => r.status === status)
|
||||
}
|
||||
|
||||
getRisksBySeverity(severity: RiskSeverity) {
|
||||
return this.getRisks().filter(r => r.severity === severity)
|
||||
}
|
||||
|
||||
getCriticalRisks() {
|
||||
return this.getRisks().filter(r => r.severity === 'CRITICAL' || r.severity === 'HIGH')
|
||||
}
|
||||
|
||||
getAverageRiskScore(): number {
|
||||
const risks = this.getRisks()
|
||||
if (risks.length === 0) return 0
|
||||
|
||||
const totalScore = risks.reduce((sum, r) => sum + r.residualRiskScore, 0)
|
||||
return Math.round(totalScore / risks.length)
|
||||
}
|
||||
}
|
||||
|
||||
export function createComplianceModule(
|
||||
client: ComplianceClient,
|
||||
getState: () => SDKState
|
||||
): ComplianceModule {
|
||||
return new ComplianceModule(client, getState)
|
||||
}
|
||||
155
breakpilot-compliance-sdk/packages/core/src/modules/dsgvo.ts
Normal file
155
breakpilot-compliance-sdk/packages/core/src/modules/dsgvo.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* DSGVO Module
|
||||
*
|
||||
* GDPR compliance functionality
|
||||
*/
|
||||
|
||||
import type {
|
||||
DSRRequest,
|
||||
DSRRequestType,
|
||||
ConsentRecord,
|
||||
ConsentPurpose,
|
||||
ProcessingActivity,
|
||||
DSFA,
|
||||
TOM,
|
||||
RetentionPolicy,
|
||||
CookieBannerConfig,
|
||||
SDKState,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
import { ComplianceClient } from '../client'
|
||||
|
||||
export class DSGVOModule {
|
||||
private client: ComplianceClient
|
||||
private getState: () => SDKState
|
||||
|
||||
constructor(client: ComplianceClient, getState: () => SDKState) {
|
||||
this.client = client
|
||||
this.getState = getState
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DSR (Data Subject Requests)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async submitDSR(type: DSRRequestType, requesterEmail: string, requesterName: string): Promise<DSRRequest> {
|
||||
const response = await fetch(`${this.client.getTenantId()}/dsgvo/dsr`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type, requesterEmail, requesterName }),
|
||||
})
|
||||
return response.json()
|
||||
}
|
||||
|
||||
getDSRRequests(): DSRRequest[] {
|
||||
return this.getState().dsrRequests
|
||||
}
|
||||
|
||||
getDSRById(id: string): DSRRequest | undefined {
|
||||
return this.getState().dsrRequests.find(r => r.id === id)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Consent Management
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getConsents(): ConsentRecord[] {
|
||||
return this.getState().consents
|
||||
}
|
||||
|
||||
getConsentsByUserId(userId: string): ConsentRecord[] {
|
||||
return this.getState().consents.filter(c => c.userId === userId)
|
||||
}
|
||||
|
||||
hasConsent(userId: string, purpose: ConsentPurpose): boolean {
|
||||
const consents = this.getConsentsByUserId(userId)
|
||||
const latestConsent = consents
|
||||
.filter(c => c.consentType === purpose)
|
||||
.sort((a, b) => new Date(b.grantedAt).getTime() - new Date(a.grantedAt).getTime())[0]
|
||||
|
||||
return latestConsent?.granted && !latestConsent.revokedAt
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// VVT (Processing Register)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getProcessingActivities(): ProcessingActivity[] {
|
||||
return this.getState().vvt
|
||||
}
|
||||
|
||||
getProcessingActivityById(id: string): ProcessingActivity | undefined {
|
||||
return this.getState().vvt.find(p => p.id === id)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DSFA
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getDSFA(): DSFA | null {
|
||||
return this.getState().dsfa
|
||||
}
|
||||
|
||||
isDSFARequired(): boolean {
|
||||
const state = this.getState()
|
||||
const activeUseCase = state.useCases.find(uc => uc.id === state.activeUseCase)
|
||||
return activeUseCase?.assessmentResult?.dsfaRequired ?? false
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TOMs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getTOMs(): TOM[] {
|
||||
return this.getState().toms
|
||||
}
|
||||
|
||||
getTOMsByCategory(category: string): TOM[] {
|
||||
return this.getState().toms.filter(t => t.category === category)
|
||||
}
|
||||
|
||||
getTOMScore(): number {
|
||||
const toms = this.getTOMs()
|
||||
if (toms.length === 0) return 0
|
||||
|
||||
const implemented = toms.filter(t => t.implementationStatus === 'IMPLEMENTED').length
|
||||
return Math.round((implemented / toms.length) * 100)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Retention Policies
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getRetentionPolicies(): RetentionPolicy[] {
|
||||
return this.getState().retentionPolicies
|
||||
}
|
||||
|
||||
getUpcomingDeletions(days: number = 30): RetentionPolicy[] {
|
||||
const cutoff = new Date()
|
||||
cutoff.setDate(cutoff.getDate() + days)
|
||||
|
||||
return this.getState().retentionPolicies.filter(p => {
|
||||
const nextReview = new Date(p.nextReviewDate)
|
||||
return nextReview <= cutoff
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cookie Banner
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getCookieBannerConfig(): CookieBannerConfig | null {
|
||||
return this.getState().cookieBanner
|
||||
}
|
||||
|
||||
async generateCookieBannerCode(): Promise<{ html: string; css: string; js: string } | null> {
|
||||
const config = this.getCookieBannerConfig()
|
||||
if (!config) return null
|
||||
|
||||
const response = await this.client.generateDocument('cookie_banner', { config })
|
||||
return response.content ? JSON.parse(response.content) : null
|
||||
}
|
||||
}
|
||||
|
||||
export function createDSGVOModule(client: ComplianceClient, getState: () => SDKState): DSGVOModule {
|
||||
return new DSGVOModule(client, getState)
|
||||
}
|
||||
206
breakpilot-compliance-sdk/packages/core/src/modules/rag.ts
Normal file
206
breakpilot-compliance-sdk/packages/core/src/modules/rag.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* RAG Module
|
||||
*
|
||||
* Legal RAG system for semantic search and AI-powered legal assistance
|
||||
*/
|
||||
|
||||
import type {
|
||||
SearchQuery,
|
||||
SearchResponse,
|
||||
AssistantQuery,
|
||||
AssistantResponse,
|
||||
LegalDocument,
|
||||
ChatSession,
|
||||
ChatMessage,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
import { ComplianceClient } from '../client'
|
||||
|
||||
export class RAGModule {
|
||||
private client: ComplianceClient
|
||||
private chatHistory: ChatMessage[] = []
|
||||
private sessionId: string | null = null
|
||||
|
||||
constructor(client: ComplianceClient) {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Search
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async search(query: string, options?: Partial<SearchQuery>): Promise<SearchResponse> {
|
||||
const searchRequest: SearchQuery = {
|
||||
query,
|
||||
limit: options?.limit ?? 10,
|
||||
offset: options?.offset ?? 0,
|
||||
filters: options?.filters,
|
||||
includeMetadata: options?.includeMetadata ?? true,
|
||||
scoreThreshold: options?.scoreThreshold ?? 0.5,
|
||||
}
|
||||
|
||||
return this.client.searchRAG({
|
||||
query: searchRequest.query,
|
||||
filters: searchRequest.filters,
|
||||
limit: searchRequest.limit,
|
||||
offset: searchRequest.offset,
|
||||
})
|
||||
}
|
||||
|
||||
async searchByRegulation(regulation: string, query: string): Promise<SearchResponse> {
|
||||
return this.search(query, {
|
||||
filters: {
|
||||
documentCodes: [regulation],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async searchByArticle(regulation: string, article: string): Promise<SearchResponse> {
|
||||
return this.search(`${regulation} Artikel ${article}`, {
|
||||
filters: {
|
||||
documentCodes: [regulation],
|
||||
articles: [article],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Legal Assistant
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async ask(question: string, options?: Partial<AssistantQuery>): Promise<AssistantResponse> {
|
||||
const request: AssistantQuery = {
|
||||
question,
|
||||
context: options?.context,
|
||||
documents: options?.documents,
|
||||
maxSources: options?.maxSources ?? 5,
|
||||
language: options?.language ?? 'de',
|
||||
responseFormat: options?.responseFormat ?? 'detailed',
|
||||
}
|
||||
|
||||
const response = await this.client.askRAG({
|
||||
question: request.question,
|
||||
context: request.context,
|
||||
documents: request.documents,
|
||||
maxSources: request.maxSources,
|
||||
language: request.language,
|
||||
})
|
||||
|
||||
// Add to chat history
|
||||
this.chatHistory.push({
|
||||
id: `user-${Date.now()}`,
|
||||
role: 'user',
|
||||
content: question,
|
||||
timestamp: new Date(),
|
||||
})
|
||||
|
||||
this.chatHistory.push({
|
||||
id: `assistant-${Date.now()}`,
|
||||
role: 'assistant',
|
||||
content: response.answer,
|
||||
timestamp: new Date(),
|
||||
sources: response.sources,
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
async askAboutRegulation(regulation: string, question: string): Promise<AssistantResponse> {
|
||||
return this.ask(question, {
|
||||
documents: [regulation],
|
||||
context: `Kontext: Frage bezieht sich auf ${regulation}`,
|
||||
})
|
||||
}
|
||||
|
||||
async explainArticle(regulation: string, article: string): Promise<AssistantResponse> {
|
||||
return this.ask(`Erkläre ${regulation} Artikel ${article} einfach und verständlich.`, {
|
||||
documents: [regulation],
|
||||
})
|
||||
}
|
||||
|
||||
async checkCompliance(
|
||||
regulation: string,
|
||||
scenario: string
|
||||
): Promise<AssistantResponse> {
|
||||
return this.ask(
|
||||
`Prüfe folgendes Szenario auf Compliance mit ${regulation}: ${scenario}`,
|
||||
{
|
||||
documents: [regulation],
|
||||
responseFormat: 'detailed',
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Chat Session
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
startNewSession(): void {
|
||||
this.sessionId = `session-${Date.now()}`
|
||||
this.chatHistory = []
|
||||
}
|
||||
|
||||
getChatHistory(): ChatMessage[] {
|
||||
return [...this.chatHistory]
|
||||
}
|
||||
|
||||
clearChatHistory(): void {
|
||||
this.chatHistory = []
|
||||
}
|
||||
|
||||
getSessionId(): string | null {
|
||||
return this.sessionId
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Document Info
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getAvailableRegulations(): readonly string[] {
|
||||
return [
|
||||
'DSGVO',
|
||||
'AI_ACT',
|
||||
'NIS2',
|
||||
'EPRIVACY',
|
||||
'TDDDG',
|
||||
'SCC',
|
||||
'DPF',
|
||||
'CRA',
|
||||
'EUCSA',
|
||||
'DATA_ACT',
|
||||
'DGA',
|
||||
'DSA',
|
||||
'EAA',
|
||||
'BDSG',
|
||||
'ISO_27001',
|
||||
'BSI_GRUNDSCHUTZ',
|
||||
'KRITIS',
|
||||
'BAIT',
|
||||
'VAIT',
|
||||
'SOC2',
|
||||
'PCI_DSS',
|
||||
] as const
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Quick Actions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getQuickAnswer(question: string): Promise<string> {
|
||||
const response = await this.ask(question, {
|
||||
responseFormat: 'concise',
|
||||
maxSources: 3,
|
||||
})
|
||||
return response.answer
|
||||
}
|
||||
|
||||
async findRelevantArticles(topic: string): Promise<SearchResponse> {
|
||||
return this.search(topic, {
|
||||
limit: 5,
|
||||
scoreThreshold: 0.7,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function createRAGModule(client: ComplianceClient): RAGModule {
|
||||
return new RAGModule(client)
|
||||
}
|
||||
241
breakpilot-compliance-sdk/packages/core/src/modules/security.ts
Normal file
241
breakpilot-compliance-sdk/packages/core/src/modules/security.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* Security Module
|
||||
*
|
||||
* Security scanning and SBOM management
|
||||
*/
|
||||
|
||||
import type {
|
||||
SBOM,
|
||||
SecurityIssue,
|
||||
SecurityScanResult,
|
||||
BacklogItem,
|
||||
SecurityIssueSeverity,
|
||||
SecurityTool,
|
||||
FindingsSummary,
|
||||
SDKState,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
import { ComplianceClient } from '../client'
|
||||
|
||||
export class SecurityModule {
|
||||
private client: ComplianceClient
|
||||
private getState: () => SDKState
|
||||
|
||||
constructor(client: ComplianceClient, getState: () => SDKState) {
|
||||
this.client = client
|
||||
this.getState = getState
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Security Scanning
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async startScan(options?: {
|
||||
tools?: SecurityTool[]
|
||||
targetPath?: string
|
||||
severityThreshold?: SecurityIssueSeverity
|
||||
generateSBOM?: boolean
|
||||
}): Promise<{ id: string; status: string }> {
|
||||
return this.client.startSecurityScan({
|
||||
tools: options?.tools,
|
||||
targetPath: options?.targetPath,
|
||||
severityThreshold: options?.severityThreshold,
|
||||
generateSBOM: options?.generateSBOM ?? true,
|
||||
})
|
||||
}
|
||||
|
||||
async getScanResult(scanId: string): Promise<SecurityScanResult | null> {
|
||||
const result = await this.client.getSecurityScanResult(scanId)
|
||||
return result as SecurityScanResult | null
|
||||
}
|
||||
|
||||
getLastScanResult(): SecurityScanResult | null {
|
||||
const screening = this.getState().screening
|
||||
return screening?.securityScan ?? null
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SBOM
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getSBOM(): SBOM | null {
|
||||
return this.getState().sbom
|
||||
}
|
||||
|
||||
getComponents() {
|
||||
return this.getSBOM()?.components ?? []
|
||||
}
|
||||
|
||||
getComponentsByLicense(license: string) {
|
||||
return this.getComponents().filter(c => c.licenses.includes(license as never))
|
||||
}
|
||||
|
||||
getVulnerableComponents() {
|
||||
return this.getComponents().filter(c => c.vulnerabilities.length > 0)
|
||||
}
|
||||
|
||||
getLicenseSummary(): Record<string, number> {
|
||||
const components = this.getComponents()
|
||||
const summary: Record<string, number> = {}
|
||||
|
||||
components.forEach(c => {
|
||||
c.licenses.forEach(license => {
|
||||
summary[license] = (summary[license] || 0) + 1
|
||||
})
|
||||
})
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Security Issues
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getSecurityIssues(): SecurityIssue[] {
|
||||
return this.getState().securityIssues
|
||||
}
|
||||
|
||||
getIssueById(id: string): SecurityIssue | undefined {
|
||||
return this.getSecurityIssues().find(i => i.id === id)
|
||||
}
|
||||
|
||||
getIssuesBySeverity(severity: SecurityIssueSeverity): SecurityIssue[] {
|
||||
return this.getSecurityIssues().filter(i => i.severity === severity)
|
||||
}
|
||||
|
||||
getIssuesByStatus(status: string): SecurityIssue[] {
|
||||
return this.getSecurityIssues().filter(i => i.status === status)
|
||||
}
|
||||
|
||||
getIssuesByTool(tool: SecurityTool): SecurityIssue[] {
|
||||
return this.getSecurityIssues().filter(i => i.tool === tool)
|
||||
}
|
||||
|
||||
getOpenIssues(): SecurityIssue[] {
|
||||
return this.getSecurityIssues().filter(i => i.status === 'OPEN' || i.status === 'IN_PROGRESS')
|
||||
}
|
||||
|
||||
getCriticalIssues(): SecurityIssue[] {
|
||||
return this.getSecurityIssues().filter(
|
||||
i => (i.severity === 'CRITICAL' || i.severity === 'HIGH') && i.status === 'OPEN'
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Backlog
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getBacklog(): BacklogItem[] {
|
||||
return this.getState().securityBacklog
|
||||
}
|
||||
|
||||
getBacklogByStatus(status: 'OPEN' | 'IN_PROGRESS' | 'DONE'): BacklogItem[] {
|
||||
return this.getBacklog().filter(i => i.status === status)
|
||||
}
|
||||
|
||||
getOverdueBacklogItems(): BacklogItem[] {
|
||||
const now = new Date()
|
||||
return this.getBacklog().filter(i => {
|
||||
if (!i.dueDate || i.status === 'DONE') return false
|
||||
return new Date(i.dueDate) < now
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Summary & Statistics
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getSecuritySummary(): FindingsSummary {
|
||||
const issues = this.getSecurityIssues()
|
||||
|
||||
const bySeverity: Record<SecurityIssueSeverity, number> = {
|
||||
CRITICAL: 0,
|
||||
HIGH: 0,
|
||||
MEDIUM: 0,
|
||||
LOW: 0,
|
||||
INFO: 0,
|
||||
}
|
||||
|
||||
const byStatus: Record<string, number> = {
|
||||
OPEN: 0,
|
||||
IN_PROGRESS: 0,
|
||||
RESOLVED: 0,
|
||||
ACCEPTED: 0,
|
||||
FALSE_POSITIVE: 0,
|
||||
}
|
||||
|
||||
const byTool: Record<string, number> = {}
|
||||
|
||||
issues.forEach(issue => {
|
||||
bySeverity[issue.severity]++
|
||||
byStatus[issue.status]++
|
||||
byTool[issue.tool] = (byTool[issue.tool] || 0) + 1
|
||||
})
|
||||
|
||||
// Calculate average resolution time for resolved issues
|
||||
const resolvedIssues = issues.filter(i => i.status === 'RESOLVED' && i.resolvedAt)
|
||||
let averageResolutionDays = 0
|
||||
if (resolvedIssues.length > 0) {
|
||||
const totalDays = resolvedIssues.reduce((sum, issue) => {
|
||||
// Would need createdAt field to calculate properly
|
||||
return sum + 7 // Placeholder
|
||||
}, 0)
|
||||
averageResolutionDays = Math.round(totalDays / resolvedIssues.length)
|
||||
}
|
||||
|
||||
// Find oldest unresolved issue
|
||||
const openIssues = issues.filter(i => i.status === 'OPEN')
|
||||
let oldestUnresolvedDays = 0
|
||||
// Would need createdAt field to calculate properly
|
||||
|
||||
return {
|
||||
totalFindings: issues.length,
|
||||
bySeverity,
|
||||
byStatus: byStatus as Record<string, number>,
|
||||
byTool: byTool as Record<SecurityTool, number>,
|
||||
averageResolutionDays,
|
||||
oldestUnresolvedDays,
|
||||
}
|
||||
}
|
||||
|
||||
getSecurityScore(): number {
|
||||
const issues = this.getSecurityIssues()
|
||||
const openIssues = issues.filter(i => i.status === 'OPEN' || i.status === 'IN_PROGRESS')
|
||||
|
||||
if (issues.length === 0) return 100
|
||||
|
||||
// Weight by severity
|
||||
const severityWeights = {
|
||||
CRITICAL: 10,
|
||||
HIGH: 5,
|
||||
MEDIUM: 2,
|
||||
LOW: 1,
|
||||
INFO: 0,
|
||||
}
|
||||
|
||||
const totalWeight = issues.reduce((sum, i) => sum + severityWeights[i.severity], 0)
|
||||
const openWeight = openIssues.reduce((sum, i) => sum + severityWeights[i.severity], 0)
|
||||
|
||||
if (totalWeight === 0) return 100
|
||||
|
||||
return Math.round(((totalWeight - openWeight) / totalWeight) * 100)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Utilities
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getAvailableTools(): SecurityTool[] {
|
||||
return ['gitleaks', 'semgrep', 'bandit', 'trivy', 'grype', 'syft']
|
||||
}
|
||||
|
||||
async exportSBOM(format: 'CycloneDX' | 'SPDX'): Promise<Blob> {
|
||||
return this.client.exportState(format === 'CycloneDX' ? 'json' : 'json')
|
||||
}
|
||||
}
|
||||
|
||||
export function createSecurityModule(
|
||||
client: ComplianceClient,
|
||||
getState: () => SDKState
|
||||
): SecurityModule {
|
||||
return new SecurityModule(client, getState)
|
||||
}
|
||||
414
breakpilot-compliance-sdk/packages/core/src/state.ts
Normal file
414
breakpilot-compliance-sdk/packages/core/src/state.ts
Normal file
@@ -0,0 +1,414 @@
|
||||
/**
|
||||
* SDK State Management
|
||||
*
|
||||
* Reducer-based state management with persistence support
|
||||
*/
|
||||
|
||||
import type {
|
||||
SDKState,
|
||||
SDKAction,
|
||||
UserPreferences,
|
||||
SDKPhase,
|
||||
SubscriptionTier,
|
||||
getStepById,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
|
||||
// =============================================================================
|
||||
// INITIAL STATE
|
||||
// =============================================================================
|
||||
|
||||
const initialPreferences: UserPreferences = {
|
||||
language: 'de',
|
||||
theme: 'light',
|
||||
compactMode: false,
|
||||
showHints: true,
|
||||
autoSave: true,
|
||||
autoValidate: true,
|
||||
}
|
||||
|
||||
export const initialState: SDKState = {
|
||||
// Metadata
|
||||
version: '1.0.0',
|
||||
lastModified: new Date(),
|
||||
|
||||
// Tenant & User
|
||||
tenantId: '',
|
||||
userId: '',
|
||||
subscription: 'PROFESSIONAL' as SubscriptionTier,
|
||||
|
||||
// Progress
|
||||
currentPhase: 1 as SDKPhase,
|
||||
currentStep: 'use-case-workshop',
|
||||
completedSteps: [],
|
||||
checkpoints: {},
|
||||
|
||||
// Phase 1 Data
|
||||
useCases: [],
|
||||
activeUseCase: null,
|
||||
screening: null,
|
||||
modules: [],
|
||||
requirements: [],
|
||||
controls: [],
|
||||
evidence: [],
|
||||
checklist: [],
|
||||
risks: [],
|
||||
|
||||
// Phase 2 Data
|
||||
aiActClassification: null,
|
||||
obligations: [],
|
||||
dsfa: null,
|
||||
toms: [],
|
||||
retentionPolicies: [],
|
||||
vvt: [],
|
||||
documents: [],
|
||||
cookieBanner: null,
|
||||
consents: [],
|
||||
dsrConfig: null,
|
||||
dsrRequests: [],
|
||||
escalationWorkflows: [],
|
||||
|
||||
// Security
|
||||
sbom: null,
|
||||
securityIssues: [],
|
||||
securityBacklog: [],
|
||||
|
||||
// UI State
|
||||
commandBarHistory: [],
|
||||
recentSearches: [],
|
||||
preferences: initialPreferences,
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// REDUCER
|
||||
// =============================================================================
|
||||
|
||||
export function sdkReducer(state: SDKState, action: SDKAction): SDKState {
|
||||
const updateState = (updates: Partial<SDKState>): SDKState => ({
|
||||
...state,
|
||||
...updates,
|
||||
lastModified: new Date(),
|
||||
})
|
||||
|
||||
switch (action.type) {
|
||||
case 'SET_STATE':
|
||||
return updateState(action.payload)
|
||||
|
||||
case 'SET_CURRENT_STEP': {
|
||||
// Import dynamically to avoid circular dependencies
|
||||
const { getStepById } = require('@breakpilot/compliance-sdk-types')
|
||||
const step = getStepById(action.payload)
|
||||
return updateState({
|
||||
currentStep: action.payload,
|
||||
currentPhase: step?.phase || state.currentPhase,
|
||||
})
|
||||
}
|
||||
|
||||
case 'COMPLETE_STEP':
|
||||
if (state.completedSteps.includes(action.payload)) {
|
||||
return state
|
||||
}
|
||||
return updateState({
|
||||
completedSteps: [...state.completedSteps, action.payload],
|
||||
})
|
||||
|
||||
case 'SET_CHECKPOINT_STATUS':
|
||||
return updateState({
|
||||
checkpoints: {
|
||||
...state.checkpoints,
|
||||
[action.payload.id]: action.payload.status,
|
||||
},
|
||||
})
|
||||
|
||||
// Use Cases
|
||||
case 'ADD_USE_CASE':
|
||||
return updateState({
|
||||
useCases: [...state.useCases, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_USE_CASE':
|
||||
return updateState({
|
||||
useCases: state.useCases.map(uc =>
|
||||
uc.id === action.payload.id ? { ...uc, ...action.payload.data } : uc
|
||||
),
|
||||
})
|
||||
|
||||
case 'DELETE_USE_CASE':
|
||||
return updateState({
|
||||
useCases: state.useCases.filter(uc => uc.id !== action.payload),
|
||||
activeUseCase: state.activeUseCase === action.payload ? null : state.activeUseCase,
|
||||
})
|
||||
|
||||
case 'SET_ACTIVE_USE_CASE':
|
||||
return updateState({ activeUseCase: action.payload })
|
||||
|
||||
// Screening
|
||||
case 'SET_SCREENING':
|
||||
return updateState({ screening: action.payload })
|
||||
|
||||
// Modules
|
||||
case 'ADD_MODULE':
|
||||
return updateState({
|
||||
modules: [...state.modules, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_MODULE':
|
||||
return updateState({
|
||||
modules: state.modules.map(m =>
|
||||
m.id === action.payload.id ? { ...m, ...action.payload.data } : m
|
||||
),
|
||||
})
|
||||
|
||||
// Requirements
|
||||
case 'ADD_REQUIREMENT':
|
||||
return updateState({
|
||||
requirements: [...state.requirements, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_REQUIREMENT':
|
||||
return updateState({
|
||||
requirements: state.requirements.map(r =>
|
||||
r.id === action.payload.id ? { ...r, ...action.payload.data } : r
|
||||
),
|
||||
})
|
||||
|
||||
// Controls
|
||||
case 'ADD_CONTROL':
|
||||
return updateState({
|
||||
controls: [...state.controls, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_CONTROL':
|
||||
return updateState({
|
||||
controls: state.controls.map(c =>
|
||||
c.id === action.payload.id ? { ...c, ...action.payload.data } : c
|
||||
),
|
||||
})
|
||||
|
||||
// Evidence
|
||||
case 'ADD_EVIDENCE':
|
||||
return updateState({
|
||||
evidence: [...state.evidence, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_EVIDENCE':
|
||||
return updateState({
|
||||
evidence: state.evidence.map(e =>
|
||||
e.id === action.payload.id ? { ...e, ...action.payload.data } : e
|
||||
),
|
||||
})
|
||||
|
||||
case 'DELETE_EVIDENCE':
|
||||
return updateState({
|
||||
evidence: state.evidence.filter(e => e.id !== action.payload),
|
||||
})
|
||||
|
||||
// Risks
|
||||
case 'ADD_RISK':
|
||||
return updateState({
|
||||
risks: [...state.risks, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_RISK':
|
||||
return updateState({
|
||||
risks: state.risks.map(r =>
|
||||
r.id === action.payload.id ? { ...r, ...action.payload.data } : r
|
||||
),
|
||||
})
|
||||
|
||||
case 'DELETE_RISK':
|
||||
return updateState({
|
||||
risks: state.risks.filter(r => r.id !== action.payload),
|
||||
})
|
||||
|
||||
// AI Act
|
||||
case 'SET_AI_ACT_RESULT':
|
||||
return updateState({ aiActClassification: action.payload })
|
||||
|
||||
// Obligations
|
||||
case 'ADD_OBLIGATION':
|
||||
return updateState({
|
||||
obligations: [...state.obligations, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_OBLIGATION':
|
||||
return updateState({
|
||||
obligations: state.obligations.map(o =>
|
||||
o.id === action.payload.id ? { ...o, ...action.payload.data } : o
|
||||
),
|
||||
})
|
||||
|
||||
// DSFA
|
||||
case 'SET_DSFA':
|
||||
return updateState({ dsfa: action.payload })
|
||||
|
||||
// TOMs
|
||||
case 'ADD_TOM':
|
||||
return updateState({
|
||||
toms: [...state.toms, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_TOM':
|
||||
return updateState({
|
||||
toms: state.toms.map(t =>
|
||||
t.id === action.payload.id ? { ...t, ...action.payload.data } : t
|
||||
),
|
||||
})
|
||||
|
||||
// Retention Policies
|
||||
case 'ADD_RETENTION_POLICY':
|
||||
return updateState({
|
||||
retentionPolicies: [...state.retentionPolicies, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_RETENTION_POLICY':
|
||||
return updateState({
|
||||
retentionPolicies: state.retentionPolicies.map(p =>
|
||||
p.id === action.payload.id ? { ...p, ...action.payload.data } : p
|
||||
),
|
||||
})
|
||||
|
||||
// Processing Activities (VVT)
|
||||
case 'ADD_PROCESSING_ACTIVITY':
|
||||
return updateState({
|
||||
vvt: [...state.vvt, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_PROCESSING_ACTIVITY':
|
||||
return updateState({
|
||||
vvt: state.vvt.map(p =>
|
||||
p.id === action.payload.id ? { ...p, ...action.payload.data } : p
|
||||
),
|
||||
})
|
||||
|
||||
// Documents
|
||||
case 'ADD_DOCUMENT':
|
||||
return updateState({
|
||||
documents: [...state.documents, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_DOCUMENT':
|
||||
return updateState({
|
||||
documents: state.documents.map(d =>
|
||||
d.id === action.payload.id ? { ...d, ...action.payload.data } : d
|
||||
),
|
||||
})
|
||||
|
||||
// Cookie Banner
|
||||
case 'SET_COOKIE_BANNER':
|
||||
return updateState({ cookieBanner: action.payload })
|
||||
|
||||
// DSR
|
||||
case 'SET_DSR_CONFIG':
|
||||
return updateState({ dsrConfig: action.payload })
|
||||
|
||||
case 'ADD_DSR_REQUEST':
|
||||
return updateState({
|
||||
dsrRequests: [...state.dsrRequests, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_DSR_REQUEST':
|
||||
return updateState({
|
||||
dsrRequests: state.dsrRequests.map(r =>
|
||||
r.id === action.payload.id ? { ...r, ...action.payload.data } : r
|
||||
),
|
||||
})
|
||||
|
||||
// Escalation Workflows
|
||||
case 'ADD_ESCALATION_WORKFLOW':
|
||||
return updateState({
|
||||
escalationWorkflows: [...state.escalationWorkflows, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_ESCALATION_WORKFLOW':
|
||||
return updateState({
|
||||
escalationWorkflows: state.escalationWorkflows.map(w =>
|
||||
w.id === action.payload.id ? { ...w, ...action.payload.data } : w
|
||||
),
|
||||
})
|
||||
|
||||
// Security
|
||||
case 'ADD_SECURITY_ISSUE':
|
||||
return updateState({
|
||||
securityIssues: [...state.securityIssues, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_SECURITY_ISSUE':
|
||||
return updateState({
|
||||
securityIssues: state.securityIssues.map(i =>
|
||||
i.id === action.payload.id ? { ...i, ...action.payload.data } : i
|
||||
),
|
||||
})
|
||||
|
||||
case 'ADD_BACKLOG_ITEM':
|
||||
return updateState({
|
||||
securityBacklog: [...state.securityBacklog, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_BACKLOG_ITEM':
|
||||
return updateState({
|
||||
securityBacklog: state.securityBacklog.map(i =>
|
||||
i.id === action.payload.id ? { ...i, ...action.payload.data } : i
|
||||
),
|
||||
})
|
||||
|
||||
// UI State
|
||||
case 'ADD_COMMAND_HISTORY':
|
||||
return updateState({
|
||||
commandBarHistory: [action.payload, ...state.commandBarHistory].slice(0, 50),
|
||||
})
|
||||
|
||||
case 'SET_PREFERENCES':
|
||||
return updateState({
|
||||
preferences: { ...state.preferences, ...action.payload },
|
||||
})
|
||||
|
||||
case 'RESET_STATE':
|
||||
return { ...initialState, lastModified: new Date() }
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STORE
|
||||
// =============================================================================
|
||||
|
||||
export interface SDKStoreOptions {
|
||||
tenantId: string
|
||||
userId: string
|
||||
initialState?: Partial<SDKState>
|
||||
onChange?: (state: SDKState) => void
|
||||
}
|
||||
|
||||
export interface SDKStore {
|
||||
getState: () => SDKState
|
||||
dispatch: (action: SDKAction) => void
|
||||
subscribe: (listener: (state: SDKState) => void) => () => void
|
||||
}
|
||||
|
||||
export function createStore(options: SDKStoreOptions): SDKStore {
|
||||
let state: SDKState = {
|
||||
...initialState,
|
||||
tenantId: options.tenantId,
|
||||
userId: options.userId,
|
||||
...options.initialState,
|
||||
}
|
||||
|
||||
const listeners = new Set<(state: SDKState) => void>()
|
||||
|
||||
const getState = () => state
|
||||
|
||||
const dispatch = (action: SDKAction) => {
|
||||
state = sdkReducer(state, action)
|
||||
listeners.forEach(listener => listener(state))
|
||||
options.onChange?.(state)
|
||||
}
|
||||
|
||||
const subscribe = (listener: (state: SDKState) => void) => {
|
||||
listeners.add(listener)
|
||||
return () => listeners.delete(listener)
|
||||
}
|
||||
|
||||
return { getState, dispatch, subscribe }
|
||||
}
|
||||
435
breakpilot-compliance-sdk/packages/core/src/sync.ts
Normal file
435
breakpilot-compliance-sdk/packages/core/src/sync.ts
Normal file
@@ -0,0 +1,435 @@
|
||||
/**
|
||||
* SDK State Synchronization
|
||||
*
|
||||
* Handles offline/online sync, multi-tab coordination,
|
||||
* and conflict resolution for SDK state.
|
||||
*/
|
||||
|
||||
import type { SDKState, SyncState, SyncStatus, ConflictResolution } from '@breakpilot/compliance-sdk-types'
|
||||
import { ComplianceClient } from './client'
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface SyncOptions {
|
||||
debounceMs?: number
|
||||
maxRetries?: number
|
||||
conflictHandler?: (local: SDKState, server: SDKState) => Promise<ConflictResolution>
|
||||
}
|
||||
|
||||
export interface SyncCallbacks {
|
||||
onSyncStart?: () => void
|
||||
onSyncComplete?: (state: SDKState) => void
|
||||
onSyncError?: (error: Error) => void
|
||||
onConflict?: (local: SDKState, server: SDKState) => void
|
||||
onOffline?: () => void
|
||||
onOnline?: () => void
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONSTANTS
|
||||
// =============================================================================
|
||||
|
||||
const STORAGE_KEY_PREFIX = 'breakpilot-compliance-sdk-state'
|
||||
const SYNC_CHANNEL = 'breakpilot-sdk-state-sync'
|
||||
const DEFAULT_DEBOUNCE_MS = 2000
|
||||
const DEFAULT_MAX_RETRIES = 3
|
||||
|
||||
// =============================================================================
|
||||
// STATE SYNC MANAGER
|
||||
// =============================================================================
|
||||
|
||||
export class StateSyncManager {
|
||||
private client: ComplianceClient
|
||||
private tenantId: string
|
||||
private options: Required<SyncOptions>
|
||||
private callbacks: SyncCallbacks
|
||||
private syncState: SyncState
|
||||
private broadcastChannel: BroadcastChannel | null = null
|
||||
private debounceTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
private pendingState: SDKState | null = null
|
||||
private isOnline = true
|
||||
|
||||
constructor(
|
||||
client: ComplianceClient,
|
||||
tenantId: string,
|
||||
options: SyncOptions = {},
|
||||
callbacks: SyncCallbacks = {}
|
||||
) {
|
||||
this.client = client
|
||||
this.tenantId = tenantId
|
||||
this.callbacks = callbacks
|
||||
this.options = {
|
||||
debounceMs: options.debounceMs ?? DEFAULT_DEBOUNCE_MS,
|
||||
maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
|
||||
conflictHandler: options.conflictHandler ?? this.defaultConflictHandler.bind(this),
|
||||
}
|
||||
|
||||
this.syncState = {
|
||||
status: 'idle' as SyncStatus,
|
||||
lastSyncedAt: null,
|
||||
localVersion: 0,
|
||||
serverVersion: 0,
|
||||
pendingChanges: 0,
|
||||
error: null,
|
||||
}
|
||||
|
||||
this.setupBroadcastChannel()
|
||||
this.setupOnlineListener()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Setup Methods
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private setupBroadcastChannel(): void {
|
||||
if (typeof window === 'undefined' || !('BroadcastChannel' in window)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
this.broadcastChannel = new BroadcastChannel(`${SYNC_CHANNEL}-${this.tenantId}`)
|
||||
this.broadcastChannel.onmessage = this.handleBroadcastMessage.bind(this)
|
||||
} catch (error) {
|
||||
console.warn('BroadcastChannel not available:', error)
|
||||
}
|
||||
}
|
||||
|
||||
private setupOnlineListener(): void {
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
window.addEventListener('online', () => {
|
||||
this.isOnline = true
|
||||
this.syncState.status = 'idle'
|
||||
this.callbacks.onOnline?.()
|
||||
if (this.pendingState) {
|
||||
this.syncToServer(this.pendingState)
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('offline', () => {
|
||||
this.isOnline = false
|
||||
this.syncState.status = 'offline'
|
||||
this.callbacks.onOffline?.()
|
||||
})
|
||||
|
||||
this.isOnline = navigator.onLine
|
||||
if (!this.isOnline) {
|
||||
this.syncState.status = 'offline'
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Broadcast Channel Methods
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private handleBroadcastMessage(event: MessageEvent): void {
|
||||
const { type, state, version } = event.data
|
||||
|
||||
switch (type) {
|
||||
case 'STATE_UPDATED':
|
||||
if (version > this.syncState.localVersion) {
|
||||
this.syncState.localVersion = version
|
||||
this.saveToLocalStorage(state)
|
||||
this.callbacks.onSyncComplete?.(state)
|
||||
}
|
||||
break
|
||||
|
||||
case 'SYNC_COMPLETE':
|
||||
this.syncState.serverVersion = version
|
||||
break
|
||||
|
||||
case 'REQUEST_STATE':
|
||||
this.broadcastState()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private broadcastState(): void {
|
||||
if (!this.broadcastChannel) return
|
||||
|
||||
const state = this.loadFromLocalStorage()
|
||||
if (state) {
|
||||
this.broadcastChannel.postMessage({
|
||||
type: 'STATE_UPDATED',
|
||||
state,
|
||||
version: this.syncState.localVersion,
|
||||
tabId: this.getTabId(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private broadcastSyncComplete(version: number): void {
|
||||
if (!this.broadcastChannel) return
|
||||
|
||||
this.broadcastChannel.postMessage({
|
||||
type: 'SYNC_COMPLETE',
|
||||
version,
|
||||
tabId: this.getTabId(),
|
||||
})
|
||||
}
|
||||
|
||||
private getTabId(): string {
|
||||
if (typeof window === 'undefined') return 'server'
|
||||
|
||||
let tabId = sessionStorage.getItem('breakpilot-sdk-tab-id')
|
||||
if (!tabId) {
|
||||
tabId = `tab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||
sessionStorage.setItem('breakpilot-sdk-tab-id', tabId)
|
||||
}
|
||||
return tabId
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Local Storage Methods
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private getStorageKey(): string {
|
||||
return `${STORAGE_KEY_PREFIX}-${this.tenantId}`
|
||||
}
|
||||
|
||||
saveToLocalStorage(state: SDKState): void {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
try {
|
||||
const data = {
|
||||
state,
|
||||
version: this.syncState.localVersion,
|
||||
savedAt: new Date().toISOString(),
|
||||
}
|
||||
localStorage.setItem(this.getStorageKey(), JSON.stringify(data))
|
||||
} catch (error) {
|
||||
console.error('Failed to save to localStorage:', error)
|
||||
}
|
||||
}
|
||||
|
||||
loadFromLocalStorage(): SDKState | null {
|
||||
if (typeof window === 'undefined') return null
|
||||
|
||||
try {
|
||||
const stored = localStorage.getItem(this.getStorageKey())
|
||||
if (stored) {
|
||||
const data = JSON.parse(stored)
|
||||
this.syncState.localVersion = data.version || 0
|
||||
return data.state
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load from localStorage:', error)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
clearLocalStorage(): void {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
try {
|
||||
localStorage.removeItem(this.getStorageKey())
|
||||
} catch (error) {
|
||||
console.error('Failed to clear localStorage:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sync Methods
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
queueSync(state: SDKState): void {
|
||||
this.pendingState = state
|
||||
this.syncState.pendingChanges++
|
||||
|
||||
this.syncState.localVersion++
|
||||
this.saveToLocalStorage(state)
|
||||
this.broadcastState()
|
||||
|
||||
if (this.debounceTimeout) {
|
||||
clearTimeout(this.debounceTimeout)
|
||||
}
|
||||
|
||||
this.debounceTimeout = setTimeout(() => {
|
||||
this.syncToServer(state)
|
||||
}, this.options.debounceMs)
|
||||
}
|
||||
|
||||
async forceSync(state: SDKState): Promise<void> {
|
||||
if (this.debounceTimeout) {
|
||||
clearTimeout(this.debounceTimeout)
|
||||
this.debounceTimeout = null
|
||||
}
|
||||
|
||||
await this.syncToServer(state)
|
||||
}
|
||||
|
||||
private async syncToServer(state: SDKState): Promise<void> {
|
||||
if (!this.isOnline) {
|
||||
this.syncState.status = 'offline'
|
||||
return
|
||||
}
|
||||
|
||||
this.syncState.status = 'syncing'
|
||||
this.callbacks.onSyncStart?.()
|
||||
|
||||
try {
|
||||
const response = await this.client.saveState(state, this.syncState.serverVersion)
|
||||
|
||||
this.syncState = {
|
||||
...this.syncState,
|
||||
status: 'idle',
|
||||
lastSyncedAt: new Date(),
|
||||
serverVersion: response.version,
|
||||
pendingChanges: 0,
|
||||
error: null,
|
||||
}
|
||||
|
||||
this.pendingState = null
|
||||
this.broadcastSyncComplete(response.version)
|
||||
this.callbacks.onSyncComplete?.(state)
|
||||
} catch (error) {
|
||||
if ((error as { status?: number }).status === 409) {
|
||||
await this.handleConflict(state)
|
||||
} else {
|
||||
this.syncState.status = 'error'
|
||||
this.syncState.error = (error as Error).message
|
||||
this.callbacks.onSyncError?.(error as Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async loadFromServer(): Promise<SDKState | null> {
|
||||
if (!this.isOnline) {
|
||||
return this.loadFromLocalStorage()
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.client.getState()
|
||||
|
||||
if (response) {
|
||||
this.syncState.serverVersion = response.version
|
||||
this.syncState.localVersion = response.version
|
||||
this.saveToLocalStorage(response.state)
|
||||
return response.state
|
||||
}
|
||||
|
||||
return this.loadFromLocalStorage()
|
||||
} catch (error) {
|
||||
console.error('Failed to load from server:', error)
|
||||
return this.loadFromLocalStorage()
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Conflict Resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private async handleConflict(localState: SDKState): Promise<void> {
|
||||
this.syncState.status = 'conflict'
|
||||
|
||||
try {
|
||||
const serverResponse = await this.client.getState()
|
||||
|
||||
if (!serverResponse) {
|
||||
await this.client.saveState(localState)
|
||||
return
|
||||
}
|
||||
|
||||
const serverState = serverResponse.state
|
||||
this.callbacks.onConflict?.(localState, serverState)
|
||||
|
||||
const resolution = await this.options.conflictHandler(localState, serverState)
|
||||
|
||||
let resolvedState: SDKState
|
||||
switch (resolution.strategy) {
|
||||
case 'local':
|
||||
resolvedState = localState
|
||||
break
|
||||
case 'server':
|
||||
resolvedState = serverState
|
||||
break
|
||||
case 'merge':
|
||||
resolvedState = resolution.mergedState || localState
|
||||
break
|
||||
}
|
||||
|
||||
const response = await this.client.saveState(resolvedState)
|
||||
this.syncState.serverVersion = response.version
|
||||
this.syncState.localVersion = response.version
|
||||
this.saveToLocalStorage(resolvedState)
|
||||
this.syncState.status = 'idle'
|
||||
this.callbacks.onSyncComplete?.(resolvedState)
|
||||
} catch (error) {
|
||||
this.syncState.status = 'error'
|
||||
this.syncState.error = (error as Error).message
|
||||
this.callbacks.onSyncError?.(error as Error)
|
||||
}
|
||||
}
|
||||
|
||||
private async defaultConflictHandler(
|
||||
local: SDKState,
|
||||
server: SDKState
|
||||
): Promise<ConflictResolution> {
|
||||
const localTime = new Date(local.lastModified).getTime()
|
||||
const serverTime = new Date(server.lastModified).getTime()
|
||||
|
||||
if (localTime > serverTime) {
|
||||
return { strategy: 'local' }
|
||||
}
|
||||
|
||||
const mergedState: SDKState = {
|
||||
...server,
|
||||
preferences: local.preferences,
|
||||
commandBarHistory: [
|
||||
...local.commandBarHistory,
|
||||
...server.commandBarHistory.filter(
|
||||
h => !local.commandBarHistory.some(lh => lh.id === h.id)
|
||||
),
|
||||
].slice(0, 50),
|
||||
recentSearches: [...new Set([...local.recentSearches, ...server.recentSearches])].slice(
|
||||
0,
|
||||
20
|
||||
),
|
||||
}
|
||||
|
||||
return { strategy: 'merge', mergedState }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Getters & Cleanup
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getSyncState(): SyncState {
|
||||
return { ...this.syncState }
|
||||
}
|
||||
|
||||
isOnlineStatus(): boolean {
|
||||
return this.isOnline
|
||||
}
|
||||
|
||||
hasPendingChanges(): boolean {
|
||||
return this.syncState.pendingChanges > 0 || this.pendingState !== null
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this.debounceTimeout) {
|
||||
clearTimeout(this.debounceTimeout)
|
||||
}
|
||||
|
||||
if (this.broadcastChannel) {
|
||||
this.broadcastChannel.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FACTORY
|
||||
// =============================================================================
|
||||
|
||||
export function createStateSyncManager(
|
||||
client: ComplianceClient,
|
||||
tenantId: string,
|
||||
options?: SyncOptions,
|
||||
callbacks?: SyncCallbacks
|
||||
): StateSyncManager {
|
||||
return new StateSyncManager(client, tenantId, options, callbacks)
|
||||
}
|
||||
262
breakpilot-compliance-sdk/packages/core/src/utils.ts
Normal file
262
breakpilot-compliance-sdk/packages/core/src/utils.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* Utility Functions
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// ID GENERATION
|
||||
// =============================================================================
|
||||
|
||||
export function generateId(prefix?: string): string {
|
||||
const timestamp = Date.now().toString(36)
|
||||
const random = Math.random().toString(36).substring(2, 9)
|
||||
return prefix ? `${prefix}-${timestamp}-${random}` : `${timestamp}-${random}`
|
||||
}
|
||||
|
||||
export function generateUUID(): string {
|
||||
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
||||
return crypto.randomUUID()
|
||||
}
|
||||
// Fallback for older environments
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
||||
const r = (Math.random() * 16) | 0
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DATE UTILITIES
|
||||
// =============================================================================
|
||||
|
||||
export function formatDate(date: Date | string, locale = 'de-DE'): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
return d.toLocaleDateString(locale, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
export function formatDateTime(date: Date | string, locale = 'de-DE'): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
return d.toLocaleString(locale, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
export function isDateExpired(date: Date | string): boolean {
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
return d < new Date()
|
||||
}
|
||||
|
||||
export function addDays(date: Date, days: number): Date {
|
||||
const result = new Date(date)
|
||||
result.setDate(result.getDate() + days)
|
||||
return result
|
||||
}
|
||||
|
||||
export function daysBetween(date1: Date, date2: Date): number {
|
||||
const oneDay = 24 * 60 * 60 * 1000
|
||||
return Math.round(Math.abs((date1.getTime() - date2.getTime()) / oneDay))
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STRING UTILITIES
|
||||
// =============================================================================
|
||||
|
||||
export function truncate(str: string, maxLength: number, suffix = '...'): string {
|
||||
if (str.length <= maxLength) return str
|
||||
return str.substring(0, maxLength - suffix.length) + suffix
|
||||
}
|
||||
|
||||
export function slugify(str: string): string {
|
||||
return str
|
||||
.toLowerCase()
|
||||
.replace(/[äöü]/g, c => ({ ä: 'ae', ö: 'oe', ü: 'ue' })[c] || c)
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
}
|
||||
|
||||
export function capitalize(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ARRAY UTILITIES
|
||||
// =============================================================================
|
||||
|
||||
export function groupBy<T, K extends string | number>(
|
||||
array: T[],
|
||||
keyFn: (item: T) => K
|
||||
): Record<K, T[]> {
|
||||
return array.reduce(
|
||||
(groups, item) => {
|
||||
const key = keyFn(item)
|
||||
if (!groups[key]) {
|
||||
groups[key] = []
|
||||
}
|
||||
groups[key].push(item)
|
||||
return groups
|
||||
},
|
||||
{} as Record<K, T[]>
|
||||
)
|
||||
}
|
||||
|
||||
export function uniqueBy<T>(array: T[], keyFn: (item: T) => unknown): T[] {
|
||||
const seen = new Set()
|
||||
return array.filter(item => {
|
||||
const key = keyFn(item)
|
||||
if (seen.has(key)) return false
|
||||
seen.add(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
export function sortBy<T>(array: T[], keyFn: (item: T) => number | string, desc = false): T[] {
|
||||
return [...array].sort((a, b) => {
|
||||
const aKey = keyFn(a)
|
||||
const bKey = keyFn(b)
|
||||
if (aKey < bKey) return desc ? 1 : -1
|
||||
if (aKey > bKey) return desc ? -1 : 1
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// OBJECT UTILITIES
|
||||
// =============================================================================
|
||||
|
||||
export function deepClone<T>(obj: T): T {
|
||||
if (obj === null || typeof obj !== 'object') return obj
|
||||
if (obj instanceof Date) return new Date(obj.getTime()) as T
|
||||
if (Array.isArray(obj)) return obj.map(item => deepClone(item)) as T
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj as object).map(([key, value]) => [key, deepClone(value)])
|
||||
) as T
|
||||
}
|
||||
|
||||
export function deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {
|
||||
const result = { ...target }
|
||||
for (const key in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
const sourceValue = source[key]
|
||||
const targetValue = target[key]
|
||||
if (
|
||||
typeof sourceValue === 'object' &&
|
||||
sourceValue !== null &&
|
||||
!Array.isArray(sourceValue) &&
|
||||
typeof targetValue === 'object' &&
|
||||
targetValue !== null &&
|
||||
!Array.isArray(targetValue)
|
||||
) {
|
||||
result[key] = deepMerge(
|
||||
targetValue as Record<string, unknown>,
|
||||
sourceValue as Record<string, unknown>
|
||||
) as T[Extract<keyof T, string>]
|
||||
} else {
|
||||
result[key] = sourceValue as T[Extract<keyof T, string>]
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
|
||||
return keys.reduce(
|
||||
(result, key) => {
|
||||
if (key in obj) {
|
||||
result[key] = obj[key]
|
||||
}
|
||||
return result
|
||||
},
|
||||
{} as Pick<T, K>
|
||||
)
|
||||
}
|
||||
|
||||
export function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
|
||||
const result = { ...obj }
|
||||
keys.forEach(key => delete result[key])
|
||||
return result
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// VALIDATION UTILITIES
|
||||
// =============================================================================
|
||||
|
||||
export function isValidEmail(email: string): boolean {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
return emailRegex.test(email)
|
||||
}
|
||||
|
||||
export function isValidUrl(url: string): boolean {
|
||||
try {
|
||||
new URL(url)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function isEmpty(value: unknown): boolean {
|
||||
if (value === null || value === undefined) return true
|
||||
if (typeof value === 'string') return value.trim().length === 0
|
||||
if (Array.isArray(value)) return value.length === 0
|
||||
if (typeof value === 'object') return Object.keys(value).length === 0
|
||||
return false
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ASYNC UTILITIES
|
||||
// =============================================================================
|
||||
|
||||
export function debounce<T extends (...args: unknown[]) => unknown>(
|
||||
fn: T,
|
||||
delay: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeoutId: ReturnType<typeof setTimeout>
|
||||
return (...args: Parameters<T>) => {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(() => fn(...args), delay)
|
||||
}
|
||||
}
|
||||
|
||||
export function throttle<T extends (...args: unknown[]) => unknown>(
|
||||
fn: T,
|
||||
limit: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let lastCall = 0
|
||||
return (...args: Parameters<T>) => {
|
||||
const now = Date.now()
|
||||
if (now - lastCall >= limit) {
|
||||
lastCall = now
|
||||
fn(...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
export async function retry<T>(
|
||||
fn: () => Promise<T>,
|
||||
maxRetries: number,
|
||||
delay: number
|
||||
): Promise<T> {
|
||||
let lastError: Error | null = null
|
||||
for (let i = 0; i <= maxRetries; i++) {
|
||||
try {
|
||||
return await fn()
|
||||
} catch (error) {
|
||||
lastError = error as Error
|
||||
if (i < maxRetries) {
|
||||
await sleep(delay * Math.pow(2, i))
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError
|
||||
}
|
||||
9
breakpilot-compliance-sdk/packages/core/tsconfig.json
Normal file
9
breakpilot-compliance-sdk/packages/core/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
11
breakpilot-compliance-sdk/packages/core/tsup.config.ts
Normal file
11
breakpilot-compliance-sdk/packages/core/tsup.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts', 'src/client.ts', 'src/state.ts', 'src/sync.ts'],
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
external: ['@breakpilot/compliance-sdk-types'],
|
||||
})
|
||||
55
breakpilot-compliance-sdk/packages/react/package.json
Normal file
55
breakpilot-compliance-sdk/packages/react/package.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@breakpilot/compliance-sdk-react",
|
||||
"version": "1.0.0",
|
||||
"description": "React components and hooks for BreakPilot Compliance SDK",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./hooks": {
|
||||
"import": "./dist/hooks.mjs",
|
||||
"require": "./dist/hooks.js",
|
||||
"types": "./dist/hooks.d.ts"
|
||||
},
|
||||
"./components": {
|
||||
"import": "./dist/components.mjs",
|
||||
"require": "./dist/components.js",
|
||||
"types": "./dist/components.d.ts"
|
||||
}
|
||||
},
|
||||
"files": ["dist"],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@breakpilot/compliance-sdk-core": "workspace:*",
|
||||
"@breakpilot/compliance-sdk-types": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react": "^14.1.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tsup": "^8.0.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vitest": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=17.0.0",
|
||||
"react-dom": ">=17.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
}
|
||||
}
|
||||
12
breakpilot-compliance-sdk/packages/react/src/components.ts
Normal file
12
breakpilot-compliance-sdk/packages/react/src/components.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* React Components
|
||||
*
|
||||
* Pre-built UI components for common compliance functionality
|
||||
*/
|
||||
|
||||
// Re-export all components
|
||||
export { ConsentBanner, type ConsentBannerProps } from './components/ConsentBanner'
|
||||
export { DSRPortal, type DSRPortalProps } from './components/DSRPortal'
|
||||
export { ComplianceDashboard, type ComplianceDashboardProps } from './components/ComplianceDashboard'
|
||||
export { ComplianceScore, type ComplianceScoreProps } from './components/ComplianceScore'
|
||||
export { RiskMatrix, type RiskMatrixProps } from './components/RiskMatrix'
|
||||
@@ -0,0 +1,264 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { useCompliance } from '../provider'
|
||||
import { ComplianceScore } from './ComplianceScore'
|
||||
import { RiskMatrix } from './RiskMatrix'
|
||||
|
||||
export interface ComplianceDashboardProps {
|
||||
showScore?: boolean
|
||||
showRisks?: boolean
|
||||
showProgress?: boolean
|
||||
showObligations?: boolean
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export function ComplianceDashboard({
|
||||
showScore = true,
|
||||
showRisks = true,
|
||||
showProgress = true,
|
||||
showObligations = true,
|
||||
className,
|
||||
style,
|
||||
}: ComplianceDashboardProps) {
|
||||
const { state, compliance, completionPercentage, phase1Completion, phase2Completion } =
|
||||
useCompliance()
|
||||
|
||||
const score = compliance.calculateComplianceScore()
|
||||
const criticalRisks = compliance.getCriticalRisks()
|
||||
const upcomingObligations = compliance.getUpcomingObligations()
|
||||
const overdueObligations = compliance.getOverdueObligations()
|
||||
|
||||
const containerStyle: React.CSSProperties = {
|
||||
fontFamily: 'system-ui, -apple-system, sans-serif',
|
||||
...style,
|
||||
}
|
||||
|
||||
const cardStyle: React.CSSProperties = {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '8px',
|
||||
padding: '20px',
|
||||
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)',
|
||||
marginBottom: '20px',
|
||||
}
|
||||
|
||||
const gridStyle: React.CSSProperties = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
|
||||
gap: '20px',
|
||||
marginBottom: '20px',
|
||||
}
|
||||
|
||||
const statStyle: React.CSSProperties = {
|
||||
...cardStyle,
|
||||
textAlign: 'center',
|
||||
}
|
||||
|
||||
const statValueStyle: React.CSSProperties = {
|
||||
fontSize: '36px',
|
||||
fontWeight: 700,
|
||||
margin: '10px 0',
|
||||
}
|
||||
|
||||
const statLabelStyle: React.CSSProperties = {
|
||||
fontSize: '14px',
|
||||
color: '#666',
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={containerStyle} className={className}>
|
||||
<h1 style={{ margin: '0 0 20px' }}>Compliance Dashboard</h1>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div style={gridStyle}>
|
||||
{showScore && (
|
||||
<div style={statStyle}>
|
||||
<div style={statLabelStyle}>Compliance Score</div>
|
||||
<div style={{ ...statValueStyle, color: score.overall >= 70 ? '#16a34a' : '#dc2626' }}>
|
||||
{score.overall}%
|
||||
</div>
|
||||
<div style={statLabelStyle}>
|
||||
Trend:{' '}
|
||||
{score.trend === 'UP' ? '↑ Steigend' : score.trend === 'DOWN' ? '↓ Fallend' : '→ Stabil'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showProgress && (
|
||||
<>
|
||||
<div style={statStyle}>
|
||||
<div style={statLabelStyle}>Gesamtfortschritt</div>
|
||||
<div style={statValueStyle}>{completionPercentage}%</div>
|
||||
<div style={statLabelStyle}>
|
||||
{state.completedSteps.length} von 19 Schritten abgeschlossen
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={statStyle}>
|
||||
<div style={statLabelStyle}>Phase 1: Assessment</div>
|
||||
<div style={statValueStyle}>{phase1Completion}%</div>
|
||||
<div style={{ height: '8px', backgroundColor: '#e5e5e5', borderRadius: '4px' }}>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: `${phase1Completion}%`,
|
||||
backgroundColor: '#3b82f6',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={statStyle}>
|
||||
<div style={statLabelStyle}>Phase 2: Dokumentation</div>
|
||||
<div style={statValueStyle}>{phase2Completion}%</div>
|
||||
<div style={{ height: '8px', backgroundColor: '#e5e5e5', borderRadius: '4px' }}>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: `${phase2Completion}%`,
|
||||
backgroundColor: '#8b5cf6',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Main Content Grid */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
|
||||
{/* Score Breakdown */}
|
||||
{showScore && (
|
||||
<div style={cardStyle}>
|
||||
<h3 style={{ margin: '0 0 15px' }}>Score nach Regulierung</h3>
|
||||
{Object.entries(score.byRegulation).map(([reg, value]) => (
|
||||
<div
|
||||
key={reg}
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '10px 0',
|
||||
borderBottom: '1px solid #eee',
|
||||
}}
|
||||
>
|
||||
<span>{reg}</span>
|
||||
<span style={{ fontWeight: 600, color: value >= 70 ? '#16a34a' : '#dc2626' }}>
|
||||
{value}%
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Obligations */}
|
||||
{showObligations && (
|
||||
<div style={cardStyle}>
|
||||
<h3 style={{ margin: '0 0 15px' }}>Anstehende Pflichten</h3>
|
||||
|
||||
{overdueObligations.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
padding: '10px',
|
||||
backgroundColor: '#fef2f2',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '10px',
|
||||
}}
|
||||
>
|
||||
<strong style={{ color: '#dc2626' }}>
|
||||
{overdueObligations.length} überfällige Pflichten!
|
||||
</strong>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{upcomingObligations.length === 0 && overdueObligations.length === 0 ? (
|
||||
<p style={{ color: '#666' }}>Keine anstehenden Pflichten in den nächsten 30 Tagen.</p>
|
||||
) : (
|
||||
<div>
|
||||
{[...overdueObligations, ...upcomingObligations].slice(0, 5).map(o => (
|
||||
<div
|
||||
key={o.id}
|
||||
style={{
|
||||
padding: '10px',
|
||||
borderBottom: '1px solid #eee',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontWeight: 500 }}>{o.title}</div>
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
{o.regulationCode} Art. {o.article}
|
||||
</div>
|
||||
{o.deadline && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
color: new Date(o.deadline) < new Date() ? '#dc2626' : '#666',
|
||||
}}
|
||||
>
|
||||
Frist: {new Date(o.deadline).toLocaleDateString('de-DE')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Risk Matrix */}
|
||||
{showRisks && state.risks.length > 0 && (
|
||||
<div style={cardStyle}>
|
||||
<h3 style={{ margin: '0 0 15px' }}>Risikomatrix</h3>
|
||||
<RiskMatrix risks={state.risks} />
|
||||
|
||||
{criticalRisks.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: '15px',
|
||||
padding: '10px',
|
||||
backgroundColor: '#fef2f2',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
>
|
||||
<strong style={{ color: '#dc2626' }}>
|
||||
{criticalRisks.length} kritische/hohe Risiken ohne Mitigation
|
||||
</strong>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Controls Summary */}
|
||||
<div style={cardStyle}>
|
||||
<h3 style={{ margin: '0 0 15px' }}>Controls Übersicht</h3>
|
||||
<div style={gridStyle}>
|
||||
<div>
|
||||
<div style={{ fontSize: '24px', fontWeight: 700 }}>{state.controls.length}</div>
|
||||
<div style={{ color: '#666' }}>Gesamt</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: '24px', fontWeight: 700, color: '#16a34a' }}>
|
||||
{state.controls.filter(c => c.implementationStatus === 'IMPLEMENTED').length}
|
||||
</div>
|
||||
<div style={{ color: '#666' }}>Implementiert</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: '24px', fontWeight: 700, color: '#f59e0b' }}>
|
||||
{state.controls.filter(c => c.implementationStatus === 'PARTIAL').length}
|
||||
</div>
|
||||
<div style={{ color: '#666' }}>Teilweise</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: '24px', fontWeight: 700, color: '#dc2626' }}>
|
||||
{state.controls.filter(c => c.implementationStatus === 'NOT_IMPLEMENTED').length}
|
||||
</div>
|
||||
<div style={{ color: '#666' }}>Offen</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
export interface ComplianceScoreProps {
|
||||
score: number
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
showLabel?: boolean
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export function ComplianceScore({
|
||||
score,
|
||||
size = 'medium',
|
||||
showLabel = true,
|
||||
className,
|
||||
style,
|
||||
}: ComplianceScoreProps) {
|
||||
const sizes = {
|
||||
small: { width: 80, strokeWidth: 6, fontSize: 18 },
|
||||
medium: { width: 120, strokeWidth: 8, fontSize: 24 },
|
||||
large: { width: 160, strokeWidth: 10, fontSize: 32 },
|
||||
}
|
||||
|
||||
const { width, strokeWidth, fontSize } = sizes[size]
|
||||
const radius = (width - strokeWidth) / 2
|
||||
const circumference = radius * 2 * Math.PI
|
||||
const offset = circumference - (score / 100) * circumference
|
||||
|
||||
const getColor = (score: number): string => {
|
||||
if (score >= 80) return '#16a34a' // Green
|
||||
if (score >= 60) return '#f59e0b' // Yellow
|
||||
if (score >= 40) return '#f97316' // Orange
|
||||
return '#dc2626' // Red
|
||||
}
|
||||
|
||||
const color = getColor(score)
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
...style,
|
||||
}}
|
||||
className={className}
|
||||
>
|
||||
<svg
|
||||
width={width}
|
||||
height={width}
|
||||
style={{ transform: 'rotate(-90deg)' }}
|
||||
>
|
||||
{/* Background circle */}
|
||||
<circle
|
||||
cx={width / 2}
|
||||
cy={width / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke="#e5e5e5"
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
{/* Progress circle */}
|
||||
<circle
|
||||
cx={width / 2}
|
||||
cy={width / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={offset}
|
||||
strokeLinecap="round"
|
||||
style={{
|
||||
transition: 'stroke-dashoffset 0.5s ease-in-out',
|
||||
}}
|
||||
/>
|
||||
{/* Score text */}
|
||||
<text
|
||||
x="50%"
|
||||
y="50%"
|
||||
dominantBaseline="middle"
|
||||
textAnchor="middle"
|
||||
style={{
|
||||
transform: 'rotate(90deg)',
|
||||
transformOrigin: 'center',
|
||||
fontSize: `${fontSize}px`,
|
||||
fontWeight: 700,
|
||||
fill: color,
|
||||
}}
|
||||
>
|
||||
{score}%
|
||||
</text>
|
||||
</svg>
|
||||
|
||||
{showLabel && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: '8px',
|
||||
fontSize: '14px',
|
||||
color: '#666',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
Compliance Score
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import { useCompliance } from '../provider'
|
||||
import type { ConsentPurpose, CookieBannerPosition, CookieBannerTheme } from '@breakpilot/compliance-sdk-types'
|
||||
|
||||
export interface ConsentBannerProps {
|
||||
position?: CookieBannerPosition
|
||||
theme?: CookieBannerTheme
|
||||
onConsentChange?: (consents: Record<ConsentPurpose, boolean>) => void
|
||||
privacyPolicyUrl?: string
|
||||
imprintUrl?: string
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export function ConsentBanner({
|
||||
position = 'BOTTOM',
|
||||
theme = 'LIGHT',
|
||||
onConsentChange,
|
||||
privacyPolicyUrl = '/privacy',
|
||||
imprintUrl = '/imprint',
|
||||
className,
|
||||
style,
|
||||
}: ConsentBannerProps) {
|
||||
const { state, dispatch } = useCompliance()
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [consents, setConsents] = useState<Record<ConsentPurpose, boolean>>({
|
||||
ESSENTIAL: true,
|
||||
FUNCTIONAL: false,
|
||||
ANALYTICS: false,
|
||||
MARKETING: false,
|
||||
PERSONALIZATION: false,
|
||||
THIRD_PARTY: false,
|
||||
})
|
||||
|
||||
const config = state.cookieBanner
|
||||
|
||||
const handleAcceptAll = useCallback(() => {
|
||||
const allConsents: Record<ConsentPurpose, boolean> = {
|
||||
ESSENTIAL: true,
|
||||
FUNCTIONAL: true,
|
||||
ANALYTICS: true,
|
||||
MARKETING: true,
|
||||
PERSONALIZATION: true,
|
||||
THIRD_PARTY: true,
|
||||
}
|
||||
setConsents(allConsents)
|
||||
onConsentChange?.(allConsents)
|
||||
// Would save to backend here
|
||||
}, [onConsentChange])
|
||||
|
||||
const handleRejectAll = useCallback(() => {
|
||||
const minimalConsents: Record<ConsentPurpose, boolean> = {
|
||||
ESSENTIAL: true,
|
||||
FUNCTIONAL: false,
|
||||
ANALYTICS: false,
|
||||
MARKETING: false,
|
||||
PERSONALIZATION: false,
|
||||
THIRD_PARTY: false,
|
||||
}
|
||||
setConsents(minimalConsents)
|
||||
onConsentChange?.(minimalConsents)
|
||||
}, [onConsentChange])
|
||||
|
||||
const handleSaveSettings = useCallback(() => {
|
||||
onConsentChange?.(consents)
|
||||
setShowSettings(false)
|
||||
}, [consents, onConsentChange])
|
||||
|
||||
const handleConsentToggle = useCallback((purpose: ConsentPurpose) => {
|
||||
if (purpose === 'ESSENTIAL') return // Cannot disable essential
|
||||
setConsents(prev => ({ ...prev, [purpose]: !prev[purpose] }))
|
||||
}, [])
|
||||
|
||||
const positionStyles: Record<CookieBannerPosition, React.CSSProperties> = {
|
||||
TOP: { top: 0, left: 0, right: 0 },
|
||||
BOTTOM: { bottom: 0, left: 0, right: 0 },
|
||||
CENTER: { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' },
|
||||
}
|
||||
|
||||
const themeStyles: Record<CookieBannerTheme, React.CSSProperties> = {
|
||||
LIGHT: { backgroundColor: '#ffffff', color: '#1a1a1a' },
|
||||
DARK: { backgroundColor: '#1a1a1a', color: '#ffffff' },
|
||||
CUSTOM: config?.customColors
|
||||
? {
|
||||
backgroundColor: config.customColors.background,
|
||||
color: config.customColors.text,
|
||||
}
|
||||
: {},
|
||||
}
|
||||
|
||||
const bannerStyle: React.CSSProperties = {
|
||||
position: 'fixed',
|
||||
zIndex: 9999,
|
||||
padding: '20px',
|
||||
boxShadow: '0 -2px 10px rgba(0, 0, 0, 0.1)',
|
||||
fontFamily: 'system-ui, -apple-system, sans-serif',
|
||||
...positionStyles[position],
|
||||
...themeStyles[theme],
|
||||
...style,
|
||||
}
|
||||
|
||||
const buttonBaseStyle: React.CSSProperties = {
|
||||
padding: '10px 20px',
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
fontWeight: 500,
|
||||
marginRight: '10px',
|
||||
}
|
||||
|
||||
const primaryButtonStyle: React.CSSProperties = {
|
||||
...buttonBaseStyle,
|
||||
backgroundColor: theme === 'DARK' ? '#ffffff' : '#1a1a1a',
|
||||
color: theme === 'DARK' ? '#1a1a1a' : '#ffffff',
|
||||
}
|
||||
|
||||
const secondaryButtonStyle: React.CSSProperties = {
|
||||
...buttonBaseStyle,
|
||||
backgroundColor: 'transparent',
|
||||
border: `1px solid ${theme === 'DARK' ? '#ffffff' : '#1a1a1a'}`,
|
||||
color: theme === 'DARK' ? '#ffffff' : '#1a1a1a',
|
||||
}
|
||||
|
||||
if (showSettings) {
|
||||
return (
|
||||
<div style={bannerStyle} className={className}>
|
||||
<h3 style={{ margin: '0 0 15px', fontSize: '18px' }}>
|
||||
{config?.texts?.settings || 'Cookie-Einstellungen'}
|
||||
</h3>
|
||||
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
{Object.entries({
|
||||
ESSENTIAL: { name: 'Notwendig', description: 'Erforderlich für die Grundfunktionen' },
|
||||
FUNCTIONAL: { name: 'Funktional', description: 'Verbesserte Funktionen' },
|
||||
ANALYTICS: { name: 'Analyse', description: 'Nutzungsstatistiken' },
|
||||
MARKETING: { name: 'Marketing', description: 'Personalisierte Werbung' },
|
||||
}).map(([key, { name, description }]) => (
|
||||
<div
|
||||
key={key}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '10px 0',
|
||||
borderBottom: `1px solid ${theme === 'DARK' ? '#333' : '#eee'}`,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{name}</div>
|
||||
<div style={{ fontSize: '12px', opacity: 0.7 }}>{description}</div>
|
||||
</div>
|
||||
<label style={{ cursor: key === 'ESSENTIAL' ? 'not-allowed' : 'pointer' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={consents[key as ConsentPurpose]}
|
||||
onChange={() => handleConsentToggle(key as ConsentPurpose)}
|
||||
disabled={key === 'ESSENTIAL'}
|
||||
style={{ width: '20px', height: '20px' }}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px' }}>
|
||||
<button style={primaryButtonStyle} onClick={handleSaveSettings}>
|
||||
{config?.texts?.save || 'Einstellungen speichern'}
|
||||
</button>
|
||||
<button style={secondaryButtonStyle} onClick={() => setShowSettings(false)}>
|
||||
Zurück
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={bannerStyle} className={className}>
|
||||
<div style={{ marginBottom: '15px' }}>
|
||||
<h3 style={{ margin: '0 0 10px', fontSize: '18px' }}>
|
||||
{config?.texts?.title || 'Cookie-Einwilligung'}
|
||||
</h3>
|
||||
<p style={{ margin: 0, fontSize: '14px', opacity: 0.8 }}>
|
||||
{config?.texts?.description ||
|
||||
'Wir verwenden Cookies, um Ihre Erfahrung zu verbessern. Weitere Informationen finden Sie in unserer Datenschutzerklärung.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', alignItems: 'center' }}>
|
||||
<button style={primaryButtonStyle} onClick={handleAcceptAll}>
|
||||
{config?.texts?.acceptAll || 'Alle akzeptieren'}
|
||||
</button>
|
||||
<button style={secondaryButtonStyle} onClick={handleRejectAll}>
|
||||
{config?.texts?.rejectAll || 'Nur notwendige'}
|
||||
</button>
|
||||
<button style={secondaryButtonStyle} onClick={() => setShowSettings(true)}>
|
||||
{config?.texts?.settings || 'Einstellungen'}
|
||||
</button>
|
||||
|
||||
<div style={{ marginLeft: 'auto', fontSize: '12px' }}>
|
||||
<a href={privacyPolicyUrl} style={{ marginRight: '15px', color: 'inherit' }}>
|
||||
Datenschutz
|
||||
</a>
|
||||
<a href={imprintUrl} style={{ color: 'inherit' }}>
|
||||
Impressum
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import { useCompliance } from '../provider'
|
||||
import type { DSRRequestType } from '@breakpilot/compliance-sdk-types'
|
||||
|
||||
export interface DSRPortalProps {
|
||||
onSubmit?: (type: DSRRequestType, email: string, name: string) => void
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
const DSR_TYPES: Record<DSRRequestType, { name: string; description: string }> = {
|
||||
ACCESS: {
|
||||
name: 'Auskunft (Art. 15)',
|
||||
description: 'Welche Daten haben Sie über mich gespeichert?',
|
||||
},
|
||||
RECTIFICATION: {
|
||||
name: 'Berichtigung (Art. 16)',
|
||||
description: 'Korrigieren Sie falsche Daten über mich.',
|
||||
},
|
||||
ERASURE: {
|
||||
name: 'Löschung (Art. 17)',
|
||||
description: 'Löschen Sie alle meine personenbezogenen Daten.',
|
||||
},
|
||||
PORTABILITY: {
|
||||
name: 'Datenübertragbarkeit (Art. 20)',
|
||||
description: 'Exportieren Sie meine Daten in einem maschinenlesbaren Format.',
|
||||
},
|
||||
RESTRICTION: {
|
||||
name: 'Einschränkung (Art. 18)',
|
||||
description: 'Schränken Sie die Verarbeitung meiner Daten ein.',
|
||||
},
|
||||
OBJECTION: {
|
||||
name: 'Widerspruch (Art. 21)',
|
||||
description: 'Ich widerspreche der Verarbeitung meiner Daten.',
|
||||
},
|
||||
}
|
||||
|
||||
export function DSRPortal({ onSubmit, className, style }: DSRPortalProps) {
|
||||
const { dsgvo } = useCompliance()
|
||||
const [selectedType, setSelectedType] = useState<DSRRequestType | null>(null)
|
||||
const [email, setEmail] = useState('')
|
||||
const [name, setName] = useState('')
|
||||
const [additionalInfo, setAdditionalInfo] = useState('')
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!selectedType || !email || !name) return
|
||||
|
||||
setIsSubmitting(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
await dsgvo.submitDSR(selectedType, email, name)
|
||||
onSubmit?.(selectedType, email, name)
|
||||
setSubmitted(true)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten')
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
},
|
||||
[selectedType, email, name, dsgvo, onSubmit]
|
||||
)
|
||||
|
||||
const containerStyle: React.CSSProperties = {
|
||||
fontFamily: 'system-ui, -apple-system, sans-serif',
|
||||
maxWidth: '600px',
|
||||
margin: '0 auto',
|
||||
padding: '20px',
|
||||
...style,
|
||||
}
|
||||
|
||||
const inputStyle: React.CSSProperties = {
|
||||
width: '100%',
|
||||
padding: '12px',
|
||||
fontSize: '14px',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '15px',
|
||||
boxSizing: 'border-box',
|
||||
}
|
||||
|
||||
const buttonStyle: React.CSSProperties = {
|
||||
padding: '12px 24px',
|
||||
fontSize: '16px',
|
||||
fontWeight: 500,
|
||||
color: '#fff',
|
||||
backgroundColor: '#1a1a1a',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
width: '100%',
|
||||
}
|
||||
|
||||
if (submitted) {
|
||||
return (
|
||||
<div style={containerStyle} className={className}>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
padding: '40px 20px',
|
||||
backgroundColor: '#f0fdf4',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '48px', marginBottom: '20px' }}>✓</div>
|
||||
<h2 style={{ margin: '0 0 10px', color: '#166534' }}>Anfrage eingereicht</h2>
|
||||
<p style={{ margin: 0, color: '#166534' }}>
|
||||
Wir werden Ihre Anfrage innerhalb von 30 Tagen bearbeiten. Sie erhalten eine
|
||||
Bestätigung per E-Mail an {email}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={containerStyle} className={className}>
|
||||
<h2 style={{ margin: '0 0 10px' }}>Betroffenenrechte-Portal</h2>
|
||||
<p style={{ margin: '0 0 20px', color: '#666' }}>
|
||||
Hier können Sie Ihre Rechte gemäß DSGVO wahrnehmen. Wählen Sie die gewünschte Anfrage und
|
||||
füllen Sie das Formular aus.
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<label style={{ display: 'block', fontWeight: 500, marginBottom: '10px' }}>
|
||||
Art der Anfrage *
|
||||
</label>
|
||||
<div style={{ display: 'grid', gap: '10px' }}>
|
||||
{Object.entries(DSR_TYPES).map(([type, { name, description }]) => (
|
||||
<label
|
||||
key={type}
|
||||
style={{
|
||||
display: 'flex',
|
||||
padding: '15px',
|
||||
border: `2px solid ${selectedType === type ? '#1a1a1a' : '#ddd'}`,
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: selectedType === type ? '#f5f5f5' : '#fff',
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="dsrType"
|
||||
value={type}
|
||||
checked={selectedType === type}
|
||||
onChange={() => setSelectedType(type as DSRRequestType)}
|
||||
style={{ marginRight: '15px' }}
|
||||
/>
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{name}</div>
|
||||
<div style={{ fontSize: '13px', color: '#666' }}>{description}</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '15px' }}>
|
||||
<label style={{ display: 'block', fontWeight: 500, marginBottom: '5px' }}>
|
||||
Ihr Name *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
required
|
||||
placeholder="Max Mustermann"
|
||||
style={inputStyle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '15px' }}>
|
||||
<label style={{ display: 'block', fontWeight: 500, marginBottom: '5px' }}>
|
||||
E-Mail-Adresse *
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
required
|
||||
placeholder="max@example.com"
|
||||
style={inputStyle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<label style={{ display: 'block', fontWeight: 500, marginBottom: '5px' }}>
|
||||
Zusätzliche Informationen (optional)
|
||||
</label>
|
||||
<textarea
|
||||
value={additionalInfo}
|
||||
onChange={e => setAdditionalInfo(e.target.value)}
|
||||
placeholder="Weitere Details zu Ihrer Anfrage..."
|
||||
rows={4}
|
||||
style={{ ...inputStyle, resize: 'vertical' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div
|
||||
style={{
|
||||
padding: '12px',
|
||||
backgroundColor: '#fef2f2',
|
||||
color: '#dc2626',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '15px',
|
||||
}}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!selectedType || !email || !name || isSubmitting}
|
||||
style={{
|
||||
...buttonStyle,
|
||||
opacity: !selectedType || !email || !name || isSubmitting ? 0.5 : 1,
|
||||
cursor: !selectedType || !email || !name || isSubmitting ? 'not-allowed' : 'pointer',
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? 'Wird gesendet...' : 'Anfrage einreichen'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p style={{ marginTop: '20px', fontSize: '12px', color: '#666' }}>
|
||||
Ihre Anfrage wird gemäß Art. 12 DSGVO innerhalb von einem Monat bearbeitet. In komplexen
|
||||
Fällen kann diese Frist um weitere zwei Monate verlängert werden.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import type { Risk, RiskLikelihood, RiskImpact } from '@breakpilot/compliance-sdk-types'
|
||||
|
||||
export interface RiskMatrixProps {
|
||||
risks: Risk[]
|
||||
onRiskClick?: (risk: Risk) => void
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export function RiskMatrix({ risks, onRiskClick, className, style }: RiskMatrixProps) {
|
||||
const getColor = (likelihood: number, impact: number): string => {
|
||||
const score = likelihood * impact
|
||||
if (score >= 20) return '#dc2626' // Critical - Red
|
||||
if (score >= 12) return '#f97316' // High - Orange
|
||||
if (score >= 6) return '#f59e0b' // Medium - Yellow
|
||||
return '#16a34a' // Low - Green
|
||||
}
|
||||
|
||||
const getCellRisks = (likelihood: RiskLikelihood, impact: RiskImpact): Risk[] => {
|
||||
return risks.filter(r => r.likelihood === likelihood && r.impact === impact)
|
||||
}
|
||||
|
||||
const containerStyle: React.CSSProperties = {
|
||||
fontFamily: 'system-ui, -apple-system, sans-serif',
|
||||
...style,
|
||||
}
|
||||
|
||||
const gridStyle: React.CSSProperties = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '60px repeat(5, 1fr)',
|
||||
gridTemplateRows: '30px repeat(5, 60px)',
|
||||
gap: '2px',
|
||||
backgroundColor: '#e5e5e5',
|
||||
padding: '2px',
|
||||
borderRadius: '8px',
|
||||
}
|
||||
|
||||
const cellStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#fff',
|
||||
position: 'relative',
|
||||
}
|
||||
|
||||
const headerStyle: React.CSSProperties = {
|
||||
...cellStyle,
|
||||
fontWeight: 600,
|
||||
fontSize: '12px',
|
||||
color: '#666',
|
||||
}
|
||||
|
||||
const likelihoodLabels = ['Sehr niedrig', 'Niedrig', 'Mittel', 'Hoch', 'Sehr hoch']
|
||||
const impactLabels = ['1', '2', '3', '4', '5']
|
||||
|
||||
return (
|
||||
<div style={containerStyle} className={className}>
|
||||
<div style={{ display: 'flex', marginBottom: '10px' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<span style={{ fontWeight: 600 }}>Risikomatrix</span>
|
||||
<span style={{ marginLeft: '15px', color: '#666', fontSize: '14px' }}>
|
||||
{risks.length} Risiken
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '10px', fontSize: '12px' }}>
|
||||
<span>
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
backgroundColor: '#16a34a',
|
||||
borderRadius: '2px',
|
||||
marginRight: '4px',
|
||||
}}
|
||||
/>
|
||||
Niedrig
|
||||
</span>
|
||||
<span>
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
backgroundColor: '#f59e0b',
|
||||
borderRadius: '2px',
|
||||
marginRight: '4px',
|
||||
}}
|
||||
/>
|
||||
Mittel
|
||||
</span>
|
||||
<span>
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
backgroundColor: '#f97316',
|
||||
borderRadius: '2px',
|
||||
marginRight: '4px',
|
||||
}}
|
||||
/>
|
||||
Hoch
|
||||
</span>
|
||||
<span>
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
backgroundColor: '#dc2626',
|
||||
borderRadius: '2px',
|
||||
marginRight: '4px',
|
||||
}}
|
||||
/>
|
||||
Kritisch
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={gridStyle}>
|
||||
{/* Header row */}
|
||||
<div style={headerStyle} />
|
||||
{impactLabels.map((label, i) => (
|
||||
<div key={`impact-${i}`} style={headerStyle}>
|
||||
Auswirkung {label}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Data rows (reversed so high likelihood is at top) */}
|
||||
{[5, 4, 3, 2, 1].map(likelihood => (
|
||||
<React.Fragment key={`row-${likelihood}`}>
|
||||
<div style={headerStyle}>{likelihoodLabels[likelihood - 1]}</div>
|
||||
{[1, 2, 3, 4, 5].map(impact => {
|
||||
const cellRisks = getCellRisks(likelihood as RiskLikelihood, impact as RiskImpact)
|
||||
return (
|
||||
<div
|
||||
key={`cell-${likelihood}-${impact}`}
|
||||
style={{
|
||||
...cellStyle,
|
||||
backgroundColor: getColor(likelihood, impact),
|
||||
opacity: cellRisks.length > 0 ? 1 : 0.3,
|
||||
cursor: cellRisks.length > 0 ? 'pointer' : 'default',
|
||||
}}
|
||||
onClick={() => {
|
||||
if (cellRisks.length > 0 && onRiskClick) {
|
||||
onRiskClick(cellRisks[0])
|
||||
}
|
||||
}}
|
||||
>
|
||||
{cellRisks.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 600,
|
||||
fontSize: '12px',
|
||||
color: '#1a1a1a',
|
||||
}}
|
||||
>
|
||||
{cellRisks.length}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: '10px',
|
||||
fontSize: '12px',
|
||||
color: '#666',
|
||||
}}
|
||||
>
|
||||
<span>Wahrscheinlichkeit →</span>
|
||||
<span>Auswirkung →</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
474
breakpilot-compliance-sdk/packages/react/src/hooks.ts
Normal file
474
breakpilot-compliance-sdk/packages/react/src/hooks.ts
Normal file
@@ -0,0 +1,474 @@
|
||||
'use client'
|
||||
|
||||
import { useContext, useMemo, useCallback } from 'react'
|
||||
import { ComplianceContext, type ComplianceContextValue } from './provider'
|
||||
import type {
|
||||
SDKState,
|
||||
SDKAction,
|
||||
Control,
|
||||
Evidence,
|
||||
Risk,
|
||||
Requirement,
|
||||
Obligation,
|
||||
TOM,
|
||||
ProcessingActivity,
|
||||
ConsentPurpose,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
|
||||
// =============================================================================
|
||||
// MAIN HOOK
|
||||
// =============================================================================
|
||||
|
||||
export function useCompliance(): ComplianceContextValue {
|
||||
const context = useContext(ComplianceContext)
|
||||
if (!context) {
|
||||
throw new Error('useCompliance must be used within ComplianceProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STATE HOOK
|
||||
// =============================================================================
|
||||
|
||||
export function useComplianceState(): SDKState {
|
||||
const { state } = useCompliance()
|
||||
return state
|
||||
}
|
||||
|
||||
export function useComplianceDispatch(): React.Dispatch<SDKAction> {
|
||||
const { dispatch } = useCompliance()
|
||||
return dispatch
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DSGVO HOOKS
|
||||
// =============================================================================
|
||||
|
||||
export function useDSGVO() {
|
||||
const { dsgvo, state } = useCompliance()
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
// DSR
|
||||
dsrRequests: state.dsrRequests,
|
||||
dsrConfig: state.dsrConfig,
|
||||
submitDSR: dsgvo.submitDSR.bind(dsgvo),
|
||||
|
||||
// Consent
|
||||
consents: state.consents,
|
||||
hasConsent: dsgvo.hasConsent.bind(dsgvo),
|
||||
getConsentsByUserId: dsgvo.getConsentsByUserId.bind(dsgvo),
|
||||
|
||||
// VVT
|
||||
processingActivities: state.vvt,
|
||||
getProcessingActivityById: dsgvo.getProcessingActivityById.bind(dsgvo),
|
||||
|
||||
// DSFA
|
||||
dsfa: state.dsfa,
|
||||
isDSFARequired: dsgvo.isDSFARequired.bind(dsgvo),
|
||||
|
||||
// TOMs
|
||||
toms: state.toms,
|
||||
getTOMsByCategory: dsgvo.getTOMsByCategory.bind(dsgvo),
|
||||
getTOMScore: dsgvo.getTOMScore.bind(dsgvo),
|
||||
|
||||
// Retention
|
||||
retentionPolicies: state.retentionPolicies,
|
||||
getUpcomingDeletions: dsgvo.getUpcomingDeletions.bind(dsgvo),
|
||||
|
||||
// Cookie Banner
|
||||
cookieBanner: state.cookieBanner,
|
||||
generateCookieBannerCode: dsgvo.generateCookieBannerCode.bind(dsgvo),
|
||||
}),
|
||||
[dsgvo, state]
|
||||
)
|
||||
}
|
||||
|
||||
export function useConsent(userId: string) {
|
||||
const { dsgvo, state } = useCompliance()
|
||||
|
||||
return useMemo(() => {
|
||||
const userConsents = state.consents.filter(c => c.userId === userId)
|
||||
|
||||
return {
|
||||
consents: userConsents,
|
||||
hasConsent: (purpose: ConsentPurpose) => dsgvo.hasConsent(userId, purpose),
|
||||
hasAnalyticsConsent: dsgvo.hasConsent(userId, 'ANALYTICS'),
|
||||
hasMarketingConsent: dsgvo.hasConsent(userId, 'MARKETING'),
|
||||
hasFunctionalConsent: dsgvo.hasConsent(userId, 'FUNCTIONAL'),
|
||||
}
|
||||
}, [dsgvo, state.consents, userId])
|
||||
}
|
||||
|
||||
export function useDSR() {
|
||||
const { dsgvo, state, dispatch } = useCompliance()
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
requests: state.dsrRequests,
|
||||
config: state.dsrConfig,
|
||||
submitRequest: dsgvo.submitDSR.bind(dsgvo),
|
||||
pendingRequests: state.dsrRequests.filter(r => r.status !== 'COMPLETED' && r.status !== 'REJECTED'),
|
||||
overdueRequests: state.dsrRequests.filter(r => {
|
||||
if (r.status === 'COMPLETED' || r.status === 'REJECTED') return false
|
||||
return new Date(r.dueDate) < new Date()
|
||||
}),
|
||||
}),
|
||||
[dsgvo, state.dsrRequests, state.dsrConfig]
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COMPLIANCE HOOKS
|
||||
// =============================================================================
|
||||
|
||||
export function useComplianceModule() {
|
||||
const { compliance, state } = useCompliance()
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
// Controls
|
||||
controls: state.controls,
|
||||
getControlById: compliance.getControlById.bind(compliance),
|
||||
getControlsByDomain: compliance.getControlsByDomain.bind(compliance),
|
||||
getControlsByStatus: compliance.getControlsByStatus.bind(compliance),
|
||||
controlComplianceRate: compliance.getControlComplianceRate(),
|
||||
|
||||
// Evidence
|
||||
evidence: state.evidence,
|
||||
getEvidenceByControlId: compliance.getEvidenceByControlId.bind(compliance),
|
||||
expiringEvidence: compliance.getExpiringEvidence(),
|
||||
|
||||
// Requirements
|
||||
requirements: state.requirements,
|
||||
getRequirementsByRegulation: compliance.getRequirementsByRegulation.bind(compliance),
|
||||
requirementComplianceRate: compliance.getRequirementComplianceRate(),
|
||||
|
||||
// Obligations
|
||||
obligations: state.obligations,
|
||||
upcomingObligations: compliance.getUpcomingObligations(),
|
||||
overdueObligations: compliance.getOverdueObligations(),
|
||||
|
||||
// AI Act
|
||||
aiActClassification: state.aiActClassification,
|
||||
aiActRiskCategory: compliance.getAIActRiskCategory(),
|
||||
isHighRiskAI: compliance.isHighRiskAI(),
|
||||
|
||||
// Score
|
||||
complianceScore: compliance.calculateComplianceScore(),
|
||||
|
||||
// Risks
|
||||
risks: state.risks,
|
||||
criticalRisks: compliance.getCriticalRisks(),
|
||||
averageRiskScore: compliance.getAverageRiskScore(),
|
||||
}),
|
||||
[compliance, state]
|
||||
)
|
||||
}
|
||||
|
||||
export function useControls() {
|
||||
const { compliance, state, dispatch } = useCompliance()
|
||||
|
||||
const addControl = useCallback(
|
||||
(control: Control) => {
|
||||
dispatch({ type: 'ADD_CONTROL', payload: control })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const updateControl = useCallback(
|
||||
(id: string, data: Partial<Control>) => {
|
||||
dispatch({ type: 'UPDATE_CONTROL', payload: { id, data } })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
controls: state.controls,
|
||||
addControl,
|
||||
updateControl,
|
||||
getById: compliance.getControlById.bind(compliance),
|
||||
getByDomain: compliance.getControlsByDomain.bind(compliance),
|
||||
getByStatus: compliance.getControlsByStatus.bind(compliance),
|
||||
implementedCount: state.controls.filter(c => c.implementationStatus === 'IMPLEMENTED').length,
|
||||
totalCount: state.controls.length,
|
||||
complianceRate: compliance.getControlComplianceRate(),
|
||||
}),
|
||||
[state.controls, compliance, addControl, updateControl]
|
||||
)
|
||||
}
|
||||
|
||||
export function useEvidence() {
|
||||
const { compliance, state, dispatch } = useCompliance()
|
||||
|
||||
const addEvidence = useCallback(
|
||||
(evidence: Evidence) => {
|
||||
dispatch({ type: 'ADD_EVIDENCE', payload: evidence })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const updateEvidence = useCallback(
|
||||
(id: string, data: Partial<Evidence>) => {
|
||||
dispatch({ type: 'UPDATE_EVIDENCE', payload: { id, data } })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const deleteEvidence = useCallback(
|
||||
(id: string) => {
|
||||
dispatch({ type: 'DELETE_EVIDENCE', payload: id })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
evidence: state.evidence,
|
||||
addEvidence,
|
||||
updateEvidence,
|
||||
deleteEvidence,
|
||||
getByControlId: compliance.getEvidenceByControlId.bind(compliance),
|
||||
expiringEvidence: compliance.getExpiringEvidence(),
|
||||
activeCount: state.evidence.filter(e => e.status === 'ACTIVE').length,
|
||||
totalCount: state.evidence.length,
|
||||
}),
|
||||
[state.evidence, compliance, addEvidence, updateEvidence, deleteEvidence]
|
||||
)
|
||||
}
|
||||
|
||||
export function useRisks() {
|
||||
const { compliance, state, dispatch } = useCompliance()
|
||||
|
||||
const addRisk = useCallback(
|
||||
(risk: Risk) => {
|
||||
dispatch({ type: 'ADD_RISK', payload: risk })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const updateRisk = useCallback(
|
||||
(id: string, data: Partial<Risk>) => {
|
||||
dispatch({ type: 'UPDATE_RISK', payload: { id, data } })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const deleteRisk = useCallback(
|
||||
(id: string) => {
|
||||
dispatch({ type: 'DELETE_RISK', payload: id })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
risks: state.risks,
|
||||
addRisk,
|
||||
updateRisk,
|
||||
deleteRisk,
|
||||
criticalRisks: compliance.getCriticalRisks(),
|
||||
getByStatus: compliance.getRisksByStatus.bind(compliance),
|
||||
getBySeverity: compliance.getRisksBySeverity.bind(compliance),
|
||||
averageScore: compliance.getAverageRiskScore(),
|
||||
}),
|
||||
[state.risks, compliance, addRisk, updateRisk, deleteRisk]
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RAG HOOKS
|
||||
// =============================================================================
|
||||
|
||||
export function useRAG() {
|
||||
const { rag } = useCompliance()
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
search: rag.search.bind(rag),
|
||||
searchByRegulation: rag.searchByRegulation.bind(rag),
|
||||
searchByArticle: rag.searchByArticle.bind(rag),
|
||||
ask: rag.ask.bind(rag),
|
||||
askAboutRegulation: rag.askAboutRegulation.bind(rag),
|
||||
explainArticle: rag.explainArticle.bind(rag),
|
||||
checkCompliance: rag.checkCompliance.bind(rag),
|
||||
getQuickAnswer: rag.getQuickAnswer.bind(rag),
|
||||
findRelevantArticles: rag.findRelevantArticles.bind(rag),
|
||||
availableRegulations: rag.getAvailableRegulations(),
|
||||
chatHistory: rag.getChatHistory(),
|
||||
clearChatHistory: rag.clearChatHistory.bind(rag),
|
||||
startNewSession: rag.startNewSession.bind(rag),
|
||||
}),
|
||||
[rag]
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SECURITY HOOKS
|
||||
// =============================================================================
|
||||
|
||||
export function useSecurity() {
|
||||
const { security, state } = useCompliance()
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
// SBOM
|
||||
sbom: state.sbom,
|
||||
components: security.getComponents(),
|
||||
vulnerableComponents: security.getVulnerableComponents(),
|
||||
licenseSummary: security.getLicenseSummary(),
|
||||
|
||||
// Issues
|
||||
issues: state.securityIssues,
|
||||
openIssues: security.getOpenIssues(),
|
||||
criticalIssues: security.getCriticalIssues(),
|
||||
getIssuesBySeverity: security.getIssuesBySeverity.bind(security),
|
||||
getIssuesByTool: security.getIssuesByTool.bind(security),
|
||||
|
||||
// Backlog
|
||||
backlog: state.securityBacklog,
|
||||
overdueBacklogItems: security.getOverdueBacklogItems(),
|
||||
|
||||
// Scanning
|
||||
startScan: security.startScan.bind(security),
|
||||
getScanResult: security.getScanResult.bind(security),
|
||||
lastScanResult: security.getLastScanResult(),
|
||||
|
||||
// Summary
|
||||
summary: security.getSecuritySummary(),
|
||||
securityScore: security.getSecurityScore(),
|
||||
availableTools: security.getAvailableTools(),
|
||||
}),
|
||||
[security, state]
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// NAVIGATION HOOKS
|
||||
// =============================================================================
|
||||
|
||||
export function useSDKNavigation() {
|
||||
const {
|
||||
currentStep,
|
||||
goToStep,
|
||||
goToNextStep,
|
||||
goToPreviousStep,
|
||||
canGoNext,
|
||||
canGoPrevious,
|
||||
completionPercentage,
|
||||
phase1Completion,
|
||||
phase2Completion,
|
||||
state,
|
||||
} = useCompliance()
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
currentStep,
|
||||
currentPhase: state.currentPhase,
|
||||
completedSteps: state.completedSteps,
|
||||
goToStep,
|
||||
goToNextStep,
|
||||
goToPreviousStep,
|
||||
canGoNext,
|
||||
canGoPrevious,
|
||||
completionPercentage,
|
||||
phase1Completion,
|
||||
phase2Completion,
|
||||
}),
|
||||
[
|
||||
currentStep,
|
||||
state.currentPhase,
|
||||
state.completedSteps,
|
||||
goToStep,
|
||||
goToNextStep,
|
||||
goToPreviousStep,
|
||||
canGoNext,
|
||||
canGoPrevious,
|
||||
completionPercentage,
|
||||
phase1Completion,
|
||||
phase2Completion,
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SYNC HOOKS
|
||||
// =============================================================================
|
||||
|
||||
export function useSync() {
|
||||
const { syncState, forceSyncToServer, isOnline, saveState, loadState } = useCompliance()
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
status: syncState.status,
|
||||
lastSyncedAt: syncState.lastSyncedAt,
|
||||
pendingChanges: syncState.pendingChanges,
|
||||
error: syncState.error,
|
||||
isOnline,
|
||||
isSyncing: syncState.status === 'syncing',
|
||||
hasConflict: syncState.status === 'conflict',
|
||||
forceSyncToServer,
|
||||
saveState,
|
||||
loadState,
|
||||
}),
|
||||
[syncState, isOnline, forceSyncToServer, saveState, loadState]
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CHECKPOINT HOOKS
|
||||
// =============================================================================
|
||||
|
||||
export function useCheckpoints() {
|
||||
const { validateCheckpoint, overrideCheckpoint, getCheckpointStatus, state } = useCompliance()
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
checkpoints: state.checkpoints,
|
||||
validateCheckpoint,
|
||||
overrideCheckpoint,
|
||||
getCheckpointStatus,
|
||||
passedCheckpoints: Object.values(state.checkpoints).filter(c => c.passed).length,
|
||||
totalCheckpoints: Object.keys(state.checkpoints).length,
|
||||
}),
|
||||
[state.checkpoints, validateCheckpoint, overrideCheckpoint, getCheckpointStatus]
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EXPORT HOOKS
|
||||
// =============================================================================
|
||||
|
||||
export function useExport() {
|
||||
const { exportState } = useCompliance()
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
exportJSON: () => exportState('json'),
|
||||
exportPDF: () => exportState('pdf'),
|
||||
exportZIP: () => exportState('zip'),
|
||||
exportState,
|
||||
}),
|
||||
[exportState]
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COMMAND BAR HOOK
|
||||
// =============================================================================
|
||||
|
||||
export function useCommandBar() {
|
||||
const { isCommandBarOpen, setCommandBarOpen } = useCompliance()
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
isOpen: isCommandBarOpen,
|
||||
open: () => setCommandBarOpen(true),
|
||||
close: () => setCommandBarOpen(false),
|
||||
toggle: () => setCommandBarOpen(!isCommandBarOpen),
|
||||
}),
|
||||
[isCommandBarOpen, setCommandBarOpen]
|
||||
)
|
||||
}
|
||||
14
breakpilot-compliance-sdk/packages/react/src/index.ts
Normal file
14
breakpilot-compliance-sdk/packages/react/src/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @breakpilot/compliance-sdk-react
|
||||
*
|
||||
* React components and hooks for BreakPilot Compliance SDK
|
||||
*/
|
||||
|
||||
// Provider
|
||||
export { ComplianceProvider, ComplianceContext, type ComplianceProviderProps } from './provider'
|
||||
|
||||
// Hooks
|
||||
export * from './hooks'
|
||||
|
||||
// Components
|
||||
export * from './components'
|
||||
539
breakpilot-compliance-sdk/packages/react/src/provider.tsx
Normal file
539
breakpilot-compliance-sdk/packages/react/src/provider.tsx
Normal file
@@ -0,0 +1,539 @@
|
||||
'use client'
|
||||
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useReducer,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
ComplianceClient,
|
||||
sdkReducer,
|
||||
initialState,
|
||||
StateSyncManager,
|
||||
createStateSyncManager,
|
||||
createDSGVOModule,
|
||||
createComplianceModule,
|
||||
createRAGModule,
|
||||
createSecurityModule,
|
||||
type DSGVOModule,
|
||||
type ComplianceModule,
|
||||
type RAGModule,
|
||||
type SecurityModule,
|
||||
} from '@breakpilot/compliance-sdk-core'
|
||||
import type {
|
||||
SDKState,
|
||||
SDKAction,
|
||||
SDKStep,
|
||||
CheckpointStatus,
|
||||
SyncState,
|
||||
UseCaseAssessment,
|
||||
Risk,
|
||||
Control,
|
||||
UserPreferences,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
import {
|
||||
getStepById,
|
||||
getNextStep,
|
||||
getPreviousStep,
|
||||
getCompletionPercentage,
|
||||
getPhaseCompletionPercentage,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
|
||||
// =============================================================================
|
||||
// CONTEXT TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface ComplianceContextValue {
|
||||
// State
|
||||
state: SDKState
|
||||
dispatch: React.Dispatch<SDKAction>
|
||||
|
||||
// Client
|
||||
client: ComplianceClient
|
||||
|
||||
// Modules
|
||||
dsgvo: DSGVOModule
|
||||
compliance: ComplianceModule
|
||||
rag: RAGModule
|
||||
security: SecurityModule
|
||||
|
||||
// Navigation
|
||||
currentStep: SDKStep | undefined
|
||||
goToStep: (stepId: string) => void
|
||||
goToNextStep: () => void
|
||||
goToPreviousStep: () => void
|
||||
canGoNext: boolean
|
||||
canGoPrevious: boolean
|
||||
|
||||
// Progress
|
||||
completionPercentage: number
|
||||
phase1Completion: number
|
||||
phase2Completion: number
|
||||
|
||||
// Checkpoints
|
||||
validateCheckpoint: (checkpointId: string) => Promise<CheckpointStatus>
|
||||
overrideCheckpoint: (checkpointId: string, reason: string) => Promise<void>
|
||||
getCheckpointStatus: (checkpointId: string) => CheckpointStatus | undefined
|
||||
|
||||
// State Updates
|
||||
updateUseCase: (id: string, data: Partial<UseCaseAssessment>) => void
|
||||
addRisk: (risk: Risk) => void
|
||||
updateControl: (id: string, data: Partial<Control>) => void
|
||||
|
||||
// Persistence
|
||||
saveState: () => Promise<void>
|
||||
loadState: () => Promise<void>
|
||||
resetState: () => void
|
||||
|
||||
// Sync
|
||||
syncState: SyncState
|
||||
forceSyncToServer: () => Promise<void>
|
||||
isOnline: boolean
|
||||
|
||||
// Export
|
||||
exportState: (format: 'json' | 'pdf' | 'zip') => Promise<Blob>
|
||||
|
||||
// Command Bar
|
||||
isCommandBarOpen: boolean
|
||||
setCommandBarOpen: (open: boolean) => void
|
||||
|
||||
// Status
|
||||
isInitialized: boolean
|
||||
isLoading: boolean
|
||||
error: Error | null
|
||||
}
|
||||
|
||||
export const ComplianceContext = createContext<ComplianceContextValue | null>(null)
|
||||
|
||||
// =============================================================================
|
||||
// PROVIDER PROPS
|
||||
// =============================================================================
|
||||
|
||||
export interface ComplianceProviderProps {
|
||||
children: React.ReactNode
|
||||
apiEndpoint: string
|
||||
apiKey?: string
|
||||
tenantId: string
|
||||
userId?: string
|
||||
enableBackendSync?: boolean
|
||||
onNavigate?: (url: string) => void
|
||||
onError?: (error: Error) => void
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PROVIDER
|
||||
// =============================================================================
|
||||
|
||||
const SDK_STORAGE_KEY = 'breakpilot-compliance-sdk-state'
|
||||
|
||||
export function ComplianceProvider({
|
||||
children,
|
||||
apiEndpoint,
|
||||
apiKey,
|
||||
tenantId,
|
||||
userId = 'default',
|
||||
enableBackendSync = true,
|
||||
onNavigate,
|
||||
onError,
|
||||
}: ComplianceProviderProps) {
|
||||
const [state, dispatch] = useReducer(sdkReducer, {
|
||||
...initialState,
|
||||
tenantId,
|
||||
userId,
|
||||
})
|
||||
const [isCommandBarOpen, setCommandBarOpen] = useState(false)
|
||||
const [isInitialized, setIsInitialized] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
const [syncState, setSyncState] = useState<SyncState>({
|
||||
status: 'idle',
|
||||
lastSyncedAt: null,
|
||||
localVersion: 0,
|
||||
serverVersion: 0,
|
||||
pendingChanges: 0,
|
||||
error: null,
|
||||
})
|
||||
const [isOnline, setIsOnline] = useState(true)
|
||||
|
||||
// Refs
|
||||
const clientRef = useRef<ComplianceClient | null>(null)
|
||||
const syncManagerRef = useRef<StateSyncManager | null>(null)
|
||||
|
||||
// Initialize client
|
||||
if (!clientRef.current) {
|
||||
clientRef.current = new ComplianceClient({
|
||||
apiEndpoint,
|
||||
apiKey,
|
||||
tenantId,
|
||||
onError: err => {
|
||||
setError(err)
|
||||
onError?.(err)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const client = clientRef.current
|
||||
|
||||
// Modules
|
||||
const dsgvo = useMemo(
|
||||
() => createDSGVOModule(client, () => state),
|
||||
[client, state]
|
||||
)
|
||||
const compliance = useMemo(
|
||||
() => createComplianceModule(client, () => state),
|
||||
[client, state]
|
||||
)
|
||||
const rag = useMemo(() => createRAGModule(client), [client])
|
||||
const security = useMemo(
|
||||
() => createSecurityModule(client, () => state),
|
||||
[client, state]
|
||||
)
|
||||
|
||||
// Initialize sync manager
|
||||
useEffect(() => {
|
||||
if (enableBackendSync && typeof window !== 'undefined') {
|
||||
syncManagerRef.current = createStateSyncManager(
|
||||
client,
|
||||
tenantId,
|
||||
{ debounceMs: 2000, maxRetries: 3 },
|
||||
{
|
||||
onSyncStart: () => {
|
||||
setSyncState(prev => ({ ...prev, status: 'syncing' }))
|
||||
},
|
||||
onSyncComplete: syncedState => {
|
||||
setSyncState(prev => ({
|
||||
...prev,
|
||||
status: 'idle',
|
||||
lastSyncedAt: new Date(),
|
||||
pendingChanges: 0,
|
||||
}))
|
||||
if (new Date(syncedState.lastModified) > new Date(state.lastModified)) {
|
||||
dispatch({ type: 'SET_STATE', payload: syncedState })
|
||||
}
|
||||
},
|
||||
onSyncError: err => {
|
||||
setSyncState(prev => ({
|
||||
...prev,
|
||||
status: 'error',
|
||||
error: err.message,
|
||||
}))
|
||||
setError(err)
|
||||
},
|
||||
onConflict: () => {
|
||||
setSyncState(prev => ({ ...prev, status: 'conflict' }))
|
||||
},
|
||||
onOffline: () => {
|
||||
setIsOnline(false)
|
||||
setSyncState(prev => ({ ...prev, status: 'offline' }))
|
||||
},
|
||||
onOnline: () => {
|
||||
setIsOnline(true)
|
||||
setSyncState(prev => ({ ...prev, status: 'idle' }))
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return () => {
|
||||
syncManagerRef.current?.destroy()
|
||||
}
|
||||
}, [enableBackendSync, tenantId, client])
|
||||
|
||||
// Load initial state
|
||||
useEffect(() => {
|
||||
const loadInitialState = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
// Load from localStorage first
|
||||
if (typeof window !== 'undefined') {
|
||||
const stored = localStorage.getItem(`${SDK_STORAGE_KEY}-${tenantId}`)
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored)
|
||||
if (parsed.lastModified) {
|
||||
parsed.lastModified = new Date(parsed.lastModified)
|
||||
}
|
||||
dispatch({ type: 'SET_STATE', payload: parsed })
|
||||
}
|
||||
}
|
||||
|
||||
// Then load from server if enabled
|
||||
if (enableBackendSync && syncManagerRef.current) {
|
||||
const serverState = await syncManagerRef.current.loadFromServer()
|
||||
if (serverState) {
|
||||
dispatch({ type: 'SET_STATE', payload: serverState })
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err as Error)
|
||||
onError?.(err as Error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
setIsInitialized(true)
|
||||
}
|
||||
}
|
||||
|
||||
loadInitialState()
|
||||
}, [tenantId, enableBackendSync])
|
||||
|
||||
// Auto-save
|
||||
useEffect(() => {
|
||||
if (!isInitialized || !state.preferences.autoSave) return
|
||||
|
||||
const saveTimeout = setTimeout(() => {
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(`${SDK_STORAGE_KEY}-${tenantId}`, JSON.stringify(state))
|
||||
}
|
||||
|
||||
if (enableBackendSync && syncManagerRef.current) {
|
||||
syncManagerRef.current.queueSync(state)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to save state:', err)
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
return () => clearTimeout(saveTimeout)
|
||||
}, [state, tenantId, isInitialized, enableBackendSync])
|
||||
|
||||
// Keyboard shortcuts
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||
e.preventDefault()
|
||||
setCommandBarOpen(prev => !prev)
|
||||
}
|
||||
if (e.key === 'Escape' && isCommandBarOpen) {
|
||||
setCommandBarOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||
}, [isCommandBarOpen])
|
||||
|
||||
// Navigation
|
||||
const currentStep = useMemo(() => getStepById(state.currentStep), [state.currentStep])
|
||||
|
||||
const goToStep = useCallback(
|
||||
(stepId: string) => {
|
||||
const step = getStepById(stepId)
|
||||
if (step) {
|
||||
dispatch({ type: 'SET_CURRENT_STEP', payload: stepId })
|
||||
onNavigate?.(step.url)
|
||||
}
|
||||
},
|
||||
[onNavigate]
|
||||
)
|
||||
|
||||
const goToNextStep = useCallback(() => {
|
||||
const nextStep = getNextStep(state.currentStep)
|
||||
if (nextStep) {
|
||||
goToStep(nextStep.id)
|
||||
}
|
||||
}, [state.currentStep, goToStep])
|
||||
|
||||
const goToPreviousStep = useCallback(() => {
|
||||
const prevStep = getPreviousStep(state.currentStep)
|
||||
if (prevStep) {
|
||||
goToStep(prevStep.id)
|
||||
}
|
||||
}, [state.currentStep, goToStep])
|
||||
|
||||
const canGoNext = useMemo(() => getNextStep(state.currentStep) !== undefined, [state.currentStep])
|
||||
const canGoPrevious = useMemo(
|
||||
() => getPreviousStep(state.currentStep) !== undefined,
|
||||
[state.currentStep]
|
||||
)
|
||||
|
||||
// Progress
|
||||
const completionPercentage = useMemo(() => getCompletionPercentage(state), [state])
|
||||
const phase1Completion = useMemo(() => getPhaseCompletionPercentage(state, 1), [state])
|
||||
const phase2Completion = useMemo(() => getPhaseCompletionPercentage(state, 2), [state])
|
||||
|
||||
// Checkpoints
|
||||
const validateCheckpoint = useCallback(
|
||||
async (checkpointId: string): Promise<CheckpointStatus> => {
|
||||
if (enableBackendSync) {
|
||||
try {
|
||||
const result = await client.validateCheckpoint(checkpointId, state)
|
||||
const status: CheckpointStatus = {
|
||||
checkpointId: result.checkpointId,
|
||||
passed: result.passed,
|
||||
validatedAt: new Date(result.validatedAt),
|
||||
validatedBy: result.validatedBy,
|
||||
errors: result.errors,
|
||||
warnings: result.warnings,
|
||||
}
|
||||
dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } })
|
||||
return status
|
||||
} catch {
|
||||
// Fall through to local validation
|
||||
}
|
||||
}
|
||||
|
||||
// Local validation
|
||||
const status: CheckpointStatus = {
|
||||
checkpointId,
|
||||
passed: true,
|
||||
validatedAt: new Date(),
|
||||
validatedBy: 'SYSTEM',
|
||||
errors: [],
|
||||
warnings: [],
|
||||
}
|
||||
|
||||
dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } })
|
||||
return status
|
||||
},
|
||||
[state, enableBackendSync, client]
|
||||
)
|
||||
|
||||
const overrideCheckpoint = useCallback(
|
||||
async (checkpointId: string, reason: string): Promise<void> => {
|
||||
const existingStatus = state.checkpoints[checkpointId]
|
||||
const overriddenStatus: CheckpointStatus = {
|
||||
...existingStatus,
|
||||
checkpointId,
|
||||
passed: true,
|
||||
overrideReason: reason,
|
||||
overriddenBy: state.userId,
|
||||
overriddenAt: new Date(),
|
||||
errors: [],
|
||||
warnings: existingStatus?.warnings || [],
|
||||
}
|
||||
|
||||
dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status: overriddenStatus } })
|
||||
},
|
||||
[state.checkpoints, state.userId]
|
||||
)
|
||||
|
||||
const getCheckpointStatus = useCallback(
|
||||
(checkpointId: string): CheckpointStatus | undefined => {
|
||||
return state.checkpoints[checkpointId]
|
||||
},
|
||||
[state.checkpoints]
|
||||
)
|
||||
|
||||
// State Updates
|
||||
const updateUseCase = useCallback((id: string, data: Partial<UseCaseAssessment>) => {
|
||||
dispatch({ type: 'UPDATE_USE_CASE', payload: { id, data } })
|
||||
}, [])
|
||||
|
||||
const addRisk = useCallback((risk: Risk) => {
|
||||
dispatch({ type: 'ADD_RISK', payload: risk })
|
||||
}, [])
|
||||
|
||||
const updateControl = useCallback((id: string, data: Partial<Control>) => {
|
||||
dispatch({ type: 'UPDATE_CONTROL', payload: { id, data } })
|
||||
}, [])
|
||||
|
||||
// Persistence
|
||||
const saveState = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(`${SDK_STORAGE_KEY}-${tenantId}`, JSON.stringify(state))
|
||||
}
|
||||
|
||||
if (enableBackendSync && syncManagerRef.current) {
|
||||
await syncManagerRef.current.forceSync(state)
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err as Error)
|
||||
throw err
|
||||
}
|
||||
}, [state, tenantId, enableBackendSync])
|
||||
|
||||
const loadState = useCallback(async (): Promise<void> => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
if (enableBackendSync && syncManagerRef.current) {
|
||||
const serverState = await syncManagerRef.current.loadFromServer()
|
||||
if (serverState) {
|
||||
dispatch({ type: 'SET_STATE', payload: serverState })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const stored = localStorage.getItem(`${SDK_STORAGE_KEY}-${tenantId}`)
|
||||
if (stored) {
|
||||
dispatch({ type: 'SET_STATE', payload: JSON.parse(stored) })
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err as Error)
|
||||
throw err
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [tenantId, enableBackendSync])
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
dispatch({ type: 'RESET_STATE' })
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem(`${SDK_STORAGE_KEY}-${tenantId}`)
|
||||
}
|
||||
}, [tenantId])
|
||||
|
||||
// Sync
|
||||
const forceSyncToServer = useCallback(async (): Promise<void> => {
|
||||
if (enableBackendSync && syncManagerRef.current) {
|
||||
await syncManagerRef.current.forceSync(state)
|
||||
}
|
||||
}, [state, enableBackendSync])
|
||||
|
||||
// Export
|
||||
const exportState = useCallback(
|
||||
async (format: 'json' | 'pdf' | 'zip'): Promise<Blob> => {
|
||||
if (format === 'json') {
|
||||
return new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' })
|
||||
}
|
||||
return client.exportState(format)
|
||||
},
|
||||
[state, client]
|
||||
)
|
||||
|
||||
const value: ComplianceContextValue = {
|
||||
state,
|
||||
dispatch,
|
||||
client,
|
||||
dsgvo,
|
||||
compliance,
|
||||
rag,
|
||||
security,
|
||||
currentStep,
|
||||
goToStep,
|
||||
goToNextStep,
|
||||
goToPreviousStep,
|
||||
canGoNext,
|
||||
canGoPrevious,
|
||||
completionPercentage,
|
||||
phase1Completion,
|
||||
phase2Completion,
|
||||
validateCheckpoint,
|
||||
overrideCheckpoint,
|
||||
getCheckpointStatus,
|
||||
updateUseCase,
|
||||
addRisk,
|
||||
updateControl,
|
||||
saveState,
|
||||
loadState,
|
||||
resetState,
|
||||
syncState,
|
||||
forceSyncToServer,
|
||||
isOnline,
|
||||
exportState,
|
||||
isCommandBarOpen,
|
||||
setCommandBarOpen,
|
||||
isInitialized,
|
||||
isLoading,
|
||||
error,
|
||||
}
|
||||
|
||||
return <ComplianceContext.Provider value={value}>{children}</ComplianceContext.Provider>
|
||||
}
|
||||
10
breakpilot-compliance-sdk/packages/react/tsconfig.json
Normal file
10
breakpilot-compliance-sdk/packages/react/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
14
breakpilot-compliance-sdk/packages/react/tsup.config.ts
Normal file
14
breakpilot-compliance-sdk/packages/react/tsup.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts', 'src/hooks.ts', 'src/components.ts'],
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
external: ['react', 'react-dom', '@breakpilot/compliance-sdk-core', '@breakpilot/compliance-sdk-types'],
|
||||
esbuildOptions(options) {
|
||||
options.jsx = 'automatic'
|
||||
},
|
||||
})
|
||||
49
breakpilot-compliance-sdk/packages/types/package.json
Normal file
49
breakpilot-compliance-sdk/packages/types/package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@breakpilot/compliance-sdk-types",
|
||||
"version": "1.0.0",
|
||||
"description": "TypeScript type definitions for BreakPilot Compliance SDK",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./dsgvo": {
|
||||
"import": "./dist/dsgvo.mjs",
|
||||
"require": "./dist/dsgvo.js",
|
||||
"types": "./dist/dsgvo.d.ts"
|
||||
},
|
||||
"./compliance": {
|
||||
"import": "./dist/compliance.mjs",
|
||||
"require": "./dist/compliance.js",
|
||||
"types": "./dist/compliance.d.ts"
|
||||
},
|
||||
"./rag": {
|
||||
"import": "./dist/rag.mjs",
|
||||
"require": "./dist/rag.js",
|
||||
"types": "./dist/rag.d.ts"
|
||||
},
|
||||
"./security": {
|
||||
"import": "./dist/security.mjs",
|
||||
"require": "./dist/security.js",
|
||||
"types": "./dist/security.d.ts"
|
||||
}
|
||||
},
|
||||
"files": ["dist"],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsup": "^8.0.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
}
|
||||
}
|
||||
318
breakpilot-compliance-sdk/packages/types/src/api.ts
Normal file
318
breakpilot-compliance-sdk/packages/types/src/api.ts
Normal file
@@ -0,0 +1,318 @@
|
||||
/**
|
||||
* API Types
|
||||
*
|
||||
* Type definitions for API communication
|
||||
*/
|
||||
|
||||
import type { SDKState } from './state'
|
||||
import type { CheckpointStatus, ValidationError } from './base'
|
||||
|
||||
// =============================================================================
|
||||
// API RESPONSE TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface APIResponse<T = unknown> {
|
||||
success: boolean
|
||||
data?: T
|
||||
error?: string
|
||||
message?: string
|
||||
version?: number
|
||||
timestamp?: string
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
data: T[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
totalPages: number
|
||||
}
|
||||
|
||||
export interface APIError {
|
||||
code: string
|
||||
message: string
|
||||
details?: Record<string, unknown>
|
||||
status: number
|
||||
retryable: boolean
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STATE MANAGEMENT
|
||||
// =============================================================================
|
||||
|
||||
export interface StateResponse {
|
||||
tenantId: string
|
||||
state: SDKState
|
||||
version: number
|
||||
lastModified: string
|
||||
}
|
||||
|
||||
export interface SaveStateRequest {
|
||||
tenantId: string
|
||||
state: SDKState
|
||||
version?: number // For optimistic locking
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CHECKPOINT VALIDATION
|
||||
// =============================================================================
|
||||
|
||||
export interface CheckpointValidationRequest {
|
||||
tenantId: string
|
||||
checkpointId: string
|
||||
data?: unknown
|
||||
}
|
||||
|
||||
export interface CheckpointValidationResult {
|
||||
checkpointId: string
|
||||
passed: boolean
|
||||
errors: ValidationError[]
|
||||
warnings: ValidationError[]
|
||||
validatedAt: string
|
||||
validatedBy: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// AUTHENTICATION
|
||||
// =============================================================================
|
||||
|
||||
export interface AuthTokenRequest {
|
||||
grantType: 'client_credentials' | 'authorization_code' | 'refresh_token'
|
||||
clientId: string
|
||||
clientSecret?: string
|
||||
code?: string
|
||||
refreshToken?: string
|
||||
redirectUri?: string
|
||||
}
|
||||
|
||||
export interface AuthTokenResponse {
|
||||
accessToken: string
|
||||
tokenType: 'Bearer'
|
||||
expiresIn: number
|
||||
refreshToken?: string
|
||||
scope?: string
|
||||
}
|
||||
|
||||
export interface APIKeyInfo {
|
||||
id: string
|
||||
name: string
|
||||
prefix: string
|
||||
scopes: string[]
|
||||
createdAt: Date
|
||||
lastUsedAt?: Date
|
||||
expiresAt?: Date
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SYNC
|
||||
// =============================================================================
|
||||
|
||||
export type SyncStatus = 'idle' | 'syncing' | 'error' | 'offline' | 'conflict'
|
||||
|
||||
export interface SyncState {
|
||||
status: SyncStatus
|
||||
lastSyncedAt: Date | null
|
||||
localVersion: number
|
||||
serverVersion: number
|
||||
pendingChanges: number
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export interface ConflictResolution {
|
||||
strategy: 'local' | 'server' | 'merge'
|
||||
mergedState?: SDKState
|
||||
}
|
||||
|
||||
export interface SyncOptions {
|
||||
debounceMs?: number
|
||||
maxRetries?: number
|
||||
conflictHandler?: (local: SDKState, server: SDKState) => Promise<ConflictResolution>
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FLOW NAVIGATION
|
||||
// =============================================================================
|
||||
|
||||
export interface FlowStateResponse {
|
||||
currentStep: string
|
||||
currentPhase: 1 | 2
|
||||
completedSteps: string[]
|
||||
suggestions: Array<{ stepId: string; reason: string }>
|
||||
}
|
||||
|
||||
export interface FlowNavigationRequest {
|
||||
tenantId: string
|
||||
direction: 'next' | 'previous'
|
||||
}
|
||||
|
||||
export interface FlowNavigationResponse {
|
||||
stepId: string
|
||||
phase: 1 | 2
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EXPORT
|
||||
// =============================================================================
|
||||
|
||||
export type ExportFormat = 'json' | 'pdf' | 'docx' | 'zip' | 'csv'
|
||||
|
||||
export interface ExportRequest {
|
||||
tenantId: string
|
||||
format: ExportFormat
|
||||
sections?: string[]
|
||||
includeEvidence?: boolean
|
||||
language?: 'de' | 'en'
|
||||
}
|
||||
|
||||
export interface ExportResponse {
|
||||
id: string
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||
downloadUrl?: string
|
||||
expiresAt?: Date
|
||||
error?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RAG API
|
||||
// =============================================================================
|
||||
|
||||
export interface RAGSearchRequest {
|
||||
query: string
|
||||
filters?: {
|
||||
documentCodes?: string[]
|
||||
jurisdictions?: string[]
|
||||
languages?: string[]
|
||||
articles?: string[]
|
||||
}
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
export interface RAGSearchResponse {
|
||||
results: Array<{
|
||||
id: string
|
||||
documentCode: string
|
||||
documentName: string
|
||||
content: string
|
||||
article?: string
|
||||
score: number
|
||||
highlights: string[]
|
||||
}>
|
||||
total: number
|
||||
processingTimeMs: number
|
||||
}
|
||||
|
||||
export interface RAGAskRequest {
|
||||
question: string
|
||||
context?: string
|
||||
documents?: string[]
|
||||
maxSources?: number
|
||||
language?: 'de' | 'en'
|
||||
}
|
||||
|
||||
export interface RAGAskResponse {
|
||||
answer: string
|
||||
sources: Array<{
|
||||
documentCode: string
|
||||
documentName: string
|
||||
article?: string
|
||||
content: string
|
||||
relevanceScore: number
|
||||
}>
|
||||
confidence: number
|
||||
processingTimeMs: number
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DOCUMENT GENERATION
|
||||
// =============================================================================
|
||||
|
||||
export interface GenerateDocumentRequest {
|
||||
tenantId: string
|
||||
documentType: 'dsfa' | 'tom' | 'vvt' | 'gutachten' | 'privacy_policy' | 'cookie_banner'
|
||||
options?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface GenerateDocumentResponse {
|
||||
id: string
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||
content?: string
|
||||
documentUrl?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SECURITY SCAN
|
||||
// =============================================================================
|
||||
|
||||
export interface SecurityScanRequest {
|
||||
tenantId: string
|
||||
tools?: string[]
|
||||
targetPath?: string
|
||||
excludePaths?: string[]
|
||||
severityThreshold?: string
|
||||
generateSBOM?: boolean
|
||||
}
|
||||
|
||||
export interface SecurityScanResponse {
|
||||
id: string
|
||||
status: 'queued' | 'running' | 'completed' | 'failed'
|
||||
totalIssues?: number
|
||||
critical?: number
|
||||
high?: number
|
||||
medium?: number
|
||||
low?: number
|
||||
sbomId?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// WEBHOOKS
|
||||
// =============================================================================
|
||||
|
||||
export type WebhookEventType =
|
||||
| 'state.updated'
|
||||
| 'checkpoint.passed'
|
||||
| 'checkpoint.failed'
|
||||
| 'document.generated'
|
||||
| 'security.scan_completed'
|
||||
| 'dsr.received'
|
||||
| 'breach.detected'
|
||||
|
||||
export interface WebhookConfig {
|
||||
id: string
|
||||
url: string
|
||||
events: WebhookEventType[]
|
||||
secret?: string
|
||||
enabled: boolean
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
export interface WebhookPayload {
|
||||
id: string
|
||||
event: WebhookEventType
|
||||
timestamp: string
|
||||
tenantId: string
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HEALTH & STATUS
|
||||
// =============================================================================
|
||||
|
||||
export interface HealthCheckResponse {
|
||||
status: 'healthy' | 'degraded' | 'unhealthy'
|
||||
version: string
|
||||
uptime: number
|
||||
services: Record<string, {
|
||||
status: 'up' | 'down'
|
||||
latencyMs?: number
|
||||
}>
|
||||
}
|
||||
|
||||
export interface RateLimitInfo {
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: Date
|
||||
}
|
||||
220
breakpilot-compliance-sdk/packages/types/src/base.ts
Normal file
220
breakpilot-compliance-sdk/packages/types/src/base.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Base Types and Enums
|
||||
*
|
||||
* Core type definitions used across all SDK modules
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// SUBSCRIPTION & PLANS
|
||||
// =============================================================================
|
||||
|
||||
export type SubscriptionTier = 'FREE' | 'STARTER' | 'PROFESSIONAL' | 'ENTERPRISE'
|
||||
|
||||
export type DeploymentMode = 'CLOUD' | 'MAC_MINI' | 'MAC_STUDIO' | 'ON_PREMISE'
|
||||
|
||||
// =============================================================================
|
||||
// STATUS ENUMS
|
||||
// =============================================================================
|
||||
|
||||
export type ValidationSeverity = 'ERROR' | 'WARNING' | 'INFO'
|
||||
|
||||
export type RiskSeverity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
|
||||
|
||||
export type RiskLikelihood = 1 | 2 | 3 | 4 | 5
|
||||
|
||||
export type RiskImpact = 1 | 2 | 3 | 4 | 5
|
||||
|
||||
export type ImplementationStatus = 'NOT_IMPLEMENTED' | 'PARTIAL' | 'IMPLEMENTED'
|
||||
|
||||
export type RequirementStatus = 'NOT_STARTED' | 'IN_PROGRESS' | 'IMPLEMENTED' | 'VERIFIED'
|
||||
|
||||
export type ReviewerType = 'NONE' | 'TEAM_LEAD' | 'DSB' | 'LEGAL'
|
||||
|
||||
// =============================================================================
|
||||
// TENANT & USER
|
||||
// =============================================================================
|
||||
|
||||
export interface Tenant {
|
||||
id: string
|
||||
name: string
|
||||
subscription: SubscriptionTier
|
||||
deploymentMode: DeploymentMode
|
||||
createdAt: Date
|
||||
settings: TenantSettings
|
||||
}
|
||||
|
||||
export interface TenantSettings {
|
||||
defaultLanguage: 'de' | 'en'
|
||||
timezone: string
|
||||
dataResidency: 'EU' | 'DE' | 'CUSTOM'
|
||||
features: EnabledFeatures
|
||||
}
|
||||
|
||||
export interface EnabledFeatures {
|
||||
dsgvo: boolean
|
||||
nis2: boolean
|
||||
aiAct: boolean
|
||||
sbom: boolean
|
||||
security: boolean
|
||||
rag: boolean
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string
|
||||
tenantId: string
|
||||
email: string
|
||||
name: string
|
||||
role: UserRole
|
||||
permissions: Permission[]
|
||||
preferences: UserPreferences
|
||||
}
|
||||
|
||||
export type UserRole = 'ADMIN' | 'DSB' | 'COMPLIANCE_MANAGER' | 'AUDITOR' | 'VIEWER'
|
||||
|
||||
export type Permission =
|
||||
| 'READ_STATE'
|
||||
| 'WRITE_STATE'
|
||||
| 'MANAGE_CONTROLS'
|
||||
| 'MANAGE_EVIDENCE'
|
||||
| 'APPROVE_DSFA'
|
||||
| 'EXPORT_DATA'
|
||||
| 'MANAGE_USERS'
|
||||
| 'MANAGE_SETTINGS'
|
||||
|
||||
export interface UserPreferences {
|
||||
language: 'de' | 'en'
|
||||
theme: 'light' | 'dark' | 'system'
|
||||
compactMode: boolean
|
||||
showHints: boolean
|
||||
autoSave: boolean
|
||||
autoValidate: boolean
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// VALIDATION & CHECKPOINTS
|
||||
// =============================================================================
|
||||
|
||||
export type CheckpointType = 'REQUIRED' | 'RECOMMENDED' | 'OPTIONAL'
|
||||
|
||||
export interface ValidationRule {
|
||||
id: string
|
||||
field: string
|
||||
condition: 'NOT_EMPTY' | 'MIN_COUNT' | 'MIN_VALUE' | 'CUSTOM' | 'REGEX'
|
||||
value?: number | string
|
||||
message: string
|
||||
severity: ValidationSeverity
|
||||
}
|
||||
|
||||
export interface ValidationError {
|
||||
ruleId: string
|
||||
field: string
|
||||
message: string
|
||||
severity: ValidationSeverity
|
||||
}
|
||||
|
||||
export interface Checkpoint {
|
||||
id: string
|
||||
step: string
|
||||
name: string
|
||||
type: CheckpointType
|
||||
validation: ValidationRule[]
|
||||
blocksProgress: boolean
|
||||
requiresReview: ReviewerType
|
||||
autoValidate: boolean
|
||||
}
|
||||
|
||||
export interface CheckpointStatus {
|
||||
checkpointId: string
|
||||
passed: boolean
|
||||
validatedAt: Date | null
|
||||
validatedBy: string | null
|
||||
errors: ValidationError[]
|
||||
warnings: ValidationError[]
|
||||
overrideReason?: string
|
||||
overriddenBy?: string
|
||||
overriddenAt?: Date
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RISK MANAGEMENT
|
||||
// =============================================================================
|
||||
|
||||
export type RiskStatus = 'IDENTIFIED' | 'ASSESSED' | 'MITIGATED' | 'ACCEPTED' | 'CLOSED'
|
||||
|
||||
export type MitigationType = 'AVOID' | 'TRANSFER' | 'MITIGATE' | 'ACCEPT'
|
||||
|
||||
export interface RiskMitigation {
|
||||
id: string
|
||||
description: string
|
||||
type: MitigationType
|
||||
status: 'PLANNED' | 'IN_PROGRESS' | 'COMPLETED'
|
||||
effectiveness: number // 0-100
|
||||
controlId: string | null
|
||||
}
|
||||
|
||||
export interface Risk {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
category: string
|
||||
likelihood: RiskLikelihood
|
||||
impact: RiskImpact
|
||||
severity: RiskSeverity
|
||||
inherentRiskScore: number
|
||||
residualRiskScore: number
|
||||
status: RiskStatus
|
||||
mitigation: RiskMitigation[]
|
||||
owner: string | null
|
||||
relatedControls: string[]
|
||||
relatedRequirements: string[]
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COMMAND BAR
|
||||
// =============================================================================
|
||||
|
||||
export type CommandType = 'ACTION' | 'NAVIGATION' | 'SEARCH' | 'GENERATE' | 'HELP'
|
||||
|
||||
export interface CommandSuggestion {
|
||||
id: string
|
||||
type: CommandType
|
||||
label: string
|
||||
description: string
|
||||
shortcut?: string
|
||||
icon?: string
|
||||
action: () => void | Promise<void>
|
||||
relevanceScore: number
|
||||
}
|
||||
|
||||
export interface CommandHistory {
|
||||
id: string
|
||||
query: string
|
||||
type: CommandType
|
||||
timestamp: Date
|
||||
success: boolean
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// UTILITY FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
export function calculateRiskScore(likelihood: RiskLikelihood, impact: RiskImpact): number {
|
||||
return likelihood * impact
|
||||
}
|
||||
|
||||
export function getRiskSeverityFromScore(score: number): RiskSeverity {
|
||||
if (score >= 20) return 'CRITICAL'
|
||||
if (score >= 12) return 'HIGH'
|
||||
if (score >= 6) return 'MEDIUM'
|
||||
return 'LOW'
|
||||
}
|
||||
|
||||
export function calculateResidualRisk(risk: Risk): number {
|
||||
const inherentScore = calculateRiskScore(risk.likelihood, risk.impact)
|
||||
const totalEffectiveness = risk.mitigation
|
||||
.filter(m => m.status === 'COMPLETED')
|
||||
.reduce((sum, m) => sum + m.effectiveness, 0)
|
||||
|
||||
const effectivenessMultiplier = Math.min(totalEffectiveness, 100) / 100
|
||||
return Math.max(1, Math.round(inherentScore * (1 - effectivenessMultiplier)))
|
||||
}
|
||||
396
breakpilot-compliance-sdk/packages/types/src/compliance.ts
Normal file
396
breakpilot-compliance-sdk/packages/types/src/compliance.ts
Normal file
@@ -0,0 +1,396 @@
|
||||
/**
|
||||
* Compliance Module Types
|
||||
*
|
||||
* Type definitions for general compliance features:
|
||||
* - Controls & Control Catalog
|
||||
* - Evidence Management
|
||||
* - Obligations & Requirements
|
||||
* - AI Act Classification
|
||||
* - NIS2 Compliance
|
||||
* - Audit & Assessment
|
||||
*/
|
||||
|
||||
import type {
|
||||
RiskSeverity,
|
||||
ImplementationStatus,
|
||||
RequirementStatus,
|
||||
ValidationError,
|
||||
} from './base'
|
||||
|
||||
// =============================================================================
|
||||
// REGULATIONS
|
||||
// =============================================================================
|
||||
|
||||
export type RegulationCode =
|
||||
| 'DSGVO'
|
||||
| 'AI_ACT'
|
||||
| 'NIS2'
|
||||
| 'EPRIVACY'
|
||||
| 'TDDDG'
|
||||
| 'SCC'
|
||||
| 'DPF'
|
||||
| 'CRA'
|
||||
| 'EUCSA'
|
||||
| 'DATA_ACT'
|
||||
| 'DGA'
|
||||
| 'DSA'
|
||||
| 'EAA'
|
||||
| 'ISO_27001'
|
||||
| 'SOC2'
|
||||
| 'BSI_GRUNDSCHUTZ'
|
||||
|
||||
export interface Regulation {
|
||||
id: string
|
||||
code: RegulationCode
|
||||
name: string
|
||||
fullName: string
|
||||
description: string
|
||||
jurisdiction: 'EU' | 'DE' | 'INT'
|
||||
effectiveDate: Date
|
||||
requirements: number
|
||||
controlMappings: number
|
||||
documentUrl?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONTROLS
|
||||
// =============================================================================
|
||||
|
||||
export type ControlType = 'TECHNICAL' | 'ORGANIZATIONAL' | 'PHYSICAL'
|
||||
|
||||
export type ControlDomain =
|
||||
| 'ACCESS_MANAGEMENT'
|
||||
| 'DATA_PROTECTION'
|
||||
| 'NETWORK_SECURITY'
|
||||
| 'INCIDENT_RESPONSE'
|
||||
| 'BUSINESS_CONTINUITY'
|
||||
| 'VENDOR_MANAGEMENT'
|
||||
| 'ASSET_MANAGEMENT'
|
||||
| 'CRYPTOGRAPHY'
|
||||
| 'PHYSICAL_SECURITY'
|
||||
|
||||
export interface Control {
|
||||
id: string
|
||||
code: string // e.g., "AC-01", "DP-03"
|
||||
name: string
|
||||
description: string
|
||||
type: ControlType
|
||||
domain: ControlDomain
|
||||
implementationStatus: ImplementationStatus
|
||||
effectiveness: RiskSeverity
|
||||
evidence: string[]
|
||||
owner: string | null
|
||||
dueDate: Date | null
|
||||
mappings: ControlMapping[]
|
||||
testingFrequency: 'ANNUAL' | 'SEMI_ANNUAL' | 'QUARTERLY' | 'MONTHLY'
|
||||
lastTestDate?: Date
|
||||
nextTestDate?: Date
|
||||
}
|
||||
|
||||
export interface ControlMapping {
|
||||
regulationCode: RegulationCode
|
||||
requirementId: string
|
||||
article: string
|
||||
}
|
||||
|
||||
export interface ControlCatalog {
|
||||
id: string
|
||||
name: string
|
||||
version: string
|
||||
controls: Control[]
|
||||
domains: ControlDomain[]
|
||||
lastUpdated: Date
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// REQUIREMENTS
|
||||
// =============================================================================
|
||||
|
||||
export interface Requirement {
|
||||
id: string
|
||||
regulationCode: RegulationCode
|
||||
article: string
|
||||
title: string
|
||||
description: string
|
||||
criticality: RiskSeverity
|
||||
applicableModules: string[]
|
||||
status: RequirementStatus
|
||||
controls: string[] // Control IDs
|
||||
deadline?: Date
|
||||
notes?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EVIDENCE
|
||||
// =============================================================================
|
||||
|
||||
export type EvidenceType =
|
||||
| 'DOCUMENT'
|
||||
| 'SCREENSHOT'
|
||||
| 'LOG'
|
||||
| 'CERTIFICATE'
|
||||
| 'AUDIT_REPORT'
|
||||
| 'POLICY'
|
||||
| 'TRAINING_RECORD'
|
||||
|
||||
export type EvidenceStatus = 'DRAFT' | 'ACTIVE' | 'EXPIRED' | 'ARCHIVED'
|
||||
|
||||
export interface Evidence {
|
||||
id: string
|
||||
controlId: string
|
||||
type: EvidenceType
|
||||
name: string
|
||||
description: string
|
||||
fileUrl: string | null
|
||||
mimeType?: string
|
||||
fileSize?: number
|
||||
validFrom: Date
|
||||
validUntil: Date | null
|
||||
status: EvidenceStatus
|
||||
uploadedBy: string
|
||||
uploadedAt: Date
|
||||
reviewedBy?: string
|
||||
reviewedAt?: Date
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
export interface EvidenceUploadRequest {
|
||||
controlId: string
|
||||
type: EvidenceType
|
||||
name: string
|
||||
description?: string
|
||||
validFrom?: Date
|
||||
validUntil?: Date
|
||||
tags?: string[]
|
||||
file: File | Blob
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// AI ACT
|
||||
// =============================================================================
|
||||
|
||||
export type AIActRiskCategory =
|
||||
| 'MINIMAL' // Minimal or no risk
|
||||
| 'LIMITED' // Limited risk - transparency
|
||||
| 'HIGH' // High-risk - conformity assessment
|
||||
| 'UNACCEPTABLE' // Prohibited
|
||||
|
||||
export type AISystemType =
|
||||
| 'GENERAL_PURPOSE'
|
||||
| 'BIOMETRIC'
|
||||
| 'CRITICAL_INFRASTRUCTURE'
|
||||
| 'EDUCATION'
|
||||
| 'EMPLOYMENT'
|
||||
| 'ESSENTIAL_SERVICES'
|
||||
| 'LAW_ENFORCEMENT'
|
||||
| 'BORDER_CONTROL'
|
||||
| 'JUSTICE'
|
||||
| 'RECOMMENDATION'
|
||||
| 'CONTENT_GENERATION'
|
||||
|
||||
export interface AIActObligation {
|
||||
id: string
|
||||
article: string
|
||||
title: string
|
||||
description: string
|
||||
riskCategory: AIActRiskCategory
|
||||
deadline: Date | null
|
||||
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED'
|
||||
evidence?: string[]
|
||||
}
|
||||
|
||||
export interface AIActResult {
|
||||
riskCategory: AIActRiskCategory
|
||||
systemType: AISystemType
|
||||
obligations: AIActObligation[]
|
||||
assessmentDate: Date
|
||||
assessedBy: string
|
||||
justification: string
|
||||
mitigationRequired: boolean
|
||||
conformityAssessmentRequired: boolean
|
||||
transparencyRequired: boolean
|
||||
}
|
||||
|
||||
export interface AIActAssessmentInput {
|
||||
systemDescription: string
|
||||
intendedUse: string
|
||||
dataCategories: string[]
|
||||
affectedPersons: string[]
|
||||
automatedDecisions: boolean
|
||||
humanOversight: boolean
|
||||
riskMitigations: string[]
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// NIS2
|
||||
// =============================================================================
|
||||
|
||||
export type NIS2Sector =
|
||||
| 'ENERGY'
|
||||
| 'TRANSPORT'
|
||||
| 'BANKING'
|
||||
| 'FINANCIAL_MARKET'
|
||||
| 'HEALTH'
|
||||
| 'DRINKING_WATER'
|
||||
| 'WASTE_WATER'
|
||||
| 'DIGITAL_INFRASTRUCTURE'
|
||||
| 'ICT_SERVICE_MANAGEMENT'
|
||||
| 'PUBLIC_ADMINISTRATION'
|
||||
| 'SPACE'
|
||||
| 'POSTAL'
|
||||
| 'WASTE_MANAGEMENT'
|
||||
| 'CHEMICAL'
|
||||
| 'FOOD'
|
||||
| 'MANUFACTURING'
|
||||
| 'DIGITAL_PROVIDERS'
|
||||
| 'RESEARCH'
|
||||
|
||||
export type NIS2EntityType = 'ESSENTIAL' | 'IMPORTANT'
|
||||
|
||||
export interface NIS2Assessment {
|
||||
sector: NIS2Sector
|
||||
entityType: NIS2EntityType
|
||||
employeeCount: number
|
||||
annualTurnover: number // in EUR
|
||||
applicableRequirements: NIS2Requirement[]
|
||||
complianceScore: number // 0-100
|
||||
gaps: string[]
|
||||
}
|
||||
|
||||
export interface NIS2Requirement {
|
||||
id: string
|
||||
article: string
|
||||
title: string
|
||||
description: string
|
||||
controls: string[]
|
||||
status: RequirementStatus
|
||||
deadline?: Date
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// OBLIGATIONS
|
||||
// =============================================================================
|
||||
|
||||
export interface Obligation {
|
||||
id: string
|
||||
regulationCode: RegulationCode
|
||||
article: string
|
||||
title: string
|
||||
description: string
|
||||
deadline: Date | null
|
||||
penalty: string | null
|
||||
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED'
|
||||
responsible: string | null
|
||||
priority: RiskSeverity
|
||||
linkedControls: string[]
|
||||
linkedEvidence: string[]
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// AUDIT & ASSESSMENT
|
||||
// =============================================================================
|
||||
|
||||
export type AuditStatus = 'PLANNED' | 'IN_PROGRESS' | 'COMPLETED' | 'CANCELLED'
|
||||
|
||||
export type AuditType = 'INTERNAL' | 'EXTERNAL' | 'CERTIFICATION' | 'REGULATORY'
|
||||
|
||||
export interface Audit {
|
||||
id: string
|
||||
name: string
|
||||
type: AuditType
|
||||
status: AuditStatus
|
||||
scope: string[]
|
||||
regulations: RegulationCode[]
|
||||
auditor: string
|
||||
startDate: Date
|
||||
endDate: Date | null
|
||||
findings: AuditFinding[]
|
||||
report?: string
|
||||
}
|
||||
|
||||
export interface AuditFinding {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
severity: RiskSeverity
|
||||
controlId?: string
|
||||
recommendation: string
|
||||
status: 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'ACCEPTED'
|
||||
dueDate?: Date
|
||||
responsiblePerson?: string
|
||||
}
|
||||
|
||||
export interface ChecklistItem {
|
||||
id: string
|
||||
requirementId: string
|
||||
title: string
|
||||
description: string
|
||||
status: 'PENDING' | 'PASSED' | 'FAILED' | 'NOT_APPLICABLE'
|
||||
notes: string
|
||||
verifiedBy: string | null
|
||||
verifiedAt: Date | null
|
||||
evidence?: string[]
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COMPLIANCE SCORE
|
||||
// =============================================================================
|
||||
|
||||
export interface ComplianceScore {
|
||||
overall: number // 0-100
|
||||
byRegulation: Record<RegulationCode, number>
|
||||
byDomain: Record<ControlDomain, number>
|
||||
trend: 'UP' | 'DOWN' | 'STABLE'
|
||||
lastCalculated: Date
|
||||
}
|
||||
|
||||
export interface ComplianceGap {
|
||||
id: string
|
||||
regulationCode: RegulationCode
|
||||
requirementId: string
|
||||
controlId?: string
|
||||
description: string
|
||||
severity: RiskSeverity
|
||||
recommendation: string
|
||||
estimatedEffort: 'LOW' | 'MEDIUM' | 'HIGH'
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MODULES / SERVICE MODULES
|
||||
// =============================================================================
|
||||
|
||||
export interface ServiceModule {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
regulations: RegulationCode[]
|
||||
criticality: RiskSeverity
|
||||
processesPersonalData: boolean
|
||||
hasAIComponents: boolean
|
||||
dataCategories: string[]
|
||||
owner: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EXPORT
|
||||
// =============================================================================
|
||||
|
||||
export interface ExportOptions {
|
||||
format: 'PDF' | 'DOCX' | 'JSON' | 'CSV' | 'ZIP'
|
||||
sections: ExportSection[]
|
||||
includeEvidence: boolean
|
||||
language: 'de' | 'en'
|
||||
template?: string
|
||||
}
|
||||
|
||||
export type ExportSection =
|
||||
| 'SUMMARY'
|
||||
| 'CONTROLS'
|
||||
| 'EVIDENCE'
|
||||
| 'RISKS'
|
||||
| 'REQUIREMENTS'
|
||||
| 'OBLIGATIONS'
|
||||
| 'AUDIT_FINDINGS'
|
||||
| 'VVT'
|
||||
| 'DSFA'
|
||||
| 'TOM'
|
||||
394
breakpilot-compliance-sdk/packages/types/src/dsgvo.ts
Normal file
394
breakpilot-compliance-sdk/packages/types/src/dsgvo.ts
Normal file
@@ -0,0 +1,394 @@
|
||||
/**
|
||||
* DSGVO Module Types
|
||||
*
|
||||
* Type definitions for GDPR/DSGVO compliance features:
|
||||
* - DSR (Data Subject Requests) - Art. 15-21
|
||||
* - Consent Management - Art. 6, 7
|
||||
* - VVT (Processing Register) - Art. 30
|
||||
* - DSFA (Impact Assessment) - Art. 35, 36
|
||||
* - TOM (Technical & Organizational Measures) - Art. 32
|
||||
* - Retention Policies - Art. 5, 17
|
||||
*/
|
||||
|
||||
import type { RiskSeverity, ImplementationStatus } from './base'
|
||||
|
||||
// =============================================================================
|
||||
// DSR (Data Subject Requests) - Art. 15-21 DSGVO
|
||||
// =============================================================================
|
||||
|
||||
export type DSRRequestType =
|
||||
| 'ACCESS' // Art. 15 - Auskunftsrecht
|
||||
| 'RECTIFICATION' // Art. 16 - Berichtigungsrecht
|
||||
| 'ERASURE' // Art. 17 - Löschungsrecht
|
||||
| 'PORTABILITY' // Art. 20 - Datenübertragbarkeit
|
||||
| 'RESTRICTION' // Art. 18 - Einschränkungsrecht
|
||||
| 'OBJECTION' // Art. 21 - Widerspruchsrecht
|
||||
|
||||
export type DSRStatus =
|
||||
| 'RECEIVED'
|
||||
| 'VERIFIED'
|
||||
| 'PROCESSING'
|
||||
| 'COMPLETED'
|
||||
| 'REJECTED'
|
||||
|
||||
export interface DSRRequest {
|
||||
id: string
|
||||
type: DSRRequestType
|
||||
status: DSRStatus
|
||||
requesterEmail: string
|
||||
requesterName: string
|
||||
requestedAt: Date
|
||||
dueDate: Date
|
||||
completedAt: Date | null
|
||||
notes: string
|
||||
verificationMethod?: string
|
||||
responseDocument?: string
|
||||
}
|
||||
|
||||
export interface DSRConfig {
|
||||
id: string
|
||||
enabled: boolean
|
||||
portalUrl: string
|
||||
emailTemplates: Record<DSRRequestType, string>
|
||||
automatedResponses: boolean
|
||||
verificationRequired: boolean
|
||||
deadlineDays: number // Default 30 days per GDPR
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONSENT MANAGEMENT - Art. 6, 7 DSGVO
|
||||
// =============================================================================
|
||||
|
||||
export type ConsentPurpose =
|
||||
| 'ESSENTIAL'
|
||||
| 'FUNCTIONAL'
|
||||
| 'ANALYTICS'
|
||||
| 'MARKETING'
|
||||
| 'PERSONALIZATION'
|
||||
| 'THIRD_PARTY'
|
||||
|
||||
export type ConsentChannel =
|
||||
| 'WEB'
|
||||
| 'MOBILE_APP'
|
||||
| 'EMAIL'
|
||||
| 'PAPER'
|
||||
| 'VERBAL'
|
||||
| 'API'
|
||||
|
||||
export interface ConsentRecord {
|
||||
id: string
|
||||
userId: string
|
||||
documentId: string
|
||||
documentVersion: string
|
||||
consentType: ConsentPurpose
|
||||
channel: ConsentChannel
|
||||
granted: boolean
|
||||
grantedAt: Date
|
||||
revokedAt: Date | null
|
||||
ipAddress: string | null
|
||||
userAgent: string | null
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface ConsentPreference {
|
||||
purpose: ConsentPurpose
|
||||
granted: boolean
|
||||
lastUpdated: Date
|
||||
}
|
||||
|
||||
export interface ConsentState {
|
||||
userId: string
|
||||
preferences: ConsentPreference[]
|
||||
lastConsentDate: Date
|
||||
consentVersion: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COOKIE BANNER - Art. 7, 13/14 DSGVO
|
||||
// =============================================================================
|
||||
|
||||
export type CookieBannerStyle = 'BANNER' | 'MODAL' | 'FLOATING'
|
||||
|
||||
export type CookieBannerPosition = 'TOP' | 'BOTTOM' | 'CENTER'
|
||||
|
||||
export type CookieBannerTheme = 'LIGHT' | 'DARK' | 'CUSTOM'
|
||||
|
||||
export type CookieType = 'NECESSARY' | 'FUNCTIONAL' | 'ANALYTICS' | 'MARKETING'
|
||||
|
||||
export interface Cookie {
|
||||
id: string
|
||||
name: string
|
||||
provider: string
|
||||
purpose: string
|
||||
expiry: string
|
||||
type: CookieType
|
||||
}
|
||||
|
||||
export interface CookieCategory {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
required: boolean
|
||||
cookies: Cookie[]
|
||||
}
|
||||
|
||||
export interface CookieBannerTexts {
|
||||
title: string
|
||||
description: string
|
||||
acceptAll: string
|
||||
rejectAll: string
|
||||
settings: string
|
||||
save: string
|
||||
necessary: string
|
||||
functional: string
|
||||
analytics: string
|
||||
marketing: string
|
||||
}
|
||||
|
||||
export interface CookieBannerGeneratedCode {
|
||||
html: string
|
||||
css: string
|
||||
js: string
|
||||
}
|
||||
|
||||
export interface CookieBannerConfig {
|
||||
id: string
|
||||
style: CookieBannerStyle
|
||||
position: CookieBannerPosition
|
||||
theme: CookieBannerTheme
|
||||
customColors?: {
|
||||
primary: string
|
||||
secondary: string
|
||||
background: string
|
||||
text: string
|
||||
}
|
||||
texts: CookieBannerTexts
|
||||
categories: CookieCategory[]
|
||||
generatedCode: CookieBannerGeneratedCode | null
|
||||
logoUrl?: string
|
||||
privacyPolicyUrl: string
|
||||
imprintUrl: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// VVT (Verarbeitungsverzeichnis) - Art. 30 DSGVO
|
||||
// =============================================================================
|
||||
|
||||
export type LegalBasis =
|
||||
| 'CONSENT' // Art. 6(1)(a) - Einwilligung
|
||||
| 'CONTRACT' // Art. 6(1)(b) - Vertragserfüllung
|
||||
| 'LEGAL_OBLIGATION' // Art. 6(1)(c) - Rechtliche Verpflichtung
|
||||
| 'VITAL_INTERESTS' // Art. 6(1)(d) - Lebenswichtige Interessen
|
||||
| 'PUBLIC_INTEREST' // Art. 6(1)(e) - Öffentliches Interesse
|
||||
| 'LEGITIMATE_INTEREST' // Art. 6(1)(f) - Berechtigtes Interesse
|
||||
|
||||
export interface ProcessingActivity {
|
||||
id: string
|
||||
name: string
|
||||
purpose: string
|
||||
legalBasis: LegalBasis
|
||||
legalBasisJustification?: string
|
||||
dataCategories: string[]
|
||||
specialDataCategories?: string[] // Art. 9 Daten
|
||||
dataSubjects: string[]
|
||||
recipients: string[]
|
||||
thirdCountryTransfers: boolean
|
||||
transferSafeguards?: string // SCCs, BCRs, etc.
|
||||
retentionPeriod: string
|
||||
technicalMeasures: string[]
|
||||
organizationalMeasures: string[]
|
||||
responsiblePerson: string
|
||||
lastReviewDate: Date
|
||||
nextReviewDate: Date
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DSFA (Datenschutz-Folgenabschätzung) - Art. 35, 36 DSGVO
|
||||
// =============================================================================
|
||||
|
||||
export type DSFAStatus = 'DRAFT' | 'IN_REVIEW' | 'APPROVED' | 'REJECTED'
|
||||
|
||||
export interface DSFASection {
|
||||
id: string
|
||||
title: string
|
||||
content: string
|
||||
status: 'DRAFT' | 'COMPLETED'
|
||||
order: number
|
||||
}
|
||||
|
||||
export interface DSFAApproval {
|
||||
id: string
|
||||
approver: string
|
||||
role: string
|
||||
status: 'PENDING' | 'APPROVED' | 'REJECTED'
|
||||
comment: string | null
|
||||
approvedAt: Date | null
|
||||
}
|
||||
|
||||
export interface DSFARisk {
|
||||
id: string
|
||||
description: string
|
||||
likelihood: number
|
||||
severity: number
|
||||
riskScore: number
|
||||
mitigation: string
|
||||
residualRisk: number
|
||||
}
|
||||
|
||||
export interface DSFA {
|
||||
id: string
|
||||
name: string
|
||||
processingActivityId: string
|
||||
status: DSFAStatus
|
||||
version: number
|
||||
sections: DSFASection[]
|
||||
risks: DSFARisk[]
|
||||
approvals: DSFAApproval[]
|
||||
consultationRequired: boolean // Art. 36 consultation
|
||||
consultationDate?: Date
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TOM (Technische und Organisatorische Maßnahmen) - Art. 32 DSGVO
|
||||
// =============================================================================
|
||||
|
||||
export type TOMType = 'TECHNICAL' | 'ORGANIZATIONAL'
|
||||
|
||||
export type TOMCategory =
|
||||
| 'ACCESS_CONTROL' // Zutrittskontrolle
|
||||
| 'DATA_ACCESS_CONTROL' // Zugangskontrolle
|
||||
| 'USER_ACCESS_CONTROL' // Zugriffskontrolle
|
||||
| 'TRANSFER_CONTROL' // Weitergabekontrolle
|
||||
| 'INPUT_CONTROL' // Eingabekontrolle
|
||||
| 'JOB_CONTROL' // Auftragskontrolle
|
||||
| 'AVAILABILITY_CONTROL' // Verfügbarkeitskontrolle
|
||||
| 'SEPARATION_CONTROL' // Trennungskontrolle
|
||||
|
||||
export interface TOM {
|
||||
id: string
|
||||
category: TOMCategory
|
||||
name: string
|
||||
description: string
|
||||
type: TOMType
|
||||
implementationStatus: ImplementationStatus
|
||||
priority: RiskSeverity
|
||||
responsiblePerson: string | null
|
||||
implementationDate: Date | null
|
||||
reviewDate: Date | null
|
||||
evidence: string[]
|
||||
controls: string[] // Related control IDs
|
||||
}
|
||||
|
||||
export interface TOMGeneratorConfig {
|
||||
companySize: 'SMALL' | 'MEDIUM' | 'LARGE' | 'ENTERPRISE'
|
||||
industry: string
|
||||
dataCategories: string[]
|
||||
riskLevel: RiskSeverity
|
||||
existingMeasures: string[]
|
||||
}
|
||||
|
||||
export interface TOMGeneratorResult {
|
||||
recommendedTOMs: TOM[]
|
||||
score: number // 0-100%
|
||||
gaps: string[]
|
||||
recommendations: string[]
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RETENTION POLICIES - Art. 5, 17 DSGVO
|
||||
// =============================================================================
|
||||
|
||||
export interface RetentionPolicy {
|
||||
id: string
|
||||
dataCategory: string
|
||||
description: string
|
||||
legalBasis: string
|
||||
retentionPeriod: string // ISO 8601 Duration (e.g., "P3Y" for 3 years)
|
||||
retentionPeriodDays: number
|
||||
deletionMethod: 'ANONYMIZATION' | 'PSEUDONYMIZATION' | 'HARD_DELETE' | 'SOFT_DELETE'
|
||||
exceptions: string[]
|
||||
automatedDeletion: boolean
|
||||
lastReviewDate: Date
|
||||
nextReviewDate: Date
|
||||
}
|
||||
|
||||
export interface RetentionSchedule {
|
||||
id: string
|
||||
policyId: string
|
||||
nextExecutionDate: Date
|
||||
lastExecutionDate: Date | null
|
||||
status: 'SCHEDULED' | 'RUNNING' | 'COMPLETED' | 'FAILED'
|
||||
itemsProcessed?: number
|
||||
errors?: string[]
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ESCALATIONS - Art. 33/34 DSGVO (Data Breach)
|
||||
// =============================================================================
|
||||
|
||||
export type EscalationLevel = 'E0' | 'E1' | 'E2' | 'E3'
|
||||
|
||||
export interface EscalationStep {
|
||||
id: string
|
||||
order: number
|
||||
action: string
|
||||
assignee: string
|
||||
timeLimit: string // ISO 8601 Duration
|
||||
escalateOnTimeout: boolean
|
||||
notificationChannels: ('EMAIL' | 'SMS' | 'SLACK' | 'WEBHOOK')[]
|
||||
}
|
||||
|
||||
export interface EscalationWorkflow {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
level: EscalationLevel
|
||||
triggerConditions: string[]
|
||||
steps: EscalationStep[]
|
||||
enabled: boolean
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
export interface DataBreach {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
discoveredAt: Date
|
||||
reportedToAuthorityAt?: Date // Art. 33 - 72 hours
|
||||
reportedToDataSubjectsAt?: Date // Art. 34
|
||||
affectedDataSubjects: number
|
||||
dataCategories: string[]
|
||||
riskLevel: RiskSeverity
|
||||
mitigationMeasures: string[]
|
||||
status: 'DETECTED' | 'INVESTIGATING' | 'REPORTED' | 'RESOLVED'
|
||||
workflowId: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// LEGAL DOCUMENTS
|
||||
// =============================================================================
|
||||
|
||||
export type LegalDocumentType =
|
||||
| 'AGB'
|
||||
| 'PRIVACY_POLICY'
|
||||
| 'TERMS_OF_USE'
|
||||
| 'IMPRINT'
|
||||
| 'COOKIE_POLICY'
|
||||
| 'DATA_PROCESSING_AGREEMENT'
|
||||
|
||||
export interface LegalDocument {
|
||||
id: string
|
||||
type: LegalDocumentType
|
||||
title: string
|
||||
content: string
|
||||
version: string
|
||||
status: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED'
|
||||
language: 'de' | 'en'
|
||||
publishedAt: Date | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
changelog?: string
|
||||
}
|
||||
14
breakpilot-compliance-sdk/packages/types/src/index.ts
Normal file
14
breakpilot-compliance-sdk/packages/types/src/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @breakpilot/compliance-sdk-types
|
||||
*
|
||||
* Comprehensive TypeScript type definitions for BreakPilot Compliance SDK
|
||||
*/
|
||||
|
||||
// Re-export all modules
|
||||
export * from './base'
|
||||
export * from './dsgvo'
|
||||
export * from './compliance'
|
||||
export * from './rag'
|
||||
export * from './security'
|
||||
export * from './state'
|
||||
export * from './api'
|
||||
305
breakpilot-compliance-sdk/packages/types/src/rag.ts
Normal file
305
breakpilot-compliance-sdk/packages/types/src/rag.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* RAG (Retrieval-Augmented Generation) Module Types
|
||||
*
|
||||
* Type definitions for the Legal RAG System:
|
||||
* - 21 pre-indexed legal documents
|
||||
* - Semantic search with Qdrant
|
||||
* - LLM-powered legal assistant
|
||||
* - Customer document upload
|
||||
*/
|
||||
|
||||
import type { RegulationCode } from './compliance'
|
||||
|
||||
// =============================================================================
|
||||
// LEGAL CORPUS
|
||||
// =============================================================================
|
||||
|
||||
export interface LegalDocument {
|
||||
id: string
|
||||
code: RegulationCode | string
|
||||
name: string
|
||||
fullName: string
|
||||
jurisdiction: 'EU' | 'DE' | 'INT'
|
||||
language: 'de' | 'en'
|
||||
effectiveDate: Date
|
||||
sourceUrl?: string
|
||||
chunks: number
|
||||
lastIndexed: Date
|
||||
}
|
||||
|
||||
export interface LegalChunk {
|
||||
id: string
|
||||
documentId: string
|
||||
documentCode: string
|
||||
chunkIndex: number
|
||||
content: string
|
||||
article?: string
|
||||
section?: string
|
||||
metadata: ChunkMetadata
|
||||
embedding?: number[]
|
||||
}
|
||||
|
||||
export interface ChunkMetadata {
|
||||
articleNumber?: string
|
||||
sectionTitle?: string
|
||||
paragraph?: number
|
||||
keywords: string[]
|
||||
references: string[]
|
||||
effectiveDate?: Date
|
||||
}
|
||||
|
||||
// Pre-indexed documents in the Legal Corpus
|
||||
export const LEGAL_CORPUS_DOCUMENTS: readonly string[] = [
|
||||
'DSGVO', // 99 Chunks
|
||||
'AI_ACT', // 85 Chunks
|
||||
'NIS2', // 46 Chunks
|
||||
'EPRIVACY',
|
||||
'TDDDG',
|
||||
'SCC', // Standard Contractual Clauses
|
||||
'DPF', // Data Privacy Framework
|
||||
'CRA', // Cyber Resilience Act
|
||||
'EUCSA', // EU Cybersecurity Act
|
||||
'DATA_ACT',
|
||||
'DGA', // Data Governance Act
|
||||
'DSA', // Digital Services Act
|
||||
'EAA', // European Accessibility Act
|
||||
'BDSG', // German BDSG
|
||||
'ISO_27001',
|
||||
'BSI_GRUNDSCHUTZ',
|
||||
'KRITIS',
|
||||
'BAIT', // Banking IT requirements
|
||||
'VAIT', // Insurance IT requirements
|
||||
'SOC2',
|
||||
'PCI_DSS',
|
||||
] as const
|
||||
|
||||
// =============================================================================
|
||||
// SEARCH & RETRIEVAL
|
||||
// =============================================================================
|
||||
|
||||
export interface SearchQuery {
|
||||
query: string
|
||||
filters?: SearchFilters
|
||||
limit?: number
|
||||
offset?: number
|
||||
includeMetadata?: boolean
|
||||
scoreThreshold?: number
|
||||
}
|
||||
|
||||
export interface SearchFilters {
|
||||
documentCodes?: string[]
|
||||
jurisdictions?: ('EU' | 'DE' | 'INT')[]
|
||||
languages?: ('de' | 'en')[]
|
||||
articles?: string[]
|
||||
dateFrom?: Date
|
||||
dateTo?: Date
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
id: string
|
||||
documentCode: string
|
||||
documentName: string
|
||||
content: string
|
||||
article?: string
|
||||
section?: string
|
||||
score: number
|
||||
highlights: string[]
|
||||
metadata: ChunkMetadata
|
||||
}
|
||||
|
||||
export interface SearchResponse {
|
||||
results: SearchResult[]
|
||||
total: number
|
||||
query: string
|
||||
processingTimeMs: number
|
||||
filters?: SearchFilters
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// LEGAL ASSISTANT
|
||||
// =============================================================================
|
||||
|
||||
export interface AssistantQuery {
|
||||
question: string
|
||||
context?: string
|
||||
documents?: string[]
|
||||
maxSources?: number
|
||||
language?: 'de' | 'en'
|
||||
responseFormat?: 'detailed' | 'concise' | 'bullet_points'
|
||||
}
|
||||
|
||||
export interface AssistantResponse {
|
||||
answer: string
|
||||
sources: SourceReference[]
|
||||
confidence: number // 0-1
|
||||
processingTimeMs: number
|
||||
suggestedFollowUps?: string[]
|
||||
}
|
||||
|
||||
export interface SourceReference {
|
||||
documentCode: string
|
||||
documentName: string
|
||||
article?: string
|
||||
content: string
|
||||
relevanceScore: number
|
||||
url?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CUSTOMER DOCUMENTS
|
||||
// =============================================================================
|
||||
|
||||
export type CustomerDocumentType =
|
||||
| 'POLICY'
|
||||
| 'GUIDELINE'
|
||||
| 'CONTRACT'
|
||||
| 'SLA'
|
||||
| 'PROCEDURE'
|
||||
| 'RISK_ASSESSMENT'
|
||||
| 'AUDIT_REPORT'
|
||||
| 'OTHER'
|
||||
|
||||
export type IndexingStatus =
|
||||
| 'PENDING'
|
||||
| 'PROCESSING'
|
||||
| 'INDEXED'
|
||||
| 'FAILED'
|
||||
| 'OUTDATED'
|
||||
|
||||
export interface CustomerDocument {
|
||||
id: string
|
||||
tenantId: string
|
||||
name: string
|
||||
fileName: string
|
||||
type: CustomerDocumentType
|
||||
mimeType: string
|
||||
fileSize: number
|
||||
uploadedBy: string
|
||||
uploadedAt: Date
|
||||
indexingStatus: IndexingStatus
|
||||
indexedAt?: Date
|
||||
chunkCount?: number
|
||||
error?: string
|
||||
tags: string[]
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface DocumentUploadRequest {
|
||||
name: string
|
||||
type: CustomerDocumentType
|
||||
tags?: string[]
|
||||
metadata?: Record<string, unknown>
|
||||
file: File | Blob
|
||||
}
|
||||
|
||||
export interface DocumentUploadResponse {
|
||||
id: string
|
||||
status: IndexingStatus
|
||||
message: string
|
||||
estimatedProcessingTimeMs?: number
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// VECTOR DATABASE
|
||||
// =============================================================================
|
||||
|
||||
export interface VectorDBConfig {
|
||||
provider: 'qdrant' | 'pinecone' | 'weaviate'
|
||||
url: string
|
||||
apiKey?: string
|
||||
collection: string
|
||||
embeddingModel: string
|
||||
embeddingDimensions: number
|
||||
}
|
||||
|
||||
export interface EmbeddingRequest {
|
||||
texts: string[]
|
||||
model?: string
|
||||
}
|
||||
|
||||
export interface EmbeddingResponse {
|
||||
embeddings: number[][]
|
||||
model: string
|
||||
usage: {
|
||||
promptTokens: number
|
||||
totalTokens: number
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RAG PIPELINE
|
||||
// =============================================================================
|
||||
|
||||
export interface RAGPipelineConfig {
|
||||
retriever: {
|
||||
topK: number
|
||||
scoreThreshold: number
|
||||
reranking: boolean
|
||||
hybridSearch: boolean
|
||||
}
|
||||
generator: {
|
||||
model: string
|
||||
temperature: number
|
||||
maxTokens: number
|
||||
systemPrompt?: string
|
||||
}
|
||||
postProcessing: {
|
||||
citationFormat: 'inline' | 'footnote' | 'none'
|
||||
sourceVerification: boolean
|
||||
hallucizationCheck: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface RAGContext {
|
||||
query: string
|
||||
retrievedChunks: LegalChunk[]
|
||||
rerankedChunks?: LegalChunk[]
|
||||
generatedResponse?: string
|
||||
citations: Citation[]
|
||||
}
|
||||
|
||||
export interface Citation {
|
||||
id: string
|
||||
chunkId: string
|
||||
documentCode: string
|
||||
article?: string
|
||||
quotedText: string
|
||||
confidence: number
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CHAT HISTORY
|
||||
// =============================================================================
|
||||
|
||||
export interface ChatMessage {
|
||||
id: string
|
||||
role: 'user' | 'assistant' | 'system'
|
||||
content: string
|
||||
timestamp: Date
|
||||
sources?: SourceReference[]
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface ChatSession {
|
||||
id: string
|
||||
tenantId: string
|
||||
userId: string
|
||||
messages: ChatMessage[]
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
title?: string
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ANALYTICS
|
||||
// =============================================================================
|
||||
|
||||
export interface RAGAnalytics {
|
||||
totalQueries: number
|
||||
averageResponseTimeMs: number
|
||||
topDocuments: Array<{ code: string; queryCount: number }>
|
||||
topQuestions: Array<{ question: string; count: number }>
|
||||
satisfactionScore?: number
|
||||
period: 'day' | 'week' | 'month'
|
||||
}
|
||||
381
breakpilot-compliance-sdk/packages/types/src/security.ts
Normal file
381
breakpilot-compliance-sdk/packages/types/src/security.ts
Normal file
@@ -0,0 +1,381 @@
|
||||
/**
|
||||
* Security Module Types
|
||||
*
|
||||
* Type definitions for security scanning:
|
||||
* - SBOM (Software Bill of Materials)
|
||||
* - Vulnerability scanning with 6 integrated tools
|
||||
* - Findings management
|
||||
* - CVE tracking
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// SBOM (Software Bill of Materials)
|
||||
// =============================================================================
|
||||
|
||||
export type SBOMFormat = 'CycloneDX' | 'SPDX'
|
||||
|
||||
export type ComponentType =
|
||||
| 'library'
|
||||
| 'framework'
|
||||
| 'application'
|
||||
| 'container'
|
||||
| 'operating-system'
|
||||
| 'device'
|
||||
| 'file'
|
||||
|
||||
export type LicenseType =
|
||||
| 'MIT'
|
||||
| 'Apache-2.0'
|
||||
| 'GPL-2.0'
|
||||
| 'GPL-3.0'
|
||||
| 'BSD-2-Clause'
|
||||
| 'BSD-3-Clause'
|
||||
| 'ISC'
|
||||
| 'MPL-2.0'
|
||||
| 'LGPL-2.1'
|
||||
| 'LGPL-3.0'
|
||||
| 'AGPL-3.0'
|
||||
| 'Proprietary'
|
||||
| 'Unknown'
|
||||
|
||||
export interface SBOMComponent {
|
||||
name: string
|
||||
version: string
|
||||
type: ComponentType
|
||||
purl: string // Package URL
|
||||
licenses: LicenseType[]
|
||||
supplier?: string
|
||||
author?: string
|
||||
description?: string
|
||||
hashes?: ComponentHash[]
|
||||
vulnerabilities: Vulnerability[]
|
||||
dependencies?: string[] // PURLs of dependencies
|
||||
}
|
||||
|
||||
export interface ComponentHash {
|
||||
algorithm: 'SHA-256' | 'SHA-384' | 'SHA-512' | 'MD5'
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface SBOMDependency {
|
||||
from: string // PURL
|
||||
to: string // PURL
|
||||
}
|
||||
|
||||
export interface SBOM {
|
||||
format: SBOMFormat
|
||||
specVersion: string
|
||||
serialNumber?: string
|
||||
version: number
|
||||
metadata: SBOMMetadata
|
||||
components: SBOMComponent[]
|
||||
dependencies: SBOMDependency[]
|
||||
generatedAt: Date
|
||||
}
|
||||
|
||||
export interface SBOMMetadata {
|
||||
timestamp: Date
|
||||
tools: SBOMTool[]
|
||||
component?: SBOMComponent // The component this SBOM describes
|
||||
authors?: string[]
|
||||
}
|
||||
|
||||
export interface SBOMTool {
|
||||
vendor: string
|
||||
name: string
|
||||
version: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// VULNERABILITIES
|
||||
// =============================================================================
|
||||
|
||||
export type SecurityIssueSeverity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'INFO'
|
||||
|
||||
export type SecurityIssueStatus = 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'ACCEPTED' | 'FALSE_POSITIVE'
|
||||
|
||||
export interface Vulnerability {
|
||||
id: string
|
||||
cve: string
|
||||
severity: SecurityIssueSeverity
|
||||
title: string
|
||||
description: string
|
||||
cvss: CVSSScore | null
|
||||
cwe?: string
|
||||
fixedIn: string | null
|
||||
publishedAt?: Date
|
||||
references: string[]
|
||||
}
|
||||
|
||||
export interface CVSSScore {
|
||||
version: '2.0' | '3.0' | '3.1'
|
||||
score: number
|
||||
vector: string
|
||||
severity: SecurityIssueSeverity
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SECURITY SCANNING
|
||||
// =============================================================================
|
||||
|
||||
export type SecurityTool =
|
||||
| 'gitleaks' // Secrets detection
|
||||
| 'semgrep' // SAST
|
||||
| 'bandit' // Python security
|
||||
| 'trivy' // Container/filesystem scanning
|
||||
| 'grype' // Vulnerability scanner
|
||||
| 'syft' // SBOM generation
|
||||
|
||||
export type ScreeningStatus = 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED'
|
||||
|
||||
export interface SecurityScanConfig {
|
||||
tools: SecurityTool[]
|
||||
targetPath: string
|
||||
excludePaths?: string[]
|
||||
severityThreshold?: SecurityIssueSeverity
|
||||
failOnFindings?: boolean
|
||||
generateSBOM?: boolean
|
||||
}
|
||||
|
||||
export interface SecurityScanResult {
|
||||
id: string
|
||||
status: ScreeningStatus
|
||||
startedAt: Date
|
||||
completedAt: Date | null
|
||||
duration?: number // ms
|
||||
totalIssues: number
|
||||
critical: number
|
||||
high: number
|
||||
medium: number
|
||||
low: number
|
||||
info: number
|
||||
issues: SecurityIssue[]
|
||||
toolResults: ToolResult[]
|
||||
sbom?: SBOM
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface ToolResult {
|
||||
tool: SecurityTool
|
||||
status: 'SUCCESS' | 'FAILED' | 'SKIPPED'
|
||||
issuesFound: number
|
||||
executionTimeMs: number
|
||||
version: string
|
||||
rawOutput?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface SecurityIssue {
|
||||
id: string
|
||||
tool: SecurityTool
|
||||
severity: SecurityIssueSeverity
|
||||
title: string
|
||||
description: string
|
||||
cve: string | null
|
||||
cvss: CVSSScore | null
|
||||
cwe?: string
|
||||
affectedComponent: string
|
||||
affectedFile?: string
|
||||
affectedLine?: number
|
||||
remediation: string
|
||||
status: SecurityIssueStatus
|
||||
assignee?: string
|
||||
dueDate?: Date
|
||||
resolvedAt?: Date
|
||||
resolvedBy?: string
|
||||
notes?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FINDINGS MANAGEMENT
|
||||
// =============================================================================
|
||||
|
||||
export interface FindingsFilter {
|
||||
tools?: SecurityTool[]
|
||||
severities?: SecurityIssueSeverity[]
|
||||
statuses?: SecurityIssueStatus[]
|
||||
cveIds?: string[]
|
||||
components?: string[]
|
||||
dateFrom?: Date
|
||||
dateTo?: Date
|
||||
}
|
||||
|
||||
export interface FindingsSummary {
|
||||
totalFindings: number
|
||||
bySeverity: Record<SecurityIssueSeverity, number>
|
||||
byStatus: Record<SecurityIssueStatus, number>
|
||||
byTool: Record<SecurityTool, number>
|
||||
averageResolutionDays: number
|
||||
oldestUnresolvedDays: number
|
||||
}
|
||||
|
||||
export interface FindingsReport {
|
||||
generatedAt: Date
|
||||
period: {
|
||||
from: Date
|
||||
to: Date
|
||||
}
|
||||
summary: FindingsSummary
|
||||
newFindings: SecurityIssue[]
|
||||
resolvedFindings: SecurityIssue[]
|
||||
trends: FindingsTrend[]
|
||||
}
|
||||
|
||||
export interface FindingsTrend {
|
||||
date: Date
|
||||
totalOpen: number
|
||||
totalResolved: number
|
||||
newFindings: number
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SECURITY BACKLOG
|
||||
// =============================================================================
|
||||
|
||||
export interface BacklogItem {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
severity: SecurityIssueSeverity
|
||||
securityIssueId: string
|
||||
status: 'OPEN' | 'IN_PROGRESS' | 'DONE'
|
||||
priority: number // 1-5
|
||||
assignee: string | null
|
||||
dueDate: Date | null
|
||||
estimatedHours?: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
labels: string[]
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SECRETS DETECTION (Gitleaks)
|
||||
// =============================================================================
|
||||
|
||||
export interface SecretFinding {
|
||||
id: string
|
||||
ruleId: string
|
||||
description: string
|
||||
match: string
|
||||
secret: string // Masked
|
||||
file: string
|
||||
startLine: number
|
||||
endLine: number
|
||||
commit?: string
|
||||
author?: string
|
||||
date?: Date
|
||||
entropy?: number
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SAST (Semgrep)
|
||||
// =============================================================================
|
||||
|
||||
export interface SASTFinding {
|
||||
id: string
|
||||
ruleId: string
|
||||
message: string
|
||||
severity: SecurityIssueSeverity
|
||||
file: string
|
||||
startLine: number
|
||||
endLine: number
|
||||
startCol: number
|
||||
endCol: number
|
||||
codeSnippet: string
|
||||
fix?: SASTFix
|
||||
metadata: {
|
||||
category: string
|
||||
technology: string[]
|
||||
cwe?: string
|
||||
owasp?: string
|
||||
confidence: 'HIGH' | 'MEDIUM' | 'LOW'
|
||||
}
|
||||
}
|
||||
|
||||
export interface SASTFix {
|
||||
fix: string
|
||||
regex_replace?: {
|
||||
regex: string
|
||||
replacement: string
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONTAINER SCANNING (Trivy)
|
||||
// =============================================================================
|
||||
|
||||
export interface ContainerScanResult {
|
||||
target: string
|
||||
type: 'image' | 'filesystem' | 'repository'
|
||||
vulnerabilities: ContainerVulnerability[]
|
||||
misconfigurations?: ContainerMisconfiguration[]
|
||||
secrets?: SecretFinding[]
|
||||
}
|
||||
|
||||
export interface ContainerVulnerability extends Vulnerability {
|
||||
pkgName: string
|
||||
installedVersion: string
|
||||
fixedVersion?: string
|
||||
layer?: {
|
||||
digest: string
|
||||
diffId: string
|
||||
createdBy?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContainerMisconfiguration {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
severity: SecurityIssueSeverity
|
||||
resolution: string
|
||||
type: string
|
||||
resource: string
|
||||
cause: {
|
||||
provider: string
|
||||
service: string
|
||||
startLine: number
|
||||
endLine: number
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CI/CD INTEGRATION
|
||||
// =============================================================================
|
||||
|
||||
export interface CICDIntegrationConfig {
|
||||
provider: 'github' | 'gitlab' | 'jenkins' | 'azure_devops' | 'circleci'
|
||||
webhookUrl?: string
|
||||
apiToken?: string
|
||||
scanOnPush: boolean
|
||||
scanOnPR: boolean
|
||||
failPipelineOnCritical: boolean
|
||||
failPipelineOnHigh: boolean
|
||||
notifyOnNewFindings: boolean
|
||||
notificationChannels: string[]
|
||||
}
|
||||
|
||||
export interface CICDScanTrigger {
|
||||
id: string
|
||||
provider: string
|
||||
repository: string
|
||||
branch: string
|
||||
commit: string
|
||||
triggeredBy: 'push' | 'pull_request' | 'schedule' | 'manual'
|
||||
triggeredAt: Date
|
||||
scanResultId?: string
|
||||
status: 'QUEUED' | 'RUNNING' | 'COMPLETED' | 'FAILED'
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COMPLIANCE MAPPING
|
||||
// =============================================================================
|
||||
|
||||
export interface SecurityComplianceMapping {
|
||||
issueId: string
|
||||
regulation: string
|
||||
requirement: string
|
||||
article: string
|
||||
impact: string
|
||||
remediation: string
|
||||
}
|
||||
505
breakpilot-compliance-sdk/packages/types/src/state.ts
Normal file
505
breakpilot-compliance-sdk/packages/types/src/state.ts
Normal file
@@ -0,0 +1,505 @@
|
||||
/**
|
||||
* SDK State Types
|
||||
*
|
||||
* Type definitions for the SDK's state management system
|
||||
*/
|
||||
|
||||
import type {
|
||||
SubscriptionTier,
|
||||
UserPreferences,
|
||||
Risk,
|
||||
CommandHistory,
|
||||
CheckpointStatus,
|
||||
} from './base'
|
||||
import type {
|
||||
ConsentRecord,
|
||||
DSRConfig,
|
||||
DSRRequest,
|
||||
CookieBannerConfig,
|
||||
ProcessingActivity,
|
||||
DSFA,
|
||||
TOM,
|
||||
RetentionPolicy,
|
||||
EscalationWorkflow,
|
||||
LegalDocument,
|
||||
} from './dsgvo'
|
||||
import type {
|
||||
Control,
|
||||
Evidence,
|
||||
Requirement,
|
||||
ServiceModule,
|
||||
Obligation,
|
||||
AIActResult,
|
||||
ChecklistItem,
|
||||
} from './compliance'
|
||||
import type { SBOM, SecurityIssue, BacklogItem, SecurityScanResult } from './security'
|
||||
|
||||
// =============================================================================
|
||||
// SDK FLOW & NAVIGATION
|
||||
// =============================================================================
|
||||
|
||||
export type SDKPhase = 1 | 2
|
||||
|
||||
export interface SDKStep {
|
||||
id: string
|
||||
phase: SDKPhase
|
||||
order: number
|
||||
name: string
|
||||
nameShort: string
|
||||
description: string
|
||||
url: string
|
||||
checkpointId: string
|
||||
prerequisiteSteps: string[]
|
||||
isOptional: boolean
|
||||
}
|
||||
|
||||
export const SDK_STEPS: SDKStep[] = [
|
||||
// Phase 1: Automatisches Compliance Assessment
|
||||
{
|
||||
id: 'use-case-workshop',
|
||||
phase: 1,
|
||||
order: 1,
|
||||
name: 'Use Case Workshop',
|
||||
nameShort: 'Use Case',
|
||||
description: '5-Schritte-Wizard für Use Case Erfassung',
|
||||
url: '/sdk/advisory-board',
|
||||
checkpointId: 'CP-UC',
|
||||
prerequisiteSteps: [],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'screening',
|
||||
phase: 1,
|
||||
order: 2,
|
||||
name: 'System Screening',
|
||||
nameShort: 'Screening',
|
||||
description: 'SBOM + Security Check',
|
||||
url: '/sdk/screening',
|
||||
checkpointId: 'CP-SCAN',
|
||||
prerequisiteSteps: ['use-case-workshop'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'modules',
|
||||
phase: 1,
|
||||
order: 3,
|
||||
name: 'Compliance Modules',
|
||||
nameShort: 'Module',
|
||||
description: 'Abgleich welche Regulierungen gelten',
|
||||
url: '/sdk/modules',
|
||||
checkpointId: 'CP-MOD',
|
||||
prerequisiteSteps: ['screening'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'requirements',
|
||||
phase: 1,
|
||||
order: 4,
|
||||
name: 'Requirements',
|
||||
nameShort: 'Anforderungen',
|
||||
description: 'Prüfaspekte aus Regulierungen ableiten',
|
||||
url: '/sdk/requirements',
|
||||
checkpointId: 'CP-REQ',
|
||||
prerequisiteSteps: ['modules'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'controls',
|
||||
phase: 1,
|
||||
order: 5,
|
||||
name: 'Controls',
|
||||
nameShort: 'Controls',
|
||||
description: 'Erforderliche Maßnahmen ermitteln',
|
||||
url: '/sdk/controls',
|
||||
checkpointId: 'CP-CTRL',
|
||||
prerequisiteSteps: ['requirements'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'evidence',
|
||||
phase: 1,
|
||||
order: 6,
|
||||
name: 'Evidence',
|
||||
nameShort: 'Nachweise',
|
||||
description: 'Nachweise dokumentieren',
|
||||
url: '/sdk/evidence',
|
||||
checkpointId: 'CP-EVI',
|
||||
prerequisiteSteps: ['controls'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'audit-checklist',
|
||||
phase: 1,
|
||||
order: 7,
|
||||
name: 'Audit Checklist',
|
||||
nameShort: 'Checklist',
|
||||
description: 'Prüfliste generieren',
|
||||
url: '/sdk/audit-checklist',
|
||||
checkpointId: 'CP-CHK',
|
||||
prerequisiteSteps: ['evidence'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'risks',
|
||||
phase: 1,
|
||||
order: 8,
|
||||
name: 'Risk Matrix',
|
||||
nameShort: 'Risiken',
|
||||
description: 'Risikobewertung & Residual Risk',
|
||||
url: '/sdk/risks',
|
||||
checkpointId: 'CP-RISK',
|
||||
prerequisiteSteps: ['audit-checklist'],
|
||||
isOptional: false,
|
||||
},
|
||||
// Phase 2: Dokumentengenerierung
|
||||
{
|
||||
id: 'ai-act',
|
||||
phase: 2,
|
||||
order: 1,
|
||||
name: 'AI Act Klassifizierung',
|
||||
nameShort: 'AI Act',
|
||||
description: 'Risikostufe nach EU AI Act',
|
||||
url: '/sdk/ai-act',
|
||||
checkpointId: 'CP-AI',
|
||||
prerequisiteSteps: ['risks'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'obligations',
|
||||
phase: 2,
|
||||
order: 2,
|
||||
name: 'Pflichtenübersicht',
|
||||
nameShort: 'Pflichten',
|
||||
description: 'NIS2, DSGVO, AI Act Pflichten',
|
||||
url: '/sdk/obligations',
|
||||
checkpointId: 'CP-OBL',
|
||||
prerequisiteSteps: ['ai-act'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'dsfa',
|
||||
phase: 2,
|
||||
order: 3,
|
||||
name: 'DSFA',
|
||||
nameShort: 'DSFA',
|
||||
description: 'Datenschutz-Folgenabschätzung',
|
||||
url: '/sdk/dsfa',
|
||||
checkpointId: 'CP-DSFA',
|
||||
prerequisiteSteps: ['obligations'],
|
||||
isOptional: true,
|
||||
},
|
||||
{
|
||||
id: 'tom',
|
||||
phase: 2,
|
||||
order: 4,
|
||||
name: 'TOMs',
|
||||
nameShort: 'TOMs',
|
||||
description: 'Technische & Org. Maßnahmen',
|
||||
url: '/sdk/tom',
|
||||
checkpointId: 'CP-TOM',
|
||||
prerequisiteSteps: ['dsfa'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'loeschfristen',
|
||||
phase: 2,
|
||||
order: 5,
|
||||
name: 'Löschfristen',
|
||||
nameShort: 'Löschfristen',
|
||||
description: 'Aufbewahrungsrichtlinien',
|
||||
url: '/sdk/loeschfristen',
|
||||
checkpointId: 'CP-RET',
|
||||
prerequisiteSteps: ['tom'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'vvt',
|
||||
phase: 2,
|
||||
order: 6,
|
||||
name: 'Verarbeitungsverzeichnis',
|
||||
nameShort: 'VVT',
|
||||
description: 'Art. 30 DSGVO Dokumentation',
|
||||
url: '/sdk/vvt',
|
||||
checkpointId: 'CP-VVT',
|
||||
prerequisiteSteps: ['loeschfristen'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'consent',
|
||||
phase: 2,
|
||||
order: 7,
|
||||
name: 'Rechtliche Vorlagen',
|
||||
nameShort: 'Vorlagen',
|
||||
description: 'AGB, Datenschutz, Nutzungsbedingungen',
|
||||
url: '/sdk/consent',
|
||||
checkpointId: 'CP-DOC',
|
||||
prerequisiteSteps: ['vvt'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'cookie-banner',
|
||||
phase: 2,
|
||||
order: 8,
|
||||
name: 'Cookie Banner',
|
||||
nameShort: 'Cookies',
|
||||
description: 'Cookie-Consent Generator',
|
||||
url: '/sdk/cookie-banner',
|
||||
checkpointId: 'CP-COOK',
|
||||
prerequisiteSteps: ['consent'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'einwilligungen',
|
||||
phase: 2,
|
||||
order: 9,
|
||||
name: 'Einwilligungen',
|
||||
nameShort: 'Consents',
|
||||
description: 'Consent-Tracking Setup',
|
||||
url: '/sdk/einwilligungen',
|
||||
checkpointId: 'CP-CONS',
|
||||
prerequisiteSteps: ['cookie-banner'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'dsr',
|
||||
phase: 2,
|
||||
order: 10,
|
||||
name: 'DSR Portal',
|
||||
nameShort: 'DSR',
|
||||
description: 'Betroffenenrechte-Portal',
|
||||
url: '/sdk/dsr',
|
||||
checkpointId: 'CP-DSR',
|
||||
prerequisiteSteps: ['einwilligungen'],
|
||||
isOptional: false,
|
||||
},
|
||||
{
|
||||
id: 'escalations',
|
||||
phase: 2,
|
||||
order: 11,
|
||||
name: 'Escalations',
|
||||
nameShort: 'Eskalationen',
|
||||
description: 'Management-Workflows',
|
||||
url: '/sdk/escalations',
|
||||
checkpointId: 'CP-ESC',
|
||||
prerequisiteSteps: ['dsr'],
|
||||
isOptional: false,
|
||||
},
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// USE CASE ASSESSMENT
|
||||
// =============================================================================
|
||||
|
||||
export interface UseCaseStep {
|
||||
id: string
|
||||
name: string
|
||||
completed: boolean
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface AssessmentResult {
|
||||
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
|
||||
applicableRegulations: string[]
|
||||
recommendedControls: string[]
|
||||
dsfaRequired: boolean
|
||||
aiActClassification: string
|
||||
}
|
||||
|
||||
export interface UseCaseAssessment {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
category: string
|
||||
stepsCompleted: number
|
||||
steps: UseCaseStep[]
|
||||
assessmentResult: AssessmentResult | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SCREENING RESULT
|
||||
// =============================================================================
|
||||
|
||||
export interface ScreeningResult {
|
||||
id: string
|
||||
status: 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED'
|
||||
startedAt: Date
|
||||
completedAt: Date | null
|
||||
sbom: SBOM | null
|
||||
securityScan: SecurityScanResult | null
|
||||
error: string | null
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SDK STATE
|
||||
// =============================================================================
|
||||
|
||||
export interface SDKState {
|
||||
// Metadata
|
||||
version: string
|
||||
lastModified: Date
|
||||
|
||||
// Tenant & User
|
||||
tenantId: string
|
||||
userId: string
|
||||
subscription: SubscriptionTier
|
||||
|
||||
// Progress
|
||||
currentPhase: SDKPhase
|
||||
currentStep: string
|
||||
completedSteps: string[]
|
||||
checkpoints: Record<string, CheckpointStatus>
|
||||
|
||||
// Phase 1 Data
|
||||
useCases: UseCaseAssessment[]
|
||||
activeUseCase: string | null
|
||||
screening: ScreeningResult | null
|
||||
modules: ServiceModule[]
|
||||
requirements: Requirement[]
|
||||
controls: Control[]
|
||||
evidence: Evidence[]
|
||||
checklist: ChecklistItem[]
|
||||
risks: Risk[]
|
||||
|
||||
// Phase 2 Data
|
||||
aiActClassification: AIActResult | null
|
||||
obligations: Obligation[]
|
||||
dsfa: DSFA | null
|
||||
toms: TOM[]
|
||||
retentionPolicies: RetentionPolicy[]
|
||||
vvt: ProcessingActivity[]
|
||||
documents: LegalDocument[]
|
||||
cookieBanner: CookieBannerConfig | null
|
||||
consents: ConsentRecord[]
|
||||
dsrConfig: DSRConfig | null
|
||||
dsrRequests: DSRRequest[]
|
||||
escalationWorkflows: EscalationWorkflow[]
|
||||
|
||||
// Security
|
||||
sbom: SBOM | null
|
||||
securityIssues: SecurityIssue[]
|
||||
securityBacklog: BacklogItem[]
|
||||
|
||||
// UI State
|
||||
commandBarHistory: CommandHistory[]
|
||||
recentSearches: string[]
|
||||
preferences: UserPreferences
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SDK ACTIONS
|
||||
// =============================================================================
|
||||
|
||||
export type SDKAction =
|
||||
| { type: 'SET_STATE'; payload: Partial<SDKState> }
|
||||
| { type: 'SET_CURRENT_STEP'; payload: string }
|
||||
| { type: 'COMPLETE_STEP'; payload: string }
|
||||
| { type: 'SET_CHECKPOINT_STATUS'; payload: { id: string; status: CheckpointStatus } }
|
||||
| { type: 'ADD_USE_CASE'; payload: UseCaseAssessment }
|
||||
| { type: 'UPDATE_USE_CASE'; payload: { id: string; data: Partial<UseCaseAssessment> } }
|
||||
| { type: 'DELETE_USE_CASE'; payload: string }
|
||||
| { type: 'SET_ACTIVE_USE_CASE'; payload: string | null }
|
||||
| { type: 'SET_SCREENING'; payload: ScreeningResult }
|
||||
| { type: 'ADD_MODULE'; payload: ServiceModule }
|
||||
| { type: 'UPDATE_MODULE'; payload: { id: string; data: Partial<ServiceModule> } }
|
||||
| { type: 'ADD_REQUIREMENT'; payload: Requirement }
|
||||
| { type: 'UPDATE_REQUIREMENT'; payload: { id: string; data: Partial<Requirement> } }
|
||||
| { type: 'ADD_CONTROL'; payload: Control }
|
||||
| { type: 'UPDATE_CONTROL'; payload: { id: string; data: Partial<Control> } }
|
||||
| { type: 'ADD_EVIDENCE'; payload: Evidence }
|
||||
| { type: 'UPDATE_EVIDENCE'; payload: { id: string; data: Partial<Evidence> } }
|
||||
| { type: 'DELETE_EVIDENCE'; payload: string }
|
||||
| { type: 'ADD_RISK'; payload: Risk }
|
||||
| { type: 'UPDATE_RISK'; payload: { id: string; data: Partial<Risk> } }
|
||||
| { type: 'DELETE_RISK'; payload: string }
|
||||
| { type: 'SET_AI_ACT_RESULT'; payload: AIActResult }
|
||||
| { type: 'ADD_OBLIGATION'; payload: Obligation }
|
||||
| { type: 'UPDATE_OBLIGATION'; payload: { id: string; data: Partial<Obligation> } }
|
||||
| { type: 'SET_DSFA'; payload: DSFA }
|
||||
| { type: 'ADD_TOM'; payload: TOM }
|
||||
| { type: 'UPDATE_TOM'; payload: { id: string; data: Partial<TOM> } }
|
||||
| { type: 'ADD_RETENTION_POLICY'; payload: RetentionPolicy }
|
||||
| { type: 'UPDATE_RETENTION_POLICY'; payload: { id: string; data: Partial<RetentionPolicy> } }
|
||||
| { type: 'ADD_PROCESSING_ACTIVITY'; payload: ProcessingActivity }
|
||||
| { type: 'UPDATE_PROCESSING_ACTIVITY'; payload: { id: string; data: Partial<ProcessingActivity> } }
|
||||
| { type: 'ADD_DOCUMENT'; payload: LegalDocument }
|
||||
| { type: 'UPDATE_DOCUMENT'; payload: { id: string; data: Partial<LegalDocument> } }
|
||||
| { type: 'SET_COOKIE_BANNER'; payload: CookieBannerConfig }
|
||||
| { type: 'SET_DSR_CONFIG'; payload: DSRConfig }
|
||||
| { type: 'ADD_DSR_REQUEST'; payload: DSRRequest }
|
||||
| { type: 'UPDATE_DSR_REQUEST'; payload: { id: string; data: Partial<DSRRequest> } }
|
||||
| { type: 'ADD_ESCALATION_WORKFLOW'; payload: EscalationWorkflow }
|
||||
| { type: 'UPDATE_ESCALATION_WORKFLOW'; payload: { id: string; data: Partial<EscalationWorkflow> } }
|
||||
| { type: 'ADD_SECURITY_ISSUE'; payload: SecurityIssue }
|
||||
| { type: 'UPDATE_SECURITY_ISSUE'; payload: { id: string; data: Partial<SecurityIssue> } }
|
||||
| { type: 'ADD_BACKLOG_ITEM'; payload: BacklogItem }
|
||||
| { type: 'UPDATE_BACKLOG_ITEM'; payload: { id: string; data: Partial<BacklogItem> } }
|
||||
| { type: 'ADD_COMMAND_HISTORY'; payload: CommandHistory }
|
||||
| { type: 'SET_PREFERENCES'; payload: Partial<UserPreferences> }
|
||||
| { type: 'RESET_STATE' }
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
export function getStepById(stepId: string): SDKStep | undefined {
|
||||
return SDK_STEPS.find(s => s.id === stepId)
|
||||
}
|
||||
|
||||
export function getStepByUrl(url: string): SDKStep | undefined {
|
||||
return SDK_STEPS.find(s => s.url === url)
|
||||
}
|
||||
|
||||
export function getStepsForPhase(phase: SDKPhase): SDKStep[] {
|
||||
return SDK_STEPS.filter(s => s.phase === phase).sort((a, b) => a.order - b.order)
|
||||
}
|
||||
|
||||
export function getNextStep(currentStepId: string): SDKStep | undefined {
|
||||
const currentStep = getStepById(currentStepId)
|
||||
if (!currentStep) return undefined
|
||||
|
||||
const stepsInPhase = getStepsForPhase(currentStep.phase)
|
||||
const currentIndex = stepsInPhase.findIndex(s => s.id === currentStepId)
|
||||
|
||||
if (currentIndex < stepsInPhase.length - 1) {
|
||||
return stepsInPhase[currentIndex + 1]
|
||||
}
|
||||
|
||||
if (currentStep.phase === 1) {
|
||||
return getStepsForPhase(2)[0]
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function getPreviousStep(currentStepId: string): SDKStep | undefined {
|
||||
const currentStep = getStepById(currentStepId)
|
||||
if (!currentStep) return undefined
|
||||
|
||||
const stepsInPhase = getStepsForPhase(currentStep.phase)
|
||||
const currentIndex = stepsInPhase.findIndex(s => s.id === currentStepId)
|
||||
|
||||
if (currentIndex > 0) {
|
||||
return stepsInPhase[currentIndex - 1]
|
||||
}
|
||||
|
||||
if (currentStep.phase === 2) {
|
||||
const phase1Steps = getStepsForPhase(1)
|
||||
return phase1Steps[phase1Steps.length - 1]
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function getCompletionPercentage(state: SDKState): number {
|
||||
const totalSteps = SDK_STEPS.length
|
||||
const completedSteps = state.completedSteps.length
|
||||
return Math.round((completedSteps / totalSteps) * 100)
|
||||
}
|
||||
|
||||
export function getPhaseCompletionPercentage(state: SDKState, phase: SDKPhase): number {
|
||||
const phaseSteps = getStepsForPhase(phase)
|
||||
const completedPhaseSteps = phaseSteps.filter(s => state.completedSteps.includes(s.id))
|
||||
return Math.round((completedPhaseSteps.length / phaseSteps.length) * 100)
|
||||
}
|
||||
9
breakpilot-compliance-sdk/packages/types/tsconfig.json
Normal file
9
breakpilot-compliance-sdk/packages/types/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
16
breakpilot-compliance-sdk/packages/types/tsup.config.ts
Normal file
16
breakpilot-compliance-sdk/packages/types/tsup.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entry: [
|
||||
'src/index.ts',
|
||||
'src/dsgvo.ts',
|
||||
'src/compliance.ts',
|
||||
'src/rag.ts',
|
||||
'src/security.ts',
|
||||
],
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
})
|
||||
55
breakpilot-compliance-sdk/packages/vanilla/package.json
Normal file
55
breakpilot-compliance-sdk/packages/vanilla/package.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@breakpilot/compliance-sdk-vanilla",
|
||||
"version": "0.0.1",
|
||||
"description": "BreakPilot Compliance SDK - Vanilla JS Embed Script & Web Components",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./embed": {
|
||||
"import": "./dist/embed.mjs",
|
||||
"require": "./dist/embed.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"lint": "eslint src/",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@breakpilot/compliance-sdk-core": "workspace:*",
|
||||
"@breakpilot/compliance-sdk-types": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/breakpilot/compliance-sdk.git",
|
||||
"directory": "packages/vanilla"
|
||||
},
|
||||
"keywords": [
|
||||
"compliance",
|
||||
"gdpr",
|
||||
"dsgvo",
|
||||
"consent",
|
||||
"cookie-banner",
|
||||
"web-components",
|
||||
"embed"
|
||||
]
|
||||
}
|
||||
611
breakpilot-compliance-sdk/packages/vanilla/src/embed.ts
Normal file
611
breakpilot-compliance-sdk/packages/vanilla/src/embed.ts
Normal file
@@ -0,0 +1,611 @@
|
||||
/**
|
||||
* BreakPilot Compliance SDK - Embed Script
|
||||
*
|
||||
* Usage:
|
||||
* <script src="https://cdn.breakpilot.app/sdk/v1/compliance.min.js"></script>
|
||||
* <script>
|
||||
* BreakPilotSDK.init({
|
||||
* apiEndpoint: 'https://compliance.example.com/api/v1',
|
||||
* apiKey: 'pk_live_xxx',
|
||||
* autoInjectBanner: true,
|
||||
* bannerConfig: { position: 'bottom', theme: 'dark', language: 'de' }
|
||||
* });
|
||||
* </script>
|
||||
*/
|
||||
|
||||
import { ComplianceClient } from '@breakpilot/compliance-sdk-core'
|
||||
import type {
|
||||
ConsentPurpose,
|
||||
CookieBannerPosition,
|
||||
CookieBannerTheme,
|
||||
DSRRequestType,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
export interface BreakPilotSDKConfig {
|
||||
apiEndpoint: string
|
||||
apiKey: string
|
||||
tenantId?: string
|
||||
autoInjectBanner?: boolean
|
||||
bannerConfig?: BannerConfig
|
||||
onConsentChange?: (consents: Record<ConsentPurpose, boolean>) => void
|
||||
onReady?: () => void
|
||||
onError?: (error: Error) => void
|
||||
debug?: boolean
|
||||
}
|
||||
|
||||
export interface BannerConfig {
|
||||
position?: CookieBannerPosition
|
||||
theme?: CookieBannerTheme
|
||||
language?: 'de' | 'en'
|
||||
privacyPolicyUrl?: string
|
||||
imprintUrl?: string
|
||||
texts?: {
|
||||
title?: string
|
||||
description?: string
|
||||
acceptAll?: string
|
||||
rejectAll?: string
|
||||
settings?: string
|
||||
save?: string
|
||||
}
|
||||
customColors?: {
|
||||
background?: string
|
||||
text?: string
|
||||
primary?: string
|
||||
secondary?: string
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Internal State
|
||||
// ============================================================================
|
||||
|
||||
let _client: ComplianceClient | null = null
|
||||
let _config: BreakPilotSDKConfig | null = null
|
||||
let _consents: Record<ConsentPurpose, boolean> = {
|
||||
ESSENTIAL: true,
|
||||
FUNCTIONAL: false,
|
||||
ANALYTICS: false,
|
||||
MARKETING: false,
|
||||
PERSONALIZATION: false,
|
||||
THIRD_PARTY: false,
|
||||
}
|
||||
let _bannerElement: HTMLElement | null = null
|
||||
let _isInitialized = false
|
||||
|
||||
// ============================================================================
|
||||
// Translations
|
||||
// ============================================================================
|
||||
|
||||
const TRANSLATIONS = {
|
||||
de: {
|
||||
title: 'Cookie-Einwilligung',
|
||||
description:
|
||||
'Wir verwenden Cookies, um Ihre Erfahrung zu verbessern. Weitere Informationen finden Sie in unserer Datenschutzerklärung.',
|
||||
acceptAll: 'Alle akzeptieren',
|
||||
rejectAll: 'Nur notwendige',
|
||||
settings: 'Einstellungen',
|
||||
save: 'Speichern',
|
||||
privacy: 'Datenschutz',
|
||||
imprint: 'Impressum',
|
||||
categories: {
|
||||
ESSENTIAL: { name: 'Notwendig', description: 'Erforderlich für die Grundfunktionen' },
|
||||
FUNCTIONAL: { name: 'Funktional', description: 'Verbesserte Funktionen' },
|
||||
ANALYTICS: { name: 'Analyse', description: 'Nutzungsstatistiken' },
|
||||
MARKETING: { name: 'Marketing', description: 'Personalisierte Werbung' },
|
||||
PERSONALIZATION: { name: 'Personalisierung', description: 'Angepasste Inhalte' },
|
||||
THIRD_PARTY: { name: 'Drittanbieter', description: 'Externe Dienste' },
|
||||
},
|
||||
},
|
||||
en: {
|
||||
title: 'Cookie Consent',
|
||||
description:
|
||||
'We use cookies to improve your experience. For more information, please see our privacy policy.',
|
||||
acceptAll: 'Accept All',
|
||||
rejectAll: 'Reject Non-Essential',
|
||||
settings: 'Settings',
|
||||
save: 'Save',
|
||||
privacy: 'Privacy Policy',
|
||||
imprint: 'Imprint',
|
||||
categories: {
|
||||
ESSENTIAL: { name: 'Essential', description: 'Required for basic functionality' },
|
||||
FUNCTIONAL: { name: 'Functional', description: 'Enhanced features' },
|
||||
ANALYTICS: { name: 'Analytics', description: 'Usage statistics' },
|
||||
MARKETING: { name: 'Marketing', description: 'Personalized advertising' },
|
||||
PERSONALIZATION: { name: 'Personalization', description: 'Customized content' },
|
||||
THIRD_PARTY: { name: 'Third Party', description: 'External services' },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Utility Functions
|
||||
// ============================================================================
|
||||
|
||||
function log(message: string, ...args: unknown[]): void {
|
||||
if (_config?.debug) {
|
||||
console.log(`[BreakPilotSDK] ${message}`, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
function getStoredConsents(): Record<ConsentPurpose, boolean> | null {
|
||||
try {
|
||||
const stored = localStorage.getItem('breakpilot_consents')
|
||||
return stored ? JSON.parse(stored) : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function storeConsents(consents: Record<ConsentPurpose, boolean>): void {
|
||||
try {
|
||||
localStorage.setItem('breakpilot_consents', JSON.stringify(consents))
|
||||
localStorage.setItem('breakpilot_consents_timestamp', Date.now().toString())
|
||||
} catch {
|
||||
log('Failed to store consents')
|
||||
}
|
||||
}
|
||||
|
||||
function createElement<K extends keyof HTMLElementTagNameMap>(
|
||||
tag: K,
|
||||
styles: Partial<CSSStyleDeclaration> = {},
|
||||
attributes: Record<string, string> = {}
|
||||
): HTMLElementTagNameMap[K] {
|
||||
const el = document.createElement(tag)
|
||||
Object.assign(el.style, styles)
|
||||
Object.entries(attributes).forEach(([key, value]) => el.setAttribute(key, value))
|
||||
return el
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Banner Implementation
|
||||
// ============================================================================
|
||||
|
||||
function createBanner(): HTMLElement {
|
||||
const config = _config!
|
||||
const bannerConfig = config.bannerConfig || {}
|
||||
const lang = bannerConfig.language || 'de'
|
||||
const t = TRANSLATIONS[lang]
|
||||
const position = bannerConfig.position || 'BOTTOM'
|
||||
const theme = bannerConfig.theme || 'LIGHT'
|
||||
|
||||
const isDark = theme === 'DARK'
|
||||
const bgColor = bannerConfig.customColors?.background || (isDark ? '#1a1a1a' : '#ffffff')
|
||||
const textColor = bannerConfig.customColors?.text || (isDark ? '#ffffff' : '#1a1a1a')
|
||||
const primaryColor = bannerConfig.customColors?.primary || (isDark ? '#ffffff' : '#1a1a1a')
|
||||
const secondaryColor = bannerConfig.customColors?.secondary || (isDark ? '#ffffff' : '#1a1a1a')
|
||||
|
||||
// Container
|
||||
const container = createElement(
|
||||
'div',
|
||||
{
|
||||
position: 'fixed',
|
||||
zIndex: '99999',
|
||||
left: position === 'CENTER' ? '50%' : '0',
|
||||
right: position === 'CENTER' ? 'auto' : '0',
|
||||
top: position === 'TOP' ? '0' : position === 'CENTER' ? '50%' : 'auto',
|
||||
bottom: position === 'BOTTOM' ? '0' : 'auto',
|
||||
transform: position === 'CENTER' ? 'translate(-50%, -50%)' : 'none',
|
||||
maxWidth: position === 'CENTER' ? '500px' : 'none',
|
||||
backgroundColor: bgColor,
|
||||
color: textColor,
|
||||
padding: '20px',
|
||||
boxShadow: '0 -2px 10px rgba(0, 0, 0, 0.1)',
|
||||
fontFamily: 'system-ui, -apple-system, sans-serif',
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.5',
|
||||
},
|
||||
{ id: 'breakpilot-consent-banner', role: 'dialog', 'aria-label': t.title }
|
||||
)
|
||||
|
||||
// Title
|
||||
const title = createElement('h3', {
|
||||
margin: '0 0 10px',
|
||||
fontSize: '18px',
|
||||
fontWeight: '600',
|
||||
})
|
||||
title.textContent = bannerConfig.texts?.title || t.title
|
||||
|
||||
// Description
|
||||
const description = createElement('p', {
|
||||
margin: '0 0 15px',
|
||||
opacity: '0.8',
|
||||
})
|
||||
description.textContent = bannerConfig.texts?.description || t.description
|
||||
|
||||
// Buttons container
|
||||
const buttonsContainer = createElement('div', {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '10px',
|
||||
alignItems: 'center',
|
||||
})
|
||||
|
||||
// Accept All button
|
||||
const acceptBtn = createElement(
|
||||
'button',
|
||||
{
|
||||
padding: '10px 20px',
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
fontWeight: '500',
|
||||
backgroundColor: primaryColor,
|
||||
color: isDark ? '#1a1a1a' : '#ffffff',
|
||||
},
|
||||
{ type: 'button' }
|
||||
)
|
||||
acceptBtn.textContent = bannerConfig.texts?.acceptAll || t.acceptAll
|
||||
acceptBtn.onclick = () => handleAcceptAll()
|
||||
|
||||
// Reject button
|
||||
const rejectBtn = createElement(
|
||||
'button',
|
||||
{
|
||||
padding: '10px 20px',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'transparent',
|
||||
border: `1px solid ${secondaryColor}`,
|
||||
color: textColor,
|
||||
cursor: 'pointer',
|
||||
fontWeight: '500',
|
||||
},
|
||||
{ type: 'button' }
|
||||
)
|
||||
rejectBtn.textContent = bannerConfig.texts?.rejectAll || t.rejectAll
|
||||
rejectBtn.onclick = () => handleRejectAll()
|
||||
|
||||
// Settings button
|
||||
const settingsBtn = createElement(
|
||||
'button',
|
||||
{
|
||||
padding: '10px 20px',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'transparent',
|
||||
border: `1px solid ${secondaryColor}`,
|
||||
color: textColor,
|
||||
cursor: 'pointer',
|
||||
fontWeight: '500',
|
||||
},
|
||||
{ type: 'button' }
|
||||
)
|
||||
settingsBtn.textContent = bannerConfig.texts?.settings || t.settings
|
||||
settingsBtn.onclick = () => showSettingsPanel()
|
||||
|
||||
// Links container
|
||||
const linksContainer = createElement('div', {
|
||||
marginLeft: 'auto',
|
||||
fontSize: '12px',
|
||||
})
|
||||
|
||||
const privacyLink = createElement('a', {
|
||||
marginRight: '15px',
|
||||
color: textColor,
|
||||
textDecoration: 'none',
|
||||
})
|
||||
privacyLink.href = bannerConfig.privacyPolicyUrl || '/privacy'
|
||||
privacyLink.textContent = t.privacy
|
||||
|
||||
const imprintLink = createElement('a', {
|
||||
color: textColor,
|
||||
textDecoration: 'none',
|
||||
})
|
||||
imprintLink.href = bannerConfig.imprintUrl || '/imprint'
|
||||
imprintLink.textContent = t.imprint
|
||||
|
||||
// Assemble
|
||||
linksContainer.appendChild(privacyLink)
|
||||
linksContainer.appendChild(imprintLink)
|
||||
|
||||
buttonsContainer.appendChild(acceptBtn)
|
||||
buttonsContainer.appendChild(rejectBtn)
|
||||
buttonsContainer.appendChild(settingsBtn)
|
||||
buttonsContainer.appendChild(linksContainer)
|
||||
|
||||
container.appendChild(title)
|
||||
container.appendChild(description)
|
||||
container.appendChild(buttonsContainer)
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
function showSettingsPanel(): void {
|
||||
if (!_bannerElement) return
|
||||
|
||||
const config = _config!
|
||||
const bannerConfig = config.bannerConfig || {}
|
||||
const lang = bannerConfig.language || 'de'
|
||||
const t = TRANSLATIONS[lang]
|
||||
const theme = bannerConfig.theme || 'LIGHT'
|
||||
const isDark = theme === 'DARK'
|
||||
const textColor = bannerConfig.customColors?.text || (isDark ? '#ffffff' : '#1a1a1a')
|
||||
|
||||
// Clear banner content
|
||||
_bannerElement.innerHTML = ''
|
||||
|
||||
// Title
|
||||
const title = createElement('h3', {
|
||||
margin: '0 0 15px',
|
||||
fontSize: '18px',
|
||||
fontWeight: '600',
|
||||
})
|
||||
title.textContent = t.settings
|
||||
|
||||
_bannerElement.appendChild(title)
|
||||
|
||||
// Categories
|
||||
const categories: ConsentPurpose[] = [
|
||||
'ESSENTIAL',
|
||||
'FUNCTIONAL',
|
||||
'ANALYTICS',
|
||||
'MARKETING',
|
||||
]
|
||||
|
||||
categories.forEach(category => {
|
||||
const catInfo = t.categories[category]
|
||||
const row = createElement('div', {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '10px 0',
|
||||
borderBottom: `1px solid ${isDark ? '#333' : '#eee'}`,
|
||||
})
|
||||
|
||||
const labelContainer = createElement('div', {})
|
||||
const labelName = createElement('div', { fontWeight: '500' })
|
||||
labelName.textContent = catInfo.name
|
||||
const labelDesc = createElement('div', { fontSize: '12px', opacity: '0.7' })
|
||||
labelDesc.textContent = catInfo.description
|
||||
|
||||
labelContainer.appendChild(labelName)
|
||||
labelContainer.appendChild(labelDesc)
|
||||
|
||||
const checkbox = createElement(
|
||||
'input',
|
||||
{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
cursor: category === 'ESSENTIAL' ? 'not-allowed' : 'pointer',
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
'data-category': category,
|
||||
}
|
||||
)
|
||||
checkbox.checked = _consents[category]
|
||||
checkbox.disabled = category === 'ESSENTIAL'
|
||||
checkbox.onchange = () => {
|
||||
if (category !== 'ESSENTIAL') {
|
||||
_consents[category] = checkbox.checked
|
||||
}
|
||||
}
|
||||
|
||||
row.appendChild(labelContainer)
|
||||
row.appendChild(checkbox)
|
||||
_bannerElement!.appendChild(row)
|
||||
})
|
||||
|
||||
// Buttons
|
||||
const buttonsContainer = createElement('div', {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '10px',
|
||||
marginTop: '15px',
|
||||
})
|
||||
|
||||
const primaryColor = bannerConfig.customColors?.primary || (isDark ? '#ffffff' : '#1a1a1a')
|
||||
const secondaryColor = bannerConfig.customColors?.secondary || (isDark ? '#ffffff' : '#1a1a1a')
|
||||
|
||||
const saveBtn = createElement(
|
||||
'button',
|
||||
{
|
||||
padding: '10px 20px',
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
fontWeight: '500',
|
||||
backgroundColor: primaryColor,
|
||||
color: isDark ? '#1a1a1a' : '#ffffff',
|
||||
},
|
||||
{ type: 'button' }
|
||||
)
|
||||
saveBtn.textContent = bannerConfig.texts?.save || t.save
|
||||
saveBtn.onclick = () => handleSaveSettings()
|
||||
|
||||
const backBtn = createElement(
|
||||
'button',
|
||||
{
|
||||
padding: '10px 20px',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'transparent',
|
||||
border: `1px solid ${secondaryColor}`,
|
||||
color: textColor,
|
||||
cursor: 'pointer',
|
||||
fontWeight: '500',
|
||||
},
|
||||
{ type: 'button' }
|
||||
)
|
||||
backBtn.textContent = 'Zurück'
|
||||
backBtn.onclick = () => showMainBanner()
|
||||
|
||||
buttonsContainer.appendChild(saveBtn)
|
||||
buttonsContainer.appendChild(backBtn)
|
||||
_bannerElement.appendChild(buttonsContainer)
|
||||
}
|
||||
|
||||
function showMainBanner(): void {
|
||||
if (_bannerElement) {
|
||||
_bannerElement.remove()
|
||||
}
|
||||
_bannerElement = createBanner()
|
||||
document.body.appendChild(_bannerElement)
|
||||
}
|
||||
|
||||
function hideBanner(): void {
|
||||
if (_bannerElement) {
|
||||
_bannerElement.remove()
|
||||
_bannerElement = null
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Consent Handlers
|
||||
// ============================================================================
|
||||
|
||||
function handleAcceptAll(): void {
|
||||
_consents = {
|
||||
ESSENTIAL: true,
|
||||
FUNCTIONAL: true,
|
||||
ANALYTICS: true,
|
||||
MARKETING: true,
|
||||
PERSONALIZATION: true,
|
||||
THIRD_PARTY: true,
|
||||
}
|
||||
applyConsents()
|
||||
}
|
||||
|
||||
function handleRejectAll(): void {
|
||||
_consents = {
|
||||
ESSENTIAL: true,
|
||||
FUNCTIONAL: false,
|
||||
ANALYTICS: false,
|
||||
MARKETING: false,
|
||||
PERSONALIZATION: false,
|
||||
THIRD_PARTY: false,
|
||||
}
|
||||
applyConsents()
|
||||
}
|
||||
|
||||
function handleSaveSettings(): void {
|
||||
applyConsents()
|
||||
}
|
||||
|
||||
function applyConsents(): void {
|
||||
storeConsents(_consents)
|
||||
hideBanner()
|
||||
_config?.onConsentChange?.(_consents)
|
||||
log('Consents applied:', _consents)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Public API
|
||||
// ============================================================================
|
||||
|
||||
function init(config: BreakPilotSDKConfig): void {
|
||||
if (_isInitialized) {
|
||||
log('SDK already initialized')
|
||||
return
|
||||
}
|
||||
|
||||
_config = config
|
||||
log('Initializing with config:', config)
|
||||
|
||||
try {
|
||||
_client = new ComplianceClient({
|
||||
apiEndpoint: config.apiEndpoint,
|
||||
apiKey: config.apiKey,
|
||||
tenantId: config.tenantId,
|
||||
})
|
||||
|
||||
// Check for stored consents
|
||||
const storedConsents = getStoredConsents()
|
||||
if (storedConsents) {
|
||||
_consents = storedConsents
|
||||
log('Loaded stored consents:', _consents)
|
||||
config.onConsentChange?.(storedConsents)
|
||||
} else if (config.autoInjectBanner !== false) {
|
||||
// Show banner if no stored consents
|
||||
showMainBanner()
|
||||
}
|
||||
|
||||
_isInitialized = true
|
||||
config.onReady?.()
|
||||
log('SDK initialized successfully')
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error(String(error))
|
||||
config.onError?.(err)
|
||||
log('SDK initialization failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
function getConsents(): Record<ConsentPurpose, boolean> {
|
||||
return { ..._consents }
|
||||
}
|
||||
|
||||
function updateConsent(purpose: ConsentPurpose, granted: boolean): void {
|
||||
if (purpose === 'ESSENTIAL') {
|
||||
log('Cannot change essential consent')
|
||||
return
|
||||
}
|
||||
_consents[purpose] = granted
|
||||
storeConsents(_consents)
|
||||
_config?.onConsentChange?.(_consents)
|
||||
log('Consent updated:', purpose, granted)
|
||||
}
|
||||
|
||||
function showBanner(): void {
|
||||
showMainBanner()
|
||||
}
|
||||
|
||||
function hasConsent(purpose: ConsentPurpose): boolean {
|
||||
return _consents[purpose]
|
||||
}
|
||||
|
||||
function getClient(): ComplianceClient | null {
|
||||
return _client
|
||||
}
|
||||
|
||||
async function submitDSR(
|
||||
type: DSRRequestType,
|
||||
email: string,
|
||||
name: string
|
||||
): Promise<{ success: boolean; requestId?: string; error?: string }> {
|
||||
if (!_client) {
|
||||
return { success: false, error: 'SDK not initialized' }
|
||||
}
|
||||
|
||||
try {
|
||||
// This would call the DSR endpoint
|
||||
log('Submitting DSR:', { type, email, name })
|
||||
// In a real implementation, this would use the client to submit
|
||||
return { success: true, requestId: `dsr_${Date.now()}` }
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
return { success: false, error: message }
|
||||
}
|
||||
}
|
||||
|
||||
function destroy(): void {
|
||||
hideBanner()
|
||||
_client = null
|
||||
_config = null
|
||||
_isInitialized = false
|
||||
log('SDK destroyed')
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Export for IIFE
|
||||
// ============================================================================
|
||||
|
||||
export const BreakPilotSDK = {
|
||||
init,
|
||||
getConsents,
|
||||
updateConsent,
|
||||
showBanner,
|
||||
hideBanner,
|
||||
hasConsent,
|
||||
getClient,
|
||||
submitDSR,
|
||||
destroy,
|
||||
version: '0.0.1',
|
||||
}
|
||||
|
||||
// Auto-attach to window for script tag usage
|
||||
if (typeof window !== 'undefined') {
|
||||
;(window as unknown as Record<string, unknown>).BreakPilotSDK = BreakPilotSDK
|
||||
}
|
||||
|
||||
export default BreakPilotSDK
|
||||
29
breakpilot-compliance-sdk/packages/vanilla/src/index.ts
Normal file
29
breakpilot-compliance-sdk/packages/vanilla/src/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @breakpilot/compliance-sdk-vanilla
|
||||
*
|
||||
* Vanilla JS integration for BreakPilot Compliance SDK
|
||||
*
|
||||
* Includes:
|
||||
* - Embed Script (IIFE for <script> tag)
|
||||
* - Web Components (Custom Elements)
|
||||
*/
|
||||
|
||||
// Re-export embed script
|
||||
export { BreakPilotSDK, type BreakPilotSDKConfig, type BannerConfig } from './embed'
|
||||
|
||||
// Re-export web components
|
||||
export {
|
||||
BreakPilotElement,
|
||||
COMMON_STYLES,
|
||||
ConsentBannerElement,
|
||||
DSRPortalElement,
|
||||
ComplianceScoreElement,
|
||||
} from './web-components'
|
||||
|
||||
// Re-export types from core
|
||||
export type {
|
||||
ConsentPurpose,
|
||||
CookieBannerPosition,
|
||||
CookieBannerTheme,
|
||||
DSRRequestType,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Base class for BreakPilot Web Components
|
||||
*/
|
||||
|
||||
import { ComplianceClient } from '@breakpilot/compliance-sdk-core'
|
||||
|
||||
export abstract class BreakPilotElement extends HTMLElement {
|
||||
protected client: ComplianceClient | null = null
|
||||
protected shadow: ShadowRoot
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.shadow = this.attachShadow({ mode: 'open' })
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
this.initializeClient()
|
||||
this.render()
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
this.cleanup()
|
||||
}
|
||||
|
||||
attributeChangedCallback(
|
||||
name: string,
|
||||
_oldValue: string | null,
|
||||
_newValue: string | null
|
||||
): void {
|
||||
if (name === 'api-key' || name === 'api-endpoint') {
|
||||
this.initializeClient()
|
||||
}
|
||||
this.render()
|
||||
}
|
||||
|
||||
protected initializeClient(): void {
|
||||
const apiKey = this.getAttribute('api-key')
|
||||
const apiEndpoint =
|
||||
this.getAttribute('api-endpoint') || 'https://compliance.breakpilot.app/api/v1'
|
||||
const tenantId = this.getAttribute('tenant-id') || undefined
|
||||
|
||||
if (apiKey) {
|
||||
this.client = new ComplianceClient({
|
||||
apiKey,
|
||||
apiEndpoint,
|
||||
tenantId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract render(): void
|
||||
protected cleanup(): void {}
|
||||
|
||||
protected createStyles(css: string): HTMLStyleElement {
|
||||
const style = document.createElement('style')
|
||||
style.textContent = css
|
||||
return style
|
||||
}
|
||||
|
||||
protected emit<T>(eventName: string, detail: T): void {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(eventName, {
|
||||
detail,
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Common styles
|
||||
export const COMMON_STYLES = `
|
||||
:host {
|
||||
display: block;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
`
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* <breakpilot-compliance-score> Web Component
|
||||
*
|
||||
* Displays a circular compliance score indicator
|
||||
*
|
||||
* Usage:
|
||||
* <breakpilot-compliance-score
|
||||
* score="75"
|
||||
* size="medium"
|
||||
* show-label="true">
|
||||
* </breakpilot-compliance-score>
|
||||
*/
|
||||
|
||||
import { BreakPilotElement, COMMON_STYLES } from './base'
|
||||
|
||||
export class ComplianceScoreElement extends BreakPilotElement {
|
||||
static get observedAttributes(): string[] {
|
||||
return ['score', 'size', 'show-label', 'label']
|
||||
}
|
||||
|
||||
private get score(): number {
|
||||
const value = parseInt(this.getAttribute('score') || '0', 10)
|
||||
return Math.max(0, Math.min(100, value))
|
||||
}
|
||||
|
||||
private get size(): 'small' | 'medium' | 'large' {
|
||||
return (this.getAttribute('size') as 'small' | 'medium' | 'large') || 'medium'
|
||||
}
|
||||
|
||||
private get showLabel(): boolean {
|
||||
return this.getAttribute('show-label') !== 'false'
|
||||
}
|
||||
|
||||
private get label(): string {
|
||||
return this.getAttribute('label') || 'Compliance Score'
|
||||
}
|
||||
|
||||
private getColor(score: number): string {
|
||||
if (score >= 80) return '#16a34a' // Green
|
||||
if (score >= 60) return '#f59e0b' // Yellow
|
||||
if (score >= 40) return '#f97316' // Orange
|
||||
return '#dc2626' // Red
|
||||
}
|
||||
|
||||
protected render(): void {
|
||||
const sizes = {
|
||||
small: { width: 80, strokeWidth: 6, fontSize: 18 },
|
||||
medium: { width: 120, strokeWidth: 8, fontSize: 24 },
|
||||
large: { width: 160, strokeWidth: 10, fontSize: 32 },
|
||||
}
|
||||
|
||||
const { width, strokeWidth, fontSize } = sizes[this.size]
|
||||
const radius = (width - strokeWidth) / 2
|
||||
const circumference = radius * 2 * Math.PI
|
||||
const offset = circumference - (this.score / 100) * circumference
|
||||
const color = this.getColor(this.score)
|
||||
|
||||
const styles = `
|
||||
${COMMON_STYLES}
|
||||
|
||||
:host {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.score-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.background-circle {
|
||||
fill: none;
|
||||
stroke: #e5e5e5;
|
||||
}
|
||||
|
||||
.progress-circle {
|
||||
fill: none;
|
||||
stroke: ${color};
|
||||
stroke-linecap: round;
|
||||
transition: stroke-dashoffset 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.score-text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: ${fontSize}px;
|
||||
font-weight: 700;
|
||||
color: ${color};
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
`
|
||||
|
||||
this.shadow.innerHTML = `
|
||||
<style>${styles}</style>
|
||||
<div class="score-container">
|
||||
<svg width="${width}" height="${width}">
|
||||
<circle
|
||||
class="background-circle"
|
||||
cx="${width / 2}"
|
||||
cy="${width / 2}"
|
||||
r="${radius}"
|
||||
stroke-width="${strokeWidth}"
|
||||
/>
|
||||
<circle
|
||||
class="progress-circle"
|
||||
cx="${width / 2}"
|
||||
cy="${width / 2}"
|
||||
r="${radius}"
|
||||
stroke-width="${strokeWidth}"
|
||||
stroke-dasharray="${circumference}"
|
||||
stroke-dashoffset="${offset}"
|
||||
/>
|
||||
</svg>
|
||||
<div class="score-text">${this.score}%</div>
|
||||
</div>
|
||||
${this.showLabel ? `<div class="label">${this.label}</div>` : ''}
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
// Register the custom element
|
||||
if (typeof customElements !== 'undefined') {
|
||||
customElements.define('breakpilot-compliance-score', ComplianceScoreElement)
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* <breakpilot-consent-banner> Web Component
|
||||
*
|
||||
* Usage:
|
||||
* <breakpilot-consent-banner
|
||||
* api-key="pk_live_xxx"
|
||||
* position="bottom"
|
||||
* theme="light"
|
||||
* language="de"
|
||||
* privacy-url="/privacy"
|
||||
* imprint-url="/imprint">
|
||||
* </breakpilot-consent-banner>
|
||||
*/
|
||||
|
||||
import type {
|
||||
ConsentPurpose,
|
||||
CookieBannerPosition,
|
||||
CookieBannerTheme,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
import { BreakPilotElement, COMMON_STYLES } from './base'
|
||||
|
||||
const TRANSLATIONS = {
|
||||
de: {
|
||||
title: 'Cookie-Einwilligung',
|
||||
description:
|
||||
'Wir verwenden Cookies, um Ihre Erfahrung zu verbessern. Weitere Informationen finden Sie in unserer Datenschutzerklärung.',
|
||||
acceptAll: 'Alle akzeptieren',
|
||||
rejectAll: 'Nur notwendige',
|
||||
settings: 'Einstellungen',
|
||||
save: 'Einstellungen speichern',
|
||||
back: 'Zurück',
|
||||
privacy: 'Datenschutz',
|
||||
imprint: 'Impressum',
|
||||
categories: {
|
||||
ESSENTIAL: { name: 'Notwendig', description: 'Erforderlich für die Grundfunktionen' },
|
||||
FUNCTIONAL: { name: 'Funktional', description: 'Verbesserte Funktionen' },
|
||||
ANALYTICS: { name: 'Analyse', description: 'Nutzungsstatistiken' },
|
||||
MARKETING: { name: 'Marketing', description: 'Personalisierte Werbung' },
|
||||
},
|
||||
},
|
||||
en: {
|
||||
title: 'Cookie Consent',
|
||||
description:
|
||||
'We use cookies to improve your experience. For more information, please see our privacy policy.',
|
||||
acceptAll: 'Accept All',
|
||||
rejectAll: 'Reject Non-Essential',
|
||||
settings: 'Settings',
|
||||
save: 'Save Settings',
|
||||
back: 'Back',
|
||||
privacy: 'Privacy Policy',
|
||||
imprint: 'Imprint',
|
||||
categories: {
|
||||
ESSENTIAL: { name: 'Essential', description: 'Required for basic functionality' },
|
||||
FUNCTIONAL: { name: 'Functional', description: 'Enhanced features' },
|
||||
ANALYTICS: { name: 'Analytics', description: 'Usage statistics' },
|
||||
MARKETING: { name: 'Marketing', description: 'Personalized advertising' },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export class ConsentBannerElement extends BreakPilotElement {
|
||||
static get observedAttributes(): string[] {
|
||||
return [
|
||||
'api-key',
|
||||
'api-endpoint',
|
||||
'position',
|
||||
'theme',
|
||||
'language',
|
||||
'privacy-url',
|
||||
'imprint-url',
|
||||
]
|
||||
}
|
||||
|
||||
private consents: Record<ConsentPurpose, boolean> = {
|
||||
ESSENTIAL: true,
|
||||
FUNCTIONAL: false,
|
||||
ANALYTICS: false,
|
||||
MARKETING: false,
|
||||
PERSONALIZATION: false,
|
||||
THIRD_PARTY: false,
|
||||
}
|
||||
|
||||
private showSettings = false
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback()
|
||||
this.loadStoredConsents()
|
||||
}
|
||||
|
||||
private get position(): CookieBannerPosition {
|
||||
return (this.getAttribute('position')?.toUpperCase() as CookieBannerPosition) || 'BOTTOM'
|
||||
}
|
||||
|
||||
private get theme(): CookieBannerTheme {
|
||||
return (this.getAttribute('theme')?.toUpperCase() as CookieBannerTheme) || 'LIGHT'
|
||||
}
|
||||
|
||||
private get language(): 'de' | 'en' {
|
||||
return (this.getAttribute('language') as 'de' | 'en') || 'de'
|
||||
}
|
||||
|
||||
private get privacyUrl(): string {
|
||||
return this.getAttribute('privacy-url') || '/privacy'
|
||||
}
|
||||
|
||||
private get imprintUrl(): string {
|
||||
return this.getAttribute('imprint-url') || '/imprint'
|
||||
}
|
||||
|
||||
private get t() {
|
||||
return TRANSLATIONS[this.language]
|
||||
}
|
||||
|
||||
private loadStoredConsents(): void {
|
||||
try {
|
||||
const stored = localStorage.getItem('breakpilot_consents')
|
||||
if (stored) {
|
||||
this.consents = JSON.parse(stored)
|
||||
this.hide()
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
private storeConsents(): void {
|
||||
try {
|
||||
localStorage.setItem('breakpilot_consents', JSON.stringify(this.consents))
|
||||
localStorage.setItem('breakpilot_consents_timestamp', Date.now().toString())
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
private handleAcceptAll = (): void => {
|
||||
this.consents = {
|
||||
ESSENTIAL: true,
|
||||
FUNCTIONAL: true,
|
||||
ANALYTICS: true,
|
||||
MARKETING: true,
|
||||
PERSONALIZATION: true,
|
||||
THIRD_PARTY: true,
|
||||
}
|
||||
this.applyConsents()
|
||||
}
|
||||
|
||||
private handleRejectAll = (): void => {
|
||||
this.consents = {
|
||||
ESSENTIAL: true,
|
||||
FUNCTIONAL: false,
|
||||
ANALYTICS: false,
|
||||
MARKETING: false,
|
||||
PERSONALIZATION: false,
|
||||
THIRD_PARTY: false,
|
||||
}
|
||||
this.applyConsents()
|
||||
}
|
||||
|
||||
private handleSave = (): void => {
|
||||
this.applyConsents()
|
||||
}
|
||||
|
||||
private applyConsents(): void {
|
||||
this.storeConsents()
|
||||
this.emit('consent-change', { consents: this.consents })
|
||||
this.hide()
|
||||
}
|
||||
|
||||
private toggleSettings = (): void => {
|
||||
this.showSettings = !this.showSettings
|
||||
this.render()
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
this.style.display = 'block'
|
||||
this.render()
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this.style.display = 'none'
|
||||
}
|
||||
|
||||
public getConsents(): Record<ConsentPurpose, boolean> {
|
||||
return { ...this.consents }
|
||||
}
|
||||
|
||||
protected render(): void {
|
||||
const isDark = this.theme === 'DARK'
|
||||
const bgColor = isDark ? '#1a1a1a' : '#ffffff'
|
||||
const textColor = isDark ? '#ffffff' : '#1a1a1a'
|
||||
const borderColor = isDark ? '#333' : '#eee'
|
||||
|
||||
const positionStyles = {
|
||||
TOP: 'top: 0; left: 0; right: 0;',
|
||||
BOTTOM: 'bottom: 0; left: 0; right: 0;',
|
||||
CENTER: 'top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: 500px;',
|
||||
}
|
||||
|
||||
const styles = `
|
||||
${COMMON_STYLES}
|
||||
|
||||
:host {
|
||||
position: fixed;
|
||||
z-index: 99999;
|
||||
${positionStyles[this.position]}
|
||||
}
|
||||
|
||||
.banner {
|
||||
background-color: ${bgColor};
|
||||
color: ${textColor};
|
||||
padding: 20px;
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0 0 15px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: ${isDark ? '#ffffff' : '#1a1a1a'};
|
||||
color: ${isDark ? '#1a1a1a' : '#ffffff'};
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: transparent;
|
||||
border: 1px solid ${textColor};
|
||||
color: ${textColor};
|
||||
}
|
||||
|
||||
.links {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.links a {
|
||||
color: ${textColor};
|
||||
text-decoration: none;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.links a:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.category {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid ${borderColor};
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.category-description {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.checkbox:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`
|
||||
|
||||
if (this.showSettings) {
|
||||
this.renderSettings(styles)
|
||||
} else {
|
||||
this.renderMain(styles)
|
||||
}
|
||||
}
|
||||
|
||||
private renderMain(styles: string): void {
|
||||
const t = this.t
|
||||
|
||||
this.shadow.innerHTML = `
|
||||
<style>${styles}</style>
|
||||
<div class="banner">
|
||||
<h3 class="title">${t.title}</h3>
|
||||
<p class="description">${t.description}</p>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-primary" id="accept-all">${t.acceptAll}</button>
|
||||
<button class="btn btn-secondary" id="reject-all">${t.rejectAll}</button>
|
||||
<button class="btn btn-secondary" id="settings">${t.settings}</button>
|
||||
<div class="links">
|
||||
<a href="${this.privacyUrl}">${t.privacy}</a>
|
||||
<a href="${this.imprintUrl}">${t.imprint}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
this.shadow.getElementById('accept-all')!.onclick = this.handleAcceptAll
|
||||
this.shadow.getElementById('reject-all')!.onclick = this.handleRejectAll
|
||||
this.shadow.getElementById('settings')!.onclick = this.toggleSettings
|
||||
}
|
||||
|
||||
private renderSettings(styles: string): void {
|
||||
const t = this.t
|
||||
const categories: ConsentPurpose[] = ['ESSENTIAL', 'FUNCTIONAL', 'ANALYTICS', 'MARKETING']
|
||||
|
||||
const categoriesHtml = categories
|
||||
.map(
|
||||
cat => `
|
||||
<div class="category">
|
||||
<div>
|
||||
<div class="category-name">${t.categories[cat].name}</div>
|
||||
<div class="category-description">${t.categories[cat].description}</div>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
data-category="${cat}"
|
||||
${this.consents[cat] ? 'checked' : ''}
|
||||
${cat === 'ESSENTIAL' ? 'disabled' : ''}
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join('')
|
||||
|
||||
this.shadow.innerHTML = `
|
||||
<style>${styles}</style>
|
||||
<div class="banner">
|
||||
<h3 class="title">${t.settings}</h3>
|
||||
${categoriesHtml}
|
||||
<div class="buttons" style="margin-top: 15px;">
|
||||
<button class="btn btn-primary" id="save">${t.save}</button>
|
||||
<button class="btn btn-secondary" id="back">${t.back}</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
this.shadow.getElementById('save')!.onclick = this.handleSave
|
||||
this.shadow.getElementById('back')!.onclick = this.toggleSettings
|
||||
|
||||
// Handle checkbox changes
|
||||
this.shadow.querySelectorAll<HTMLInputElement>('.checkbox').forEach(checkbox => {
|
||||
checkbox.onchange = () => {
|
||||
const category = checkbox.dataset.category as ConsentPurpose
|
||||
if (category !== 'ESSENTIAL') {
|
||||
this.consents[category] = checkbox.checked
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Register the custom element
|
||||
if (typeof customElements !== 'undefined') {
|
||||
customElements.define('breakpilot-consent-banner', ConsentBannerElement)
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
/**
|
||||
* <breakpilot-dsr-portal> Web Component
|
||||
*
|
||||
* Data Subject Request Portal for GDPR rights
|
||||
*
|
||||
* Usage:
|
||||
* <breakpilot-dsr-portal
|
||||
* api-key="pk_live_xxx"
|
||||
* language="de">
|
||||
* </breakpilot-dsr-portal>
|
||||
*/
|
||||
|
||||
import type { DSRRequestType } from '@breakpilot/compliance-sdk-types'
|
||||
import { BreakPilotElement, COMMON_STYLES } from './base'
|
||||
|
||||
const TRANSLATIONS = {
|
||||
de: {
|
||||
title: 'Betroffenenrechte-Portal',
|
||||
subtitle:
|
||||
'Hier können Sie Ihre Rechte gemäß DSGVO wahrnehmen. Wählen Sie die gewünschte Anfrage und füllen Sie das Formular aus.',
|
||||
requestType: 'Art der Anfrage',
|
||||
name: 'Ihr Name',
|
||||
namePlaceholder: 'Max Mustermann',
|
||||
email: 'E-Mail-Adresse',
|
||||
emailPlaceholder: 'max@example.com',
|
||||
additionalInfo: 'Zusätzliche Informationen (optional)',
|
||||
additionalInfoPlaceholder: 'Weitere Details zu Ihrer Anfrage...',
|
||||
submit: 'Anfrage einreichen',
|
||||
submitting: 'Wird gesendet...',
|
||||
successTitle: 'Anfrage eingereicht',
|
||||
successMessage:
|
||||
'Wir werden Ihre Anfrage innerhalb von 30 Tagen bearbeiten. Sie erhalten eine Bestätigung per E-Mail an',
|
||||
disclaimer:
|
||||
'Ihre Anfrage wird gemäß Art. 12 DSGVO innerhalb von einem Monat bearbeitet. In komplexen Fällen kann diese Frist um weitere zwei Monate verlängert werden.',
|
||||
types: {
|
||||
ACCESS: {
|
||||
name: 'Auskunft (Art. 15)',
|
||||
description: 'Welche Daten haben Sie über mich gespeichert?',
|
||||
},
|
||||
RECTIFICATION: {
|
||||
name: 'Berichtigung (Art. 16)',
|
||||
description: 'Korrigieren Sie falsche Daten über mich.',
|
||||
},
|
||||
ERASURE: {
|
||||
name: 'Löschung (Art. 17)',
|
||||
description: 'Löschen Sie alle meine personenbezogenen Daten.',
|
||||
},
|
||||
PORTABILITY: {
|
||||
name: 'Datenübertragbarkeit (Art. 20)',
|
||||
description: 'Exportieren Sie meine Daten in einem maschinenlesbaren Format.',
|
||||
},
|
||||
RESTRICTION: {
|
||||
name: 'Einschränkung (Art. 18)',
|
||||
description: 'Schränken Sie die Verarbeitung meiner Daten ein.',
|
||||
},
|
||||
OBJECTION: {
|
||||
name: 'Widerspruch (Art. 21)',
|
||||
description: 'Ich widerspreche der Verarbeitung meiner Daten.',
|
||||
},
|
||||
},
|
||||
},
|
||||
en: {
|
||||
title: 'Data Subject Rights Portal',
|
||||
subtitle:
|
||||
'Here you can exercise your rights under GDPR. Select the type of request and fill out the form.',
|
||||
requestType: 'Request Type',
|
||||
name: 'Your Name',
|
||||
namePlaceholder: 'John Doe',
|
||||
email: 'Email Address',
|
||||
emailPlaceholder: 'john@example.com',
|
||||
additionalInfo: 'Additional Information (optional)',
|
||||
additionalInfoPlaceholder: 'Any additional details about your request...',
|
||||
submit: 'Submit Request',
|
||||
submitting: 'Submitting...',
|
||||
successTitle: 'Request Submitted',
|
||||
successMessage:
|
||||
'We will process your request within 30 days. You will receive a confirmation email at',
|
||||
disclaimer:
|
||||
'Your request will be processed in accordance with Article 12 GDPR within one month. In complex cases, this period may be extended by up to two additional months.',
|
||||
types: {
|
||||
ACCESS: {
|
||||
name: 'Access (Art. 15)',
|
||||
description: 'What data do you have about me?',
|
||||
},
|
||||
RECTIFICATION: {
|
||||
name: 'Rectification (Art. 16)',
|
||||
description: 'Correct inaccurate data about me.',
|
||||
},
|
||||
ERASURE: {
|
||||
name: 'Erasure (Art. 17)',
|
||||
description: 'Delete all my personal data.',
|
||||
},
|
||||
PORTABILITY: {
|
||||
name: 'Data Portability (Art. 20)',
|
||||
description: 'Export my data in a machine-readable format.',
|
||||
},
|
||||
RESTRICTION: {
|
||||
name: 'Restriction (Art. 18)',
|
||||
description: 'Restrict the processing of my data.',
|
||||
},
|
||||
OBJECTION: {
|
||||
name: 'Objection (Art. 21)',
|
||||
description: 'I object to the processing of my data.',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export class DSRPortalElement extends BreakPilotElement {
|
||||
static get observedAttributes(): string[] {
|
||||
return ['api-key', 'api-endpoint', 'tenant-id', 'language']
|
||||
}
|
||||
|
||||
private selectedType: DSRRequestType | null = null
|
||||
private name = ''
|
||||
private email = ''
|
||||
private additionalInfo = ''
|
||||
private isSubmitting = false
|
||||
private isSubmitted = false
|
||||
private error: string | null = null
|
||||
|
||||
private get language(): 'de' | 'en' {
|
||||
return (this.getAttribute('language') as 'de' | 'en') || 'de'
|
||||
}
|
||||
|
||||
private get t() {
|
||||
return TRANSLATIONS[this.language]
|
||||
}
|
||||
|
||||
private handleTypeSelect = (type: DSRRequestType): void => {
|
||||
this.selectedType = type
|
||||
this.render()
|
||||
}
|
||||
|
||||
private handleSubmit = async (e: Event): Promise<void> => {
|
||||
e.preventDefault()
|
||||
|
||||
if (!this.selectedType || !this.email || !this.name) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isSubmitting = true
|
||||
this.error = null
|
||||
this.render()
|
||||
|
||||
try {
|
||||
// In a real implementation, this would use the client
|
||||
// For now, we simulate a successful submission
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
this.emit('dsr-submitted', {
|
||||
type: this.selectedType,
|
||||
email: this.email,
|
||||
name: this.name,
|
||||
additionalInfo: this.additionalInfo,
|
||||
})
|
||||
|
||||
this.isSubmitted = true
|
||||
} catch (err) {
|
||||
this.error = err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'
|
||||
} finally {
|
||||
this.isSubmitting = false
|
||||
this.render()
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): void {
|
||||
const styles = `
|
||||
${COMMON_STYLES}
|
||||
|
||||
:host {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.portal {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0 0 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.type-options {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.type-option {
|
||||
display: flex;
|
||||
padding: 15px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.type-option:hover {
|
||||
border-color: #999;
|
||||
}
|
||||
|
||||
.type-option.selected {
|
||||
border-color: #1a1a1a;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.type-option input {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.type-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.type-description {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: #1a1a1a;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 12px;
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: #1a1a1a;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-submit:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
margin-top: 20px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.success {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
background: #f0fdf4;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.success-title {
|
||||
margin: 0 0 10px;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
margin: 0;
|
||||
color: #166534;
|
||||
}
|
||||
`
|
||||
|
||||
if (this.isSubmitted) {
|
||||
this.renderSuccess(styles)
|
||||
} else {
|
||||
this.renderForm(styles)
|
||||
}
|
||||
}
|
||||
|
||||
private renderForm(styles: string): void {
|
||||
const t = this.t
|
||||
const types: DSRRequestType[] = [
|
||||
'ACCESS',
|
||||
'RECTIFICATION',
|
||||
'ERASURE',
|
||||
'PORTABILITY',
|
||||
'RESTRICTION',
|
||||
'OBJECTION',
|
||||
]
|
||||
|
||||
const typesHtml = types
|
||||
.map(
|
||||
type => `
|
||||
<label class="type-option ${this.selectedType === type ? 'selected' : ''}">
|
||||
<input
|
||||
type="radio"
|
||||
name="dsrType"
|
||||
value="${type}"
|
||||
${this.selectedType === type ? 'checked' : ''}
|
||||
/>
|
||||
<div>
|
||||
<div class="type-name">${t.types[type].name}</div>
|
||||
<div class="type-description">${t.types[type].description}</div>
|
||||
</div>
|
||||
</label>
|
||||
`
|
||||
)
|
||||
.join('')
|
||||
|
||||
const isValid = this.selectedType && this.email && this.name
|
||||
const isDisabled = !isValid || this.isSubmitting
|
||||
|
||||
this.shadow.innerHTML = `
|
||||
<style>${styles}</style>
|
||||
<div class="portal">
|
||||
<h2 class="title">${t.title}</h2>
|
||||
<p class="subtitle">${t.subtitle}</p>
|
||||
|
||||
<form id="dsr-form">
|
||||
<div class="form-group">
|
||||
<label class="label">${t.requestType} *</label>
|
||||
<div class="type-options">
|
||||
${typesHtml}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">${t.name} *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
id="name-input"
|
||||
value="${this.name}"
|
||||
placeholder="${t.namePlaceholder}"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">${t.email} *</label>
|
||||
<input
|
||||
type="email"
|
||||
class="input"
|
||||
id="email-input"
|
||||
value="${this.email}"
|
||||
placeholder="${t.emailPlaceholder}"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">${t.additionalInfo}</label>
|
||||
<textarea
|
||||
class="input textarea"
|
||||
id="info-input"
|
||||
placeholder="${t.additionalInfoPlaceholder}"
|
||||
rows="4"
|
||||
>${this.additionalInfo}</textarea>
|
||||
</div>
|
||||
|
||||
${this.error ? `<div class="error">${this.error}</div>` : ''}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-submit"
|
||||
${isDisabled ? 'disabled' : ''}
|
||||
>
|
||||
${this.isSubmitting ? t.submitting : t.submit}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="disclaimer">${t.disclaimer}</p>
|
||||
</div>
|
||||
`
|
||||
|
||||
// Bind events
|
||||
const form = this.shadow.getElementById('dsr-form') as HTMLFormElement
|
||||
form.onsubmit = this.handleSubmit
|
||||
|
||||
const nameInput = this.shadow.getElementById('name-input') as HTMLInputElement
|
||||
nameInput.oninput = e => {
|
||||
this.name = (e.target as HTMLInputElement).value
|
||||
}
|
||||
|
||||
const emailInput = this.shadow.getElementById('email-input') as HTMLInputElement
|
||||
emailInput.oninput = e => {
|
||||
this.email = (e.target as HTMLInputElement).value
|
||||
}
|
||||
|
||||
const infoInput = this.shadow.getElementById('info-input') as HTMLTextAreaElement
|
||||
infoInput.oninput = e => {
|
||||
this.additionalInfo = (e.target as HTMLTextAreaElement).value
|
||||
}
|
||||
|
||||
// Bind radio buttons
|
||||
this.shadow.querySelectorAll<HTMLInputElement>('input[name="dsrType"]').forEach(radio => {
|
||||
radio.onchange = () => {
|
||||
this.handleTypeSelect(radio.value as DSRRequestType)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private renderSuccess(styles: string): void {
|
||||
const t = this.t
|
||||
|
||||
this.shadow.innerHTML = `
|
||||
<style>${styles}</style>
|
||||
<div class="portal">
|
||||
<div class="success">
|
||||
<div class="success-icon">✓</div>
|
||||
<h2 class="success-title">${t.successTitle}</h2>
|
||||
<p class="success-message">
|
||||
${t.successMessage} ${this.email}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
// Register the custom element
|
||||
if (typeof customElements !== 'undefined') {
|
||||
customElements.define('breakpilot-dsr-portal', DSRPortalElement)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* BreakPilot Compliance SDK - Web Components
|
||||
*
|
||||
* Available components:
|
||||
* - <breakpilot-consent-banner>
|
||||
* - <breakpilot-dsr-portal>
|
||||
* - <breakpilot-compliance-score>
|
||||
*/
|
||||
|
||||
export { BreakPilotElement, COMMON_STYLES } from './base'
|
||||
export { ConsentBannerElement } from './consent-banner'
|
||||
export { DSRPortalElement } from './dsr-portal'
|
||||
export { ComplianceScoreElement } from './compliance-score'
|
||||
11
breakpilot-compliance-sdk/packages/vanilla/tsconfig.json
Normal file
11
breakpilot-compliance-sdk/packages/vanilla/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
32
breakpilot-compliance-sdk/packages/vanilla/tsup.config.ts
Normal file
32
breakpilot-compliance-sdk/packages/vanilla/tsup.config.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig([
|
||||
// Main bundle with web components
|
||||
{
|
||||
entry: ['src/index.ts'],
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
treeshake: true,
|
||||
},
|
||||
// Embed script (IIFE for <script> tag usage)
|
||||
{
|
||||
entry: ['src/embed.ts'],
|
||||
format: ['iife'],
|
||||
globalName: 'BreakPilotSDK',
|
||||
outDir: 'dist',
|
||||
minify: true,
|
||||
sourcemap: true,
|
||||
outExtension: () => ({ js: '.min.js' }),
|
||||
},
|
||||
// Embed script (ESM for modern usage)
|
||||
{
|
||||
entry: ['src/embed.ts'],
|
||||
format: ['esm', 'cjs'],
|
||||
dts: false,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
},
|
||||
])
|
||||
53
breakpilot-compliance-sdk/packages/vue/package.json
Normal file
53
breakpilot-compliance-sdk/packages/vue/package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@breakpilot/compliance-sdk-vue",
|
||||
"version": "0.0.1",
|
||||
"description": "BreakPilot Compliance SDK - Vue 3 Plugin",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"lint": "eslint src/",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@breakpilot/compliance-sdk-core": "workspace:*",
|
||||
"@breakpilot/compliance-sdk-types": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.3.0",
|
||||
"vue": "^3.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/breakpilot/compliance-sdk.git",
|
||||
"directory": "packages/vue"
|
||||
},
|
||||
"keywords": [
|
||||
"compliance",
|
||||
"gdpr",
|
||||
"dsgvo",
|
||||
"vue",
|
||||
"vue3",
|
||||
"plugin"
|
||||
]
|
||||
}
|
||||
@@ -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'
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
136
breakpilot-compliance-sdk/packages/vue/src/composables/useRAG.ts
Normal file
136
breakpilot-compliance-sdk/packages/vue/src/composables/useRAG.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
64
breakpilot-compliance-sdk/packages/vue/src/index.ts
Normal file
64
breakpilot-compliance-sdk/packages/vue/src/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @breakpilot/compliance-sdk-vue
|
||||
*
|
||||
* Vue 3 integration for BreakPilot Compliance SDK
|
||||
*
|
||||
* Usage:
|
||||
* import { createApp } from 'vue'
|
||||
* import { CompliancePlugin, useCompliance } from '@breakpilot/compliance-sdk-vue'
|
||||
*
|
||||
* const app = createApp(App)
|
||||
* app.use(CompliancePlugin, {
|
||||
* apiEndpoint: 'https://compliance.example.com/api/v1',
|
||||
* apiKey: 'pk_live_xxx',
|
||||
* tenantId: 'tenant_xxx'
|
||||
* })
|
||||
*/
|
||||
|
||||
// Plugin
|
||||
export {
|
||||
CompliancePlugin,
|
||||
useComplianceStore,
|
||||
COMPLIANCE_KEY,
|
||||
type CompliancePluginOptions,
|
||||
type ComplianceStore,
|
||||
} from './plugin'
|
||||
|
||||
// Composables
|
||||
export {
|
||||
useCompliance,
|
||||
useDSGVO,
|
||||
useRAG,
|
||||
useControls,
|
||||
useSecurity,
|
||||
type UseComplianceReturn,
|
||||
type UseDSGVOReturn,
|
||||
type UseRAGReturn,
|
||||
type UseControlsReturn,
|
||||
type UseSecurityReturn,
|
||||
type ScanOptions,
|
||||
} from './composables'
|
||||
|
||||
// Re-export types
|
||||
export type {
|
||||
SDKState,
|
||||
SDKAction,
|
||||
SDKStep,
|
||||
Risk,
|
||||
Control,
|
||||
Evidence,
|
||||
DSRRequest,
|
||||
ConsentRecord,
|
||||
ProcessingActivity,
|
||||
TOM,
|
||||
DSFA,
|
||||
RetentionPolicy,
|
||||
Obligation,
|
||||
SBOM,
|
||||
SBOMComponent,
|
||||
SecurityIssue,
|
||||
Vulnerability,
|
||||
SearchResponse,
|
||||
AssistantResponse,
|
||||
ChatMessage,
|
||||
} from '@breakpilot/compliance-sdk-types'
|
||||
164
breakpilot-compliance-sdk/packages/vue/src/plugin.ts
Normal file
164
breakpilot-compliance-sdk/packages/vue/src/plugin.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* BreakPilot Compliance SDK - Vue 3 Plugin
|
||||
*
|
||||
* Usage:
|
||||
* import { createApp } from 'vue'
|
||||
* import { CompliancePlugin } from '@breakpilot/compliance-sdk-vue'
|
||||
*
|
||||
* const app = createApp(App)
|
||||
* app.use(CompliancePlugin, {
|
||||
* apiEndpoint: 'https://compliance.example.com/api/v1',
|
||||
* apiKey: 'pk_live_xxx',
|
||||
* tenantId: 'tenant_xxx'
|
||||
* })
|
||||
*/
|
||||
|
||||
import { type App, reactive, inject, type InjectionKey, readonly } from 'vue'
|
||||
import {
|
||||
ComplianceClient,
|
||||
sdkReducer,
|
||||
DSGVOModule,
|
||||
ComplianceModule,
|
||||
RAGModule,
|
||||
SecurityModule,
|
||||
StateSyncManager,
|
||||
} from '@breakpilot/compliance-sdk-core'
|
||||
import type { SDKState, SDKAction } from '@breakpilot/compliance-sdk-types'
|
||||
import { createInitialState } from '@breakpilot/compliance-sdk-types'
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
export interface CompliancePluginOptions {
|
||||
apiEndpoint: string
|
||||
apiKey: string
|
||||
tenantId?: string
|
||||
autoSync?: boolean
|
||||
syncInterval?: number
|
||||
debug?: boolean
|
||||
}
|
||||
|
||||
export interface ComplianceStore {
|
||||
state: SDKState
|
||||
dispatch: (action: SDKAction) => void
|
||||
client: ComplianceClient
|
||||
dsgvo: DSGVOModule
|
||||
compliance: ComplianceModule
|
||||
rag: RAGModule
|
||||
security: SecurityModule
|
||||
syncManager: StateSyncManager
|
||||
isReady: boolean
|
||||
error: Error | null
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Injection Key
|
||||
// ============================================================================
|
||||
|
||||
export const COMPLIANCE_KEY: InjectionKey<ComplianceStore> = Symbol('compliance')
|
||||
|
||||
// ============================================================================
|
||||
// Plugin
|
||||
// ============================================================================
|
||||
|
||||
export const CompliancePlugin = {
|
||||
install(app: App, options: CompliancePluginOptions): void {
|
||||
// Create client
|
||||
const client = new ComplianceClient({
|
||||
apiEndpoint: options.apiEndpoint,
|
||||
apiKey: options.apiKey,
|
||||
tenantId: options.tenantId,
|
||||
})
|
||||
|
||||
// Create reactive state
|
||||
const state = reactive<SDKState>(createInitialState())
|
||||
|
||||
// Create dispatch function
|
||||
const dispatch = (action: SDKAction): void => {
|
||||
const newState = sdkReducer(state, action)
|
||||
Object.assign(state, newState)
|
||||
}
|
||||
|
||||
// Create modules
|
||||
const dsgvo = new DSGVOModule(state, dispatch, client)
|
||||
const compliance = new ComplianceModule(state, dispatch, client)
|
||||
const rag = new RAGModule(state, dispatch, client)
|
||||
const security = new SecurityModule(state, dispatch, client)
|
||||
|
||||
// Create sync manager
|
||||
const syncManager = new StateSyncManager({
|
||||
client,
|
||||
syncInterval: options.syncInterval || 30000,
|
||||
onStateLoaded: loadedState => {
|
||||
Object.assign(state, loadedState)
|
||||
},
|
||||
onSyncError: error => {
|
||||
console.error('[ComplianceSDK] Sync error:', error)
|
||||
store.error = error
|
||||
},
|
||||
})
|
||||
|
||||
// Create store
|
||||
const store = reactive<ComplianceStore>({
|
||||
state: state as SDKState,
|
||||
dispatch,
|
||||
client,
|
||||
dsgvo,
|
||||
compliance,
|
||||
rag,
|
||||
security,
|
||||
syncManager,
|
||||
isReady: false,
|
||||
error: null,
|
||||
})
|
||||
|
||||
// Initialize
|
||||
const initialize = async (): Promise<void> => {
|
||||
try {
|
||||
// Load state from server
|
||||
const savedState = await client.getState()
|
||||
if (savedState) {
|
||||
Object.assign(state, savedState.state)
|
||||
}
|
||||
|
||||
// Start sync if enabled
|
||||
if (options.autoSync !== false) {
|
||||
syncManager.start()
|
||||
}
|
||||
|
||||
store.isReady = true
|
||||
|
||||
if (options.debug) {
|
||||
console.log('[ComplianceSDK] Initialized:', state)
|
||||
}
|
||||
} catch (error) {
|
||||
store.error = error instanceof Error ? error : new Error(String(error))
|
||||
console.error('[ComplianceSDK] Initialization failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Start initialization
|
||||
initialize()
|
||||
|
||||
// Provide store
|
||||
app.provide(COMPLIANCE_KEY, store)
|
||||
|
||||
// Also make available as global property
|
||||
app.config.globalProperties.$compliance = store
|
||||
},
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Injection Helper
|
||||
// ============================================================================
|
||||
|
||||
export function useComplianceStore(): ComplianceStore {
|
||||
const store = inject(COMPLIANCE_KEY)
|
||||
if (!store) {
|
||||
throw new Error(
|
||||
'[ComplianceSDK] No store found. Did you forget to install the CompliancePlugin?'
|
||||
)
|
||||
}
|
||||
return store
|
||||
}
|
||||
11
breakpilot-compliance-sdk/packages/vue/tsconfig.json
Normal file
11
breakpilot-compliance-sdk/packages/vue/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
12
breakpilot-compliance-sdk/packages/vue/tsup.config.ts
Normal file
12
breakpilot-compliance-sdk/packages/vue/tsup.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
treeshake: true,
|
||||
external: ['vue'],
|
||||
})
|
||||
Reference in New Issue
Block a user