Files
breakpilot-core/admin-core/components/infrastructure/DevOpsPipelineSidebar.tsx
T
Benjamin Admin 92c86ec6ba [split-required] [guardrail-change] Enforce 500 LOC budget across all services
Install LOC guardrails (check-loc.sh, architecture.md, pre-commit hook)
and split all 44 files exceeding 500 LOC into domain-focused modules:

- consent-service (Go): models, handlers, services, database splits
- backend-core (Python): security_api, rbac_api, pdf_service, auth splits
- admin-core (TypeScript): 5 page.tsx + sidebar extractions
- pitch-deck (TypeScript): 6 slides, 3 UI components, engine.ts splits
- voice-service (Python): enhanced_task_orchestrator split

Result: 0 violations, 36 exempted (pipeline, tests, pure-data files).
Go build verified clean. No behavior changes — pure structural splits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 00:09:30 +02:00

425 lines
18 KiB
TypeScript

'use client'
/**
* DevOps Pipeline Sidebar
*
* Kompakte Sidebar-Komponente fuer Cross-Navigation zwischen DevOps-Modulen.
* Zeigt Pipeline-Flow und ermoeglicht schnelle Navigation.
*
* Features:
* - Desktop: Fixierte Sidebar rechts
* - Mobile: Floating Action Button mit Slide-In Drawer
* - Live Pipeline-Status Badge
* - Backlog-Count Badge
* - Security-Findings-Count Badge
* - Quick-Action: Pipeline triggern
*
* Datenfluss: CI/CD -> Tests -> SBOM -> Security
*/
import Link from 'next/link'
import { useState, useEffect } from 'react'
import type {
DevOpsPipelineSidebarProps,
DevOpsPipelineSidebarResponsiveProps,
} from '@/types/infrastructure-modules'
import { DEVOPS_PIPELINE_MODULES } from '@/types/infrastructure-modules'
import {
ToolIcon,
ServerIcon,
PlayIcon,
StatusBadge,
usePipelineLiveStatus,
} from './DevOpsPipelineSidebarParts'
// =============================================================================
// Main Sidebar Component
// =============================================================================
export function DevOpsPipelineSidebar({
currentTool,
compact = false,
className = '',
}: DevOpsPipelineSidebarProps) {
const [isExpanded, setIsExpanded] = useState(!compact)
const liveStatus = usePipelineLiveStatus()
return (
<div className={`bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-slate-200 dark:border-gray-700 overflow-hidden ${className}`}>
{/* Header */}
<div
className="px-4 py-3 bg-gradient-to-r from-orange-50 to-amber-50 dark:from-orange-900/20 dark:to-amber-900/20 border-b border-slate-200 dark:border-gray-700 cursor-pointer"
onClick={() => setIsExpanded(!isExpanded)}
>
<div className="flex items-center justify-between">
<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 text-sm">
DevOps Pipeline
</span>
{/* Live status indicator */}
{liveStatus?.isRunning && (
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse" title="Pipeline laeuft" />
)}
</div>
<svg
className={`w-4 h-4 text-slate-400 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
{/* Content */}
{isExpanded && (
<div className="p-3 space-y-3">
{/* Tool Links */}
<div className="space-y-1">
{DEVOPS_PIPELINE_MODULES.map((tool) => (
<Link
key={tool.id}
href={tool.href}
className={`flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
currentTool === tool.id
? 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300 font-medium'
: 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-gray-700'
}`}
>
<ToolIcon id={tool.id} />
<div className="flex-1 min-w-0">
<div className="text-sm font-medium truncate">{tool.name}</div>
<div className="text-xs text-slate-500 dark:text-slate-500 truncate">
{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 h-2 bg-orange-500 rounded-full" />
)}
</Link>
))}
</div>
{/* Pipeline Flow Visualization */}
<div className="pt-2 border-t border-slate-200 dark:border-gray-700">
<div className="text-xs font-medium text-slate-500 dark:text-slate-400 mb-2 px-1">
Pipeline Flow
</div>
<div className="flex items-center justify-between px-2 py-2 bg-slate-50 dark:bg-gray-900 rounded-lg">
<div className="flex items-center gap-1.5 text-xs">
<span title="Code" className={currentTool === 'ci-cd' ? 'opacity-100' : 'opacity-50'}>📝</span>
<span className="text-slate-400"></span>
<span title="Build" className={currentTool === 'ci-cd' ? 'opacity-100' : 'opacity-50'}>🏗️</span>
<span className="text-slate-400"></span>
<span title="Test" className={currentTool === 'tests' ? 'opacity-100' : 'opacity-50'}></span>
<span className="text-slate-400"></span>
<span title="SBOM" className={currentTool === 'sbom' ? 'opacity-100' : 'opacity-50'}>📦</span>
<span className="text-slate-400"></span>
<span title="Security" className={currentTool === 'security' ? 'opacity-100' : 'opacity-50'}>🛡️</span>
<span className="text-slate-400"></span>
<span title="Deploy" className="opacity-50">🚀</span>
</div>
</div>
</div>
{/* Quick Info zum aktuellen Tool */}
<div className="pt-2 border-t border-slate-200 dark:border-gray-700">
<div className="text-xs text-slate-500 dark:text-slate-400 px-1">
{currentTool === 'ci-cd' && (
<span>Verwalten Sie Gitea Actions Pipelines und Deployments</span>
)}
{currentTool === 'tests' && (
<span>Ueberwachen Sie 280+ Tests ueber alle Services</span>
)}
{currentTool === 'sbom' && (
<span>Pruefen Sie Abhaengigkeiten und Lizenzen</span>
)}
{currentTool === 'security' && (
<span>Analysieren Sie Vulnerabilities und Security-Scans</span>
)}
</div>
</div>
{/* Quick Action: Pipeline triggern */}
<div className="pt-2 border-t border-slate-200 dark:border-gray-700">
<button
onClick={() => {
// TODO: Implement pipeline trigger
alert('Pipeline wird getriggert...')
}}
className="w-full flex items-center justify-center gap-2 px-3 py-2 text-sm text-orange-600 dark:text-orange-400 bg-orange-50 dark:bg-orange-900/20 hover:bg-orange-100 dark:hover:bg-orange-900/30 rounded-lg transition-colors"
>
<PlayIcon />
<span>Pipeline triggern</span>
</button>
</div>
</div>
)}
</div>
)
}
// =============================================================================
// 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> Gitea Actions 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>
</>
)
}
export default DevOpsPipelineSidebar