[split-required] Split final 43 files (500-668 LOC) to complete refactoring
klausur-service (11 files): - cv_gutter_repair, ocr_pipeline_regression, upload_api - ocr_pipeline_sessions, smart_spell, nru_worksheet_generator - ocr_pipeline_overlays, mail/aggregator, zeugnis_api - cv_syllable_detect, self_rag backend-lehrer (17 files): - classroom_engine/suggestions, generators/quiz_generator - worksheets_api, llm_gateway/comparison, state_engine_api - classroom/models (→ 4 submodules), services/file_processor - alerts_agent/api/wizard+digests+routes, content_generators/pdf - classroom/routes/sessions, llm_gateway/inference - classroom_engine/analytics, auth/keycloak_auth - alerts_agent/processing/rule_engine, ai_processor/print_versions agent-core (5 files): - brain/memory_store, brain/knowledge_graph, brain/context_manager - orchestrator/supervisor, sessions/session_manager admin-lehrer (5 components): - GridOverlay, StepGridReview, DevOpsPipelineSidebar - DataFlowDiagram, sbom/wizard/page website (2 files): - DependencyMap, lehrer/abitur-archiv Other: nibis_ingestion, grid_detection_service, export-doclayout-onnx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -279,255 +279,7 @@ export function DevOpsPipelineSidebar({
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Responsive Version with Mobile FAB + Drawer
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Responsive DevOps Sidebar mit Mobile FAB + Drawer
|
||||
*
|
||||
* Desktop (xl+): Fixierte Sidebar rechts
|
||||
* Mobile/Tablet: Floating Action Button unten rechts, oeffnet Drawer
|
||||
*/
|
||||
export function DevOpsPipelineSidebarResponsive({
|
||||
currentTool,
|
||||
compact = false,
|
||||
className = '',
|
||||
fabPosition = 'bottom-right',
|
||||
}: DevOpsPipelineSidebarResponsiveProps) {
|
||||
const [isMobileOpen, setIsMobileOpen] = useState(false)
|
||||
const liveStatus = usePipelineLiveStatus()
|
||||
|
||||
// Close drawer on escape key
|
||||
useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') setIsMobileOpen(false)
|
||||
}
|
||||
window.addEventListener('keydown', handleEscape)
|
||||
return () => window.removeEventListener('keydown', handleEscape)
|
||||
}, [])
|
||||
|
||||
// Prevent body scroll when drawer is open
|
||||
useEffect(() => {
|
||||
if (isMobileOpen) {
|
||||
document.body.style.overflow = 'hidden'
|
||||
} else {
|
||||
document.body.style.overflow = ''
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = ''
|
||||
}
|
||||
}, [isMobileOpen])
|
||||
|
||||
const fabPositionClasses = fabPosition === 'bottom-right'
|
||||
? 'right-4 bottom-20'
|
||||
: 'left-4 bottom-20'
|
||||
|
||||
// Calculate total badge count for FAB
|
||||
const totalBadgeCount = liveStatus
|
||||
? liveStatus.backlogCount + liveStatus.securityFindingsCount
|
||||
: 0
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Desktop: Fixed Sidebar */}
|
||||
<div className={`hidden xl:block fixed right-6 top-24 w-64 z-10 ${className}`}>
|
||||
<DevOpsPipelineSidebar currentTool={currentTool} compact={compact} />
|
||||
</div>
|
||||
|
||||
{/* Mobile/Tablet: FAB */}
|
||||
<button
|
||||
onClick={() => setIsMobileOpen(true)}
|
||||
className={`xl:hidden fixed ${fabPositionClasses} z-40 w-14 h-14 bg-gradient-to-r from-orange-500 to-amber-500 text-white rounded-full shadow-lg hover:shadow-xl transition-all flex items-center justify-center group`}
|
||||
aria-label="DevOps Pipeline Navigation oeffnen"
|
||||
>
|
||||
<ServerIcon />
|
||||
{/* Badge indicator */}
|
||||
{totalBadgeCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white text-xs font-bold rounded-full flex items-center justify-center">
|
||||
{totalBadgeCount > 9 ? '9+' : totalBadgeCount}
|
||||
</span>
|
||||
)}
|
||||
{/* Pulse indicator when pipeline is running */}
|
||||
{liveStatus?.isRunning && (
|
||||
<span className="absolute -top-1 -right-1 w-3 h-3 bg-green-400 rounded-full animate-pulse" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Mobile/Tablet: Drawer Overlay */}
|
||||
{isMobileOpen && (
|
||||
<div className="xl:hidden fixed inset-0 z-50">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/50 backdrop-blur-sm transition-opacity"
|
||||
onClick={() => setIsMobileOpen(false)}
|
||||
/>
|
||||
|
||||
{/* Drawer */}
|
||||
<div className="absolute right-0 top-0 bottom-0 w-80 max-w-[85vw] bg-white dark:bg-gray-900 shadow-2xl transform transition-transform animate-slide-in-right">
|
||||
{/* Drawer Header */}
|
||||
<div className="flex items-center justify-between px-4 py-4 border-b border-slate-200 dark:border-gray-700 bg-gradient-to-r from-orange-50 to-amber-50 dark:from-orange-900/20 dark:to-amber-900/20">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-orange-600 dark:text-orange-400">
|
||||
<ServerIcon />
|
||||
</span>
|
||||
<span className="font-semibold text-slate-700 dark:text-slate-200">
|
||||
DevOps Pipeline
|
||||
</span>
|
||||
{liveStatus?.isRunning && (
|
||||
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsMobileOpen(false)}
|
||||
className="p-2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 rounded-lg hover:bg-slate-100 dark:hover:bg-gray-800 transition-colors"
|
||||
aria-label="Schliessen"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Drawer Content */}
|
||||
<div className="p-4 space-y-4 overflow-y-auto max-h-[calc(100vh-80px)]">
|
||||
{/* Tool Links */}
|
||||
<div className="space-y-2">
|
||||
{DEVOPS_PIPELINE_MODULES.map((tool) => (
|
||||
<Link
|
||||
key={tool.id}
|
||||
href={tool.href}
|
||||
onClick={() => setIsMobileOpen(false)}
|
||||
className={`flex items-center gap-3 px-4 py-3 rounded-xl transition-all ${
|
||||
currentTool === tool.id
|
||||
? 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300 font-medium shadow-sm'
|
||||
: 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-gray-800'
|
||||
}`}
|
||||
>
|
||||
<ToolIcon id={tool.id} />
|
||||
<div className="flex-1">
|
||||
<div className="text-base font-medium">{tool.name}</div>
|
||||
<div className="text-sm text-slate-500 dark:text-slate-500">
|
||||
{tool.description}
|
||||
</div>
|
||||
</div>
|
||||
{/* Status badges */}
|
||||
{tool.id === 'tests' && liveStatus && (
|
||||
<StatusBadge count={liveStatus.backlogCount} type="backlog" />
|
||||
)}
|
||||
{tool.id === 'security' && liveStatus && (
|
||||
<StatusBadge count={liveStatus.securityFindingsCount} type="security" />
|
||||
)}
|
||||
{currentTool === tool.id && (
|
||||
<span className="flex-shrink-0 w-2.5 h-2.5 bg-orange-500 rounded-full" />
|
||||
)}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Pipeline Flow Visualization */}
|
||||
<div className="pt-4 border-t border-slate-200 dark:border-gray-700">
|
||||
<div className="text-sm font-medium text-slate-500 dark:text-slate-400 mb-3">
|
||||
Pipeline Flow
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-2 p-4 bg-slate-50 dark:bg-gray-800 rounded-xl">
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="text-2xl">📝</span>
|
||||
<span className="text-xs text-slate-500 mt-1">Code</span>
|
||||
</div>
|
||||
<span className="text-slate-400">→</span>
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="text-2xl">🏗️</span>
|
||||
<span className="text-xs text-slate-500 mt-1">Build</span>
|
||||
</div>
|
||||
<span className="text-slate-400">→</span>
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="text-2xl">✅</span>
|
||||
<span className="text-xs text-slate-500 mt-1">Test</span>
|
||||
</div>
|
||||
<span className="text-slate-400">→</span>
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="text-2xl">🚀</span>
|
||||
<span className="text-xs text-slate-500 mt-1">Deploy</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Info */}
|
||||
<div className="pt-4 border-t border-slate-200 dark:border-gray-700">
|
||||
<div className="text-sm text-slate-600 dark:text-slate-400 p-3 bg-slate-50 dark:bg-gray-800 rounded-xl">
|
||||
{currentTool === 'ci-cd' && (
|
||||
<>
|
||||
<strong className="text-slate-700 dark:text-slate-300">Aktuell:</strong> Woodpecker Pipelines und Deployments verwalten
|
||||
</>
|
||||
)}
|
||||
{currentTool === 'tests' && (
|
||||
<>
|
||||
<strong className="text-slate-700 dark:text-slate-300">Aktuell:</strong> 280+ Tests ueber alle Services ueberwachen
|
||||
</>
|
||||
)}
|
||||
{currentTool === 'sbom' && (
|
||||
<>
|
||||
<strong className="text-slate-700 dark:text-slate-300">Aktuell:</strong> Abhaengigkeiten und Lizenzen pruefen
|
||||
</>
|
||||
)}
|
||||
{currentTool === 'security' && (
|
||||
<>
|
||||
<strong className="text-slate-700 dark:text-slate-300">Aktuell:</strong> Vulnerabilities und Security-Scans analysieren
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Action: Pipeline triggern */}
|
||||
<div className="pt-4 border-t border-slate-200 dark:border-gray-700">
|
||||
<button
|
||||
onClick={() => {
|
||||
// TODO: Implement pipeline trigger
|
||||
alert('Pipeline wird getriggert...')
|
||||
setIsMobileOpen(false)
|
||||
}}
|
||||
className="w-full flex items-center justify-center gap-2 px-4 py-3 text-base text-white bg-gradient-to-r from-orange-500 to-amber-500 hover:from-orange-600 hover:to-amber-600 rounded-xl transition-colors font-medium"
|
||||
>
|
||||
<PlayIcon />
|
||||
<span>Pipeline triggern</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Link to Infrastructure Overview */}
|
||||
<div className="pt-4 border-t border-slate-200 dark:border-gray-700">
|
||||
<Link
|
||||
href="/infrastructure"
|
||||
onClick={() => setIsMobileOpen(false)}
|
||||
className="flex items-center gap-2 px-3 py-2 text-sm text-orange-600 dark:text-orange-400 hover:bg-orange-50 dark:hover:bg-orange-900/20 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="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
<span>Zur Infrastructure-Uebersicht</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* CSS for slide-in animation */}
|
||||
<style jsx>{`
|
||||
@keyframes slide-in-right {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
.animate-slide-in-right {
|
||||
animation: slide-in-right 0.2s ease-out;
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
)
|
||||
}
|
||||
// Re-export responsive version for backwards compatibility
|
||||
export { DevOpsPipelineSidebarResponsive } from './DevOpsPipelineSidebarResponsive'
|
||||
|
||||
export default DevOpsPipelineSidebar
|
||||
|
||||
Reference in New Issue
Block a user