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
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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user