feat(sdk): Multi-Projekt-Architektur — mehrere Projekte pro Tenant
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 33s
CI / test-python-backend-compliance (push) Successful in 34s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 19s

Jeder Tenant kann jetzt mehrere Compliance-Projekte anlegen (z.B. verschiedene
Produkte, Tochterunternehmen). CompanyProfile ist pro Projekt kopierbar und
danach unabhaengig editierbar. Multi-Tab-Support via separater BroadcastChannel
und localStorage Keys pro Projekt.

- Migration 039: compliance_projects Tabelle, sdk_states.project_id
- Backend: FastAPI CRUD-Routes fuer Projekte mit Tenant-Isolation
- Frontend: ProjectSelector UI, SDKProvider mit projectId, URL ?project=
- State API: UPSERT auf (tenant_id, project_id) mit Abwaertskompatibilitaet
- Tests: pytest fuer Model-Validierung, Row-Konvertierung, Tenant-Isolation
- Docs: MKDocs Seite, CLAUDE.md, Backend README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-09 14:53:50 +01:00
parent d3fc4cdaaa
commit 0affa4eb66
19 changed files with 1833 additions and 102 deletions

View File

@@ -13,6 +13,15 @@ import {
type RAGCorpusStatus,
} from '@/lib/sdk'
/**
* Append ?project= to a URL if a projectId is set
*/
function withProject(url: string, projectId?: string): string {
if (!projectId) return url
const separator = url.includes('?') ? '&' : '?'
return `${url}${separator}project=${projectId}`
}
// =============================================================================
// ICONS
// =============================================================================
@@ -179,9 +188,10 @@ interface StepItemProps {
isLocked: boolean
checkpointStatus?: 'passed' | 'failed' | 'warning' | 'pending'
collapsed: boolean
projectId?: string
}
function StepItem({ step, isActive, isCompleted, isLocked, checkpointStatus, collapsed }: StepItemProps) {
function StepItem({ step, isActive, isCompleted, isLocked, checkpointStatus, collapsed, projectId }: StepItemProps) {
const content = (
<div
className={`flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${
@@ -243,7 +253,7 @@ function StepItem({ step, isActive, isCompleted, isLocked, checkpointStatus, col
}
return (
<Link href={step.url} className="block">
<Link href={withProject(step.url, projectId)} className="block">
{content}
</Link>
)
@@ -259,9 +269,10 @@ interface AdditionalModuleItemProps {
label: string
isActive: boolean
collapsed: boolean
projectId?: string
}
function AdditionalModuleItem({ href, icon, label, isActive, collapsed }: AdditionalModuleItemProps) {
function AdditionalModuleItem({ href, icon, label, isActive, collapsed, projectId }: AdditionalModuleItemProps) {
const isExternal = href.startsWith('http')
const className = `flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${
collapsed ? 'justify-center' : ''
@@ -288,7 +299,7 @@ function AdditionalModuleItem({ href, icon, label, isActive, collapsed }: Additi
}
return (
<Link href={href} className={className} title={collapsed ? label : undefined}>
<Link href={withProject(href, projectId)} className={className} title={collapsed ? label : undefined}>
{icon}
{!collapsed && <span>{label}</span>}
</Link>
@@ -341,7 +352,7 @@ function CorpusStalenessInfo({ ragCorpusStatus }: { ragCorpusStatus: RAGCorpusSt
export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarProps) {
const pathname = usePathname()
const { state, packageCompletion, completionPercentage, getCheckpointStatus, setCustomerType } = useSDK()
const { state, packageCompletion, completionPercentage, getCheckpointStatus, projectId } = useSDK()
const [pendingCRCount, setPendingCRCount] = React.useState(0)
// Poll pending change-request count every 60s
@@ -430,12 +441,10 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
<aside className={`fixed left-0 top-0 h-screen ${collapsed ? 'w-16' : 'w-64'} bg-white border-r border-gray-200 flex flex-col z-40 transition-all duration-300`}>
{/* Header */}
<div className={`p-4 border-b border-gray-200 ${collapsed ? 'flex justify-center' : ''}`}>
<button
onClick={() => {
setCustomerType(null as any)
window.location.href = '/sdk'
}}
<Link
href="/sdk"
className={`flex items-center gap-3 ${collapsed ? 'justify-center' : ''} hover:opacity-80 transition-opacity`}
title="Zurueck zur Projektliste"
>
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-purple-600 to-indigo-600 flex items-center justify-center flex-shrink-0">
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -450,10 +459,12 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
{!collapsed && (
<div className="text-left">
<div className="font-bold text-gray-900">AI Compliance</div>
<div className="text-xs text-gray-500">SDK</div>
<div className="text-xs text-gray-500 truncate max-w-[140px]">
{state.projectInfo?.name || 'SDK'}
</div>
</div>
)}
</button>
</Link>
</div>
{/* Overall Progress - hidden when collapsed */}
@@ -504,6 +515,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
isLocked={isStepLocked(step)}
checkpointStatus={getStepCheckpointStatus(step)}
collapsed={collapsed}
projectId={projectId}
/>
))}
</div>
@@ -530,6 +542,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="CE-Compliance (IACE)"
isActive={pathname?.startsWith('/sdk/iace') ?? false}
collapsed={collapsed}
projectId={projectId}
/>
</div>
@@ -555,6 +568,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="Legal RAG"
isActive={pathname === '/sdk/rag'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/quality"
@@ -571,6 +585,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="AI Quality"
isActive={pathname === '/sdk/quality'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/security-backlog"
@@ -587,6 +602,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="Security Backlog"
isActive={pathname === '/sdk/security-backlog'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/compliance-hub"
@@ -599,6 +615,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="Compliance Hub"
isActive={pathname === '/sdk/compliance-hub'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/dsms"
@@ -611,6 +628,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="DSMS"
isActive={pathname === '/sdk/dsms'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/sdk-flow"
@@ -623,6 +641,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="SDK Flow"
isActive={pathname === '/sdk/sdk-flow'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/architecture"
@@ -635,6 +654,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="Architektur"
isActive={pathname === '/sdk/architecture'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/agents"
@@ -647,6 +667,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="Agenten"
isActive={pathname?.startsWith('/sdk/agents') ?? false}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/workshop"
@@ -659,6 +680,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="Workshop"
isActive={pathname === '/sdk/workshop'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/portfolio"
@@ -671,6 +693,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="Portfolio"
isActive={pathname === '/sdk/portfolio'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/roadmap"
@@ -683,6 +706,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="Roadmap"
isActive={pathname === '/sdk/roadmap'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/isms"
@@ -695,6 +719,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="ISMS (ISO 27001)"
isActive={pathname === '/sdk/isms'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/audit-llm"
@@ -707,6 +732,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="LLM Audit"
isActive={pathname === '/sdk/audit-llm'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/rbac"
@@ -719,6 +745,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="RBAC Admin"
isActive={pathname === '/sdk/rbac'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/catalog-manager"
@@ -731,6 +758,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="Kataloge"
isActive={pathname === '/sdk/catalog-manager'}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="/sdk/api-docs"
@@ -743,9 +771,10 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="API-Referenz"
isActive={pathname === '/sdk/api-docs'}
collapsed={collapsed}
projectId={projectId}
/>
<Link
href="/sdk/change-requests"
href={withProject('/sdk/change-requests', projectId)}
className={`flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${
collapsed ? 'justify-center' : ''
} ${
@@ -784,6 +813,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="Developer Portal"
isActive={false}
collapsed={collapsed}
projectId={projectId}
/>
<AdditionalModuleItem
href="https://macmini:8011"
@@ -796,6 +826,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
label="SDK Dokumentation"
isActive={false}
collapsed={collapsed}
projectId={projectId}
/>
</div>
</nav>