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 32s
CI / test-python-document-crawler (push) Successful in 18s
CI / test-python-dsms-gateway (push) Successful in 16s
- Remove 5 unused UCCA routes (wizard, stats, dsb-pool) from Go main.go - Delete 64 deprecated Go handlers (DSGVO, Vendors, Incidents, Drafting) - Delete legacy proxy routes (dsgvo, vendors) - Add LLM Audit Dashboard (3 tabs: Log, Nutzung, Compliance) with export - Add RBAC Admin UI (5 tabs: Mandanten, Namespaces, Rollen, Benutzer, LLM-Policies) - Add proxy routes for audit-llm and rbac to Go backend - Add Workshop, Portfolio, Roadmap proxy routes and frontends - Add LLM Audit + RBAC Admin to SDKSidebar Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
758 lines
29 KiB
TypeScript
758 lines
29 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import Link from 'next/link'
|
|
import { usePathname } from 'next/navigation'
|
|
import {
|
|
useSDK,
|
|
SDK_STEPS,
|
|
SDK_PACKAGES,
|
|
getStepsForPackage,
|
|
type SDKPackageId,
|
|
type SDKStep,
|
|
type RAGCorpusStatus,
|
|
} from '@/lib/sdk'
|
|
|
|
// =============================================================================
|
|
// ICONS
|
|
// =============================================================================
|
|
|
|
const CheckIcon = () => (
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
)
|
|
|
|
const LockIcon = () => (
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
</svg>
|
|
)
|
|
|
|
const WarningIcon = () => (
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
)
|
|
|
|
const ChevronDownIcon = ({ className = '' }: { className?: string }) => (
|
|
<svg className={`w-4 h-4 ${className}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
</svg>
|
|
)
|
|
|
|
const CollapseIcon = ({ collapsed }: { collapsed: boolean }) => (
|
|
<svg
|
|
className={`w-5 h-5 transition-transform duration-300 ${collapsed ? 'rotate-180' : ''}`}
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
|
|
</svg>
|
|
)
|
|
|
|
// =============================================================================
|
|
// PROGRESS BAR
|
|
// =============================================================================
|
|
|
|
interface ProgressBarProps {
|
|
value: number
|
|
className?: string
|
|
}
|
|
|
|
function ProgressBar({ value, className = '' }: ProgressBarProps) {
|
|
return (
|
|
<div className={`h-1 bg-gray-200 rounded-full overflow-hidden ${className}`}>
|
|
<div
|
|
className="h-full bg-purple-600 rounded-full transition-all duration-500"
|
|
style={{ width: `${Math.min(100, Math.max(0, value))}%` }}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// PACKAGE INDICATOR
|
|
// =============================================================================
|
|
|
|
interface PackageIndicatorProps {
|
|
packageId: SDKPackageId
|
|
order: number
|
|
name: string
|
|
icon: string
|
|
completion: number
|
|
isActive: boolean
|
|
isExpanded: boolean
|
|
isLocked: boolean
|
|
onToggle: () => void
|
|
collapsed: boolean
|
|
}
|
|
|
|
function PackageIndicator({
|
|
order,
|
|
name,
|
|
icon,
|
|
completion,
|
|
isActive,
|
|
isExpanded,
|
|
isLocked,
|
|
onToggle,
|
|
collapsed,
|
|
}: PackageIndicatorProps) {
|
|
if (collapsed) {
|
|
return (
|
|
<button
|
|
onClick={onToggle}
|
|
className={`w-full flex items-center justify-center py-3 transition-colors ${
|
|
isActive
|
|
? 'bg-purple-50 border-l-4 border-purple-600'
|
|
: isLocked
|
|
? 'border-l-4 border-transparent opacity-50'
|
|
: 'hover:bg-gray-50 border-l-4 border-transparent'
|
|
}`}
|
|
title={`${order}. ${name} (${completion}%)`}
|
|
>
|
|
<div
|
|
className={`w-8 h-8 rounded-full flex items-center justify-center text-lg ${
|
|
isLocked
|
|
? 'bg-gray-200 text-gray-400'
|
|
: isActive
|
|
? 'bg-purple-600 text-white'
|
|
: completion === 100
|
|
? 'bg-green-500 text-white'
|
|
: 'bg-gray-200 text-gray-600'
|
|
}`}
|
|
>
|
|
{isLocked ? <LockIcon /> : completion === 100 ? <CheckIcon /> : icon}
|
|
</div>
|
|
</button>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<button
|
|
onClick={onToggle}
|
|
disabled={isLocked}
|
|
className={`w-full flex items-center justify-between px-4 py-3 text-left transition-colors ${
|
|
isLocked
|
|
? 'opacity-50 cursor-not-allowed'
|
|
: isActive
|
|
? 'bg-purple-50 border-l-4 border-purple-600'
|
|
: 'hover:bg-gray-50 border-l-4 border-transparent'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div
|
|
className={`w-8 h-8 rounded-full flex items-center justify-center text-lg ${
|
|
isLocked
|
|
? 'bg-gray-200 text-gray-400'
|
|
: isActive
|
|
? 'bg-purple-600 text-white'
|
|
: completion === 100
|
|
? 'bg-green-500 text-white'
|
|
: 'bg-gray-200 text-gray-600'
|
|
}`}
|
|
>
|
|
{isLocked ? <LockIcon /> : completion === 100 ? <CheckIcon /> : icon}
|
|
</div>
|
|
<div>
|
|
<div className={`font-medium text-sm ${isActive ? 'text-purple-900' : isLocked ? 'text-gray-400' : 'text-gray-700'}`}>
|
|
{order}. {name}
|
|
</div>
|
|
<div className="text-xs text-gray-500">{completion}%</div>
|
|
</div>
|
|
</div>
|
|
{!isLocked && <ChevronDownIcon className={`transition-transform ${isExpanded ? 'rotate-180' : ''}`} />}
|
|
</button>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// STEP ITEM
|
|
// =============================================================================
|
|
|
|
interface StepItemProps {
|
|
step: SDKStep
|
|
isActive: boolean
|
|
isCompleted: boolean
|
|
isLocked: boolean
|
|
checkpointStatus?: 'passed' | 'failed' | 'warning' | 'pending'
|
|
collapsed: boolean
|
|
}
|
|
|
|
function StepItem({ step, isActive, isCompleted, isLocked, checkpointStatus, collapsed }: StepItemProps) {
|
|
const content = (
|
|
<div
|
|
className={`flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${
|
|
collapsed ? 'justify-center' : ''
|
|
} ${
|
|
isActive
|
|
? 'bg-purple-100 text-purple-900 font-medium'
|
|
: isLocked
|
|
? 'text-gray-400 cursor-not-allowed'
|
|
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
|
|
}`}
|
|
title={collapsed ? step.name : undefined}
|
|
>
|
|
{/* Step indicator */}
|
|
<div className="flex-shrink-0">
|
|
{isCompleted ? (
|
|
<div className="w-5 h-5 rounded-full bg-green-500 text-white flex items-center justify-center">
|
|
<CheckIcon />
|
|
</div>
|
|
) : isLocked ? (
|
|
<div className="w-5 h-5 rounded-full bg-gray-200 text-gray-400 flex items-center justify-center">
|
|
<LockIcon />
|
|
</div>
|
|
) : isActive ? (
|
|
<div className="w-5 h-5 rounded-full bg-purple-600 flex items-center justify-center">
|
|
<div className="w-2 h-2 rounded-full bg-white" />
|
|
</div>
|
|
) : (
|
|
<div className="w-5 h-5 rounded-full border-2 border-gray-300" />
|
|
)}
|
|
</div>
|
|
|
|
{/* Step name - hidden when collapsed */}
|
|
{!collapsed && <span className="flex-1 truncate">{step.nameShort}</span>}
|
|
|
|
{/* Checkpoint status - hidden when collapsed */}
|
|
{!collapsed && checkpointStatus && checkpointStatus !== 'pending' && (
|
|
<div className="flex-shrink-0">
|
|
{checkpointStatus === 'passed' ? (
|
|
<div className="w-4 h-4 rounded-full bg-green-100 text-green-600 flex items-center justify-center">
|
|
<CheckIcon />
|
|
</div>
|
|
) : checkpointStatus === 'failed' ? (
|
|
<div className="w-4 h-4 rounded-full bg-red-100 text-red-600 flex items-center justify-center">
|
|
<span className="text-xs font-bold">!</span>
|
|
</div>
|
|
) : (
|
|
<div className="w-4 h-4 rounded-full bg-yellow-100 text-yellow-600 flex items-center justify-center">
|
|
<WarningIcon />
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
|
|
if (isLocked) {
|
|
return content
|
|
}
|
|
|
|
return (
|
|
<Link href={step.url} className="block">
|
|
{content}
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// ADDITIONAL MODULE ITEM
|
|
// =============================================================================
|
|
|
|
interface AdditionalModuleItemProps {
|
|
href: string
|
|
icon: React.ReactNode
|
|
label: string
|
|
isActive: boolean
|
|
collapsed: boolean
|
|
}
|
|
|
|
function AdditionalModuleItem({ href, icon, label, isActive, collapsed }: 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' : ''
|
|
} ${
|
|
isActive
|
|
? 'bg-purple-100 text-purple-900 font-medium'
|
|
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
|
|
}`
|
|
|
|
if (isExternal) {
|
|
return (
|
|
<a href={href} target="_blank" rel="noopener noreferrer" className={className} title={collapsed ? label : undefined}>
|
|
{icon}
|
|
{!collapsed && (
|
|
<span className="flex items-center gap-1">
|
|
{label}
|
|
<svg className="w-3 h-3 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
</svg>
|
|
</span>
|
|
)}
|
|
</a>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Link href={href} className={className} title={collapsed ? label : undefined}>
|
|
{icon}
|
|
{!collapsed && <span>{label}</span>}
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// MAIN SIDEBAR
|
|
// =============================================================================
|
|
|
|
interface SDKSidebarProps {
|
|
collapsed?: boolean
|
|
onCollapsedChange?: (collapsed: boolean) => void
|
|
}
|
|
|
|
// =============================================================================
|
|
// CORPUS STALENESS INFO
|
|
// =============================================================================
|
|
|
|
function CorpusStalenessInfo({ ragCorpusStatus }: { ragCorpusStatus: RAGCorpusStatus }) {
|
|
const collections = ragCorpusStatus.collections
|
|
const collectionNames = Object.keys(collections)
|
|
if (collectionNames.length === 0) return null
|
|
|
|
// Check if corpus was updated after the last fetch (simplified: show last update time)
|
|
const lastUpdated = collectionNames.reduce((latest, name) => {
|
|
const updated = new Date(collections[name].last_updated)
|
|
return updated > latest ? updated : latest
|
|
}, new Date(0))
|
|
|
|
const daysSinceUpdate = Math.floor((Date.now() - lastUpdated.getTime()) / (1000 * 60 * 60 * 24))
|
|
const totalChunks = collectionNames.reduce((sum, name) => sum + collections[name].chunks_count, 0)
|
|
|
|
return (
|
|
<div className="px-4 py-2 border-b border-gray-100">
|
|
<div className="flex items-center gap-2 text-xs">
|
|
<div className={`w-2 h-2 rounded-full flex-shrink-0 ${daysSinceUpdate > 30 ? 'bg-amber-400' : 'bg-green-400'}`} />
|
|
<span className="text-gray-500 truncate">
|
|
RAG Corpus: {totalChunks} Chunks
|
|
</span>
|
|
</div>
|
|
{daysSinceUpdate > 30 && (
|
|
<div className="mt-1 text-xs text-amber-600 bg-amber-50 rounded px-2 py-1">
|
|
Corpus {daysSinceUpdate}d alt — Re-Evaluation empfohlen
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarProps) {
|
|
const pathname = usePathname()
|
|
const { state, packageCompletion, completionPercentage, getCheckpointStatus } = useSDK()
|
|
const [expandedPackages, setExpandedPackages] = React.useState<Record<SDKPackageId, boolean>>({
|
|
'vorbereitung': true,
|
|
'analyse': false,
|
|
'dokumentation': false,
|
|
'rechtliche-texte': false,
|
|
'betrieb': false,
|
|
})
|
|
|
|
// Auto-expand current package
|
|
React.useEffect(() => {
|
|
const currentStep = SDK_STEPS.find(s => s.url === pathname)
|
|
if (currentStep) {
|
|
setExpandedPackages(prev => ({
|
|
...prev,
|
|
[currentStep.package]: true,
|
|
}))
|
|
}
|
|
}, [pathname])
|
|
|
|
const togglePackage = (packageId: SDKPackageId) => {
|
|
setExpandedPackages(prev => ({ ...prev, [packageId]: !prev[packageId] }))
|
|
}
|
|
|
|
const isStepLocked = (step: SDKStep): boolean => {
|
|
if (state.preferences?.allowParallelWork) return false
|
|
return step.prerequisiteSteps.some(prereq => !state.completedSteps.includes(prereq))
|
|
}
|
|
|
|
const isPackageLocked = (packageId: SDKPackageId): boolean => {
|
|
if (state.preferences?.allowParallelWork) return false
|
|
const pkg = SDK_PACKAGES.find(p => p.id === packageId)
|
|
if (!pkg || pkg.order === 1) return false
|
|
|
|
// Check if previous package is complete
|
|
const prevPkg = SDK_PACKAGES.find(p => p.order === pkg.order - 1)
|
|
if (!prevPkg) return false
|
|
|
|
return packageCompletion[prevPkg.id] < 100
|
|
}
|
|
|
|
const getStepCheckpointStatus = (step: SDKStep): 'passed' | 'failed' | 'warning' | 'pending' => {
|
|
const status = getCheckpointStatus(step.checkpointId)
|
|
if (!status) return 'pending'
|
|
if (status.passed) return 'passed'
|
|
if (status.errors.length > 0) return 'failed'
|
|
if (status.warnings.length > 0) return 'warning'
|
|
return 'pending'
|
|
}
|
|
|
|
const isStepActive = (stepUrl: string) => pathname === stepUrl
|
|
|
|
const isPackageActive = (packageId: SDKPackageId) => {
|
|
const steps = getStepsForPackage(packageId)
|
|
return steps.some(s => s.url === pathname)
|
|
}
|
|
|
|
// Filter steps based on visibleWhen conditions
|
|
const getVisibleStepsForPackage = (packageId: SDKPackageId): SDKStep[] => {
|
|
const steps = getStepsForPackage(packageId)
|
|
return steps.filter(step => {
|
|
if (step.visibleWhen) return step.visibleWhen(state)
|
|
return true
|
|
})
|
|
}
|
|
|
|
return (
|
|
<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' : ''}`}>
|
|
<Link href="/sdk" className={`flex items-center gap-3 ${collapsed ? 'justify-center' : ''}`}>
|
|
<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">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
{!collapsed && (
|
|
<div>
|
|
<div className="font-bold text-gray-900">AI Compliance</div>
|
|
<div className="text-xs text-gray-500">SDK</div>
|
|
</div>
|
|
)}
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Overall Progress - hidden when collapsed */}
|
|
{!collapsed && (
|
|
<div className="px-4 py-3 border-b border-gray-100">
|
|
<div className="flex items-center justify-between text-sm mb-2">
|
|
<span className="text-gray-600">Gesamtfortschritt</span>
|
|
<span className="font-medium text-purple-600">{completionPercentage}%</span>
|
|
</div>
|
|
<ProgressBar value={completionPercentage} />
|
|
</div>
|
|
)}
|
|
|
|
{/* RAG Corpus Staleness Badge */}
|
|
{!collapsed && state.ragCorpusStatus && (
|
|
<CorpusStalenessInfo ragCorpusStatus={state.ragCorpusStatus} />
|
|
)}
|
|
|
|
{/* Navigation - 5 Packages */}
|
|
<nav className="flex-1 overflow-y-auto">
|
|
{SDK_PACKAGES.map(pkg => {
|
|
const steps = getVisibleStepsForPackage(pkg.id)
|
|
const isLocked = isPackageLocked(pkg.id)
|
|
const isActive = isPackageActive(pkg.id)
|
|
|
|
return (
|
|
<div key={pkg.id} className={pkg.order > 1 ? 'border-t border-gray-100' : ''}>
|
|
<PackageIndicator
|
|
packageId={pkg.id}
|
|
order={pkg.order}
|
|
name={pkg.name}
|
|
icon={pkg.icon}
|
|
completion={packageCompletion[pkg.id]}
|
|
isActive={isActive}
|
|
isExpanded={expandedPackages[pkg.id]}
|
|
isLocked={isLocked}
|
|
onToggle={() => togglePackage(pkg.id)}
|
|
collapsed={collapsed}
|
|
/>
|
|
{expandedPackages[pkg.id] && !isLocked && (
|
|
<div className="py-1">
|
|
{steps.map(step => (
|
|
<StepItem
|
|
key={step.id}
|
|
step={step}
|
|
isActive={isStepActive(step.url)}
|
|
isCompleted={state.completedSteps.includes(step.id)}
|
|
isLocked={isStepLocked(step)}
|
|
checkpointStatus={getStepCheckpointStatus(step)}
|
|
collapsed={collapsed}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
|
|
{/* Maschinenrecht / CE */}
|
|
<div className="border-t border-gray-100 py-2">
|
|
{!collapsed && (
|
|
<div className="px-4 py-2 text-xs font-medium text-gray-400 uppercase tracking-wider">
|
|
Maschinenrecht / CE
|
|
</div>
|
|
)}
|
|
<AdditionalModuleItem
|
|
href="/sdk/iace"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
</svg>
|
|
}
|
|
label="CE-Compliance (IACE)"
|
|
isActive={pathname?.startsWith('/sdk/iace') ?? false}
|
|
collapsed={collapsed}
|
|
/>
|
|
</div>
|
|
|
|
{/* Additional Modules */}
|
|
<div className="border-t border-gray-100 py-2">
|
|
{!collapsed && (
|
|
<div className="px-4 py-2 text-xs font-medium text-gray-400 uppercase tracking-wider">
|
|
Zusatzmodule
|
|
</div>
|
|
)}
|
|
<AdditionalModuleItem
|
|
href="/sdk/rag"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
/>
|
|
</svg>
|
|
}
|
|
label="Legal RAG"
|
|
isActive={pathname === '/sdk/rag'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/quality"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
}
|
|
label="AI Quality"
|
|
isActive={pathname === '/sdk/quality'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/security-backlog"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
/>
|
|
</svg>
|
|
}
|
|
label="Security Backlog"
|
|
isActive={pathname === '/sdk/security-backlog'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/compliance-hub"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
</svg>
|
|
}
|
|
label="Compliance Hub"
|
|
isActive={pathname === '/sdk/compliance-hub'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/dsms"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
</svg>
|
|
}
|
|
label="DSMS"
|
|
isActive={pathname === '/sdk/dsms'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/sdk-flow"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
</svg>
|
|
}
|
|
label="SDK Flow"
|
|
isActive={pathname === '/sdk/sdk-flow'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/architecture"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />
|
|
</svg>
|
|
}
|
|
label="Architektur"
|
|
isActive={pathname === '/sdk/architecture'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/agents"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
</svg>
|
|
}
|
|
label="Agenten"
|
|
isActive={pathname?.startsWith('/sdk/agents') ?? false}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/workshop"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
}
|
|
label="Workshop"
|
|
isActive={pathname === '/sdk/workshop'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/portfolio"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
|
</svg>
|
|
}
|
|
label="Portfolio"
|
|
isActive={pathname === '/sdk/portfolio'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/roadmap"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
|
|
</svg>
|
|
}
|
|
label="Roadmap"
|
|
isActive={pathname === '/sdk/roadmap'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/audit-llm"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
</svg>
|
|
}
|
|
label="LLM Audit"
|
|
isActive={pathname === '/sdk/audit-llm'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/rbac"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
</svg>
|
|
}
|
|
label="RBAC Admin"
|
|
isActive={pathname === '/sdk/rbac'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="/sdk/catalog-manager"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
|
|
</svg>
|
|
}
|
|
label="Kataloge"
|
|
isActive={pathname === '/sdk/catalog-manager'}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="https://macmini:3006"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
|
</svg>
|
|
}
|
|
label="Developer Portal"
|
|
isActive={false}
|
|
collapsed={collapsed}
|
|
/>
|
|
<AdditionalModuleItem
|
|
href="https://macmini:8011"
|
|
icon={
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
|
</svg>
|
|
}
|
|
label="SDK Dokumentation"
|
|
isActive={false}
|
|
collapsed={collapsed}
|
|
/>
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Footer */}
|
|
<div className={`${collapsed ? 'p-2' : 'p-4'} border-t border-gray-200 bg-gray-50`}>
|
|
{/* Collapse Toggle */}
|
|
<button
|
|
onClick={() => onCollapsedChange?.(!collapsed)}
|
|
className={`w-full flex items-center justify-center gap-2 ${collapsed ? 'p-2' : 'px-4 py-2'} text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors ${collapsed ? '' : 'mb-2'}`}
|
|
title={collapsed ? 'Sidebar erweitern' : 'Sidebar einklappen'}
|
|
>
|
|
<CollapseIcon collapsed={collapsed} />
|
|
{!collapsed && <span>Einklappen</span>}
|
|
</button>
|
|
|
|
{/* Export Button */}
|
|
{!collapsed && (
|
|
<button
|
|
onClick={() => {}}
|
|
className="w-full flex items-center justify-center gap-2 px-4 py-2 text-sm text-purple-600 hover:text-purple-700 hover:bg-purple-50 rounded-lg transition-colors"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"
|
|
/>
|
|
</svg>
|
|
<span>Exportieren</span>
|
|
</button>
|
|
)}
|
|
</div>
|
|
</aside>
|
|
)
|
|
}
|