Files
breakpilot-lehrer/admin-lehrer/app/(admin)/infrastructure/ci-cd/useCiCdData.ts
Benjamin Admin 9ba420fa91
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-school (push) Successful in 42s
CI / test-go-edu-search (push) Successful in 34s
CI / test-python-klausur (push) Failing after 2m51s
CI / test-python-agent-core (push) Successful in 21s
CI / test-nodejs-website (push) Successful in 29s
Fix: Remove broken getKlausurApiUrl and clean up empty lines
sed replacement left orphaned hostname references in story page
and empty lines in getApiBase functions.

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

245 lines
7.3 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react'
import type {
PipelineStatus,
PipelineRun,
SystemStats,
DockerStats,
WoodpeckerStatus,
TabType,
ContainerFilter,
ContainerInfo,
} from './types'
export interface CiCdData {
// Tab
activeTab: TabType
setActiveTab: (tab: TabType) => void
// Pipeline
pipelineStatus: PipelineStatus | null
pipelineHistory: PipelineRun[]
triggeringPipeline: boolean
triggerPipeline: () => Promise<void>
// Container
systemStats: SystemStats | null
dockerStats: DockerStats | null
containerFilter: ContainerFilter
setContainerFilter: (f: ContainerFilter) => void
filteredContainers: ContainerInfo[]
actionLoading: string | null
containerAction: (containerId: string, action: 'start' | 'stop' | 'restart') => Promise<void>
loadContainerData: () => Promise<void>
// Woodpecker
woodpeckerStatus: WoodpeckerStatus | null
triggeringWoodpecker: boolean
triggerWoodpeckerPipeline: () => Promise<void>
// General
loading: boolean
error: string | null
message: string | null
}
export function useCiCdData(): CiCdData {
const [activeTab, setActiveTab] = useState<TabType>('overview')
// Pipeline State
const [pipelineStatus, setPipelineStatus] = useState<PipelineStatus | null>(null)
const [pipelineHistory, setPipelineHistory] = useState<PipelineRun[]>([])
const [triggeringPipeline, setTriggeringPipeline] = useState(false)
// Container State
const [systemStats, setSystemStats] = useState<SystemStats | null>(null)
const [dockerStats, setDockerStats] = useState<DockerStats | null>(null)
const [containerFilter, setContainerFilter] = useState<ContainerFilter>('all')
const [actionLoading, setActionLoading] = useState<string | null>(null)
// Woodpecker State
const [woodpeckerStatus, setWoodpeckerStatus] = useState<WoodpeckerStatus | null>(null)
const [triggeringWoodpecker, setTriggeringWoodpecker] = useState(false)
// General State
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [message, setMessage] = useState<string | null>(null)
const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || ''
// ============================================================================
// Data Loading
// ============================================================================
const loadPipelineData = useCallback(async () => {
try {
const [statusRes, historyRes] = await Promise.all([
fetch(`${BACKEND_URL}/api/v1/security/sbom/pipeline/status`),
fetch(`${BACKEND_URL}/api/v1/security/sbom/pipeline/history`),
])
if (statusRes.ok) {
setPipelineStatus(await statusRes.json())
}
if (historyRes.ok) {
setPipelineHistory(await historyRes.json())
}
} catch (err) {
console.error('Failed to load pipeline data:', err)
}
}, [BACKEND_URL])
const loadContainerData = useCallback(async () => {
try {
const response = await fetch('/api/admin/infrastructure/mac-mini')
if (response.ok) {
const data = await response.json()
setSystemStats(data.system)
setDockerStats(data.docker)
}
} catch (err) {
console.error('Failed to load container data:', err)
}
}, [])
const loadWoodpeckerData = useCallback(async () => {
try {
const response = await fetch('/api/admin/infrastructure/woodpecker?limit=10')
if (response.ok) {
const data = await response.json()
setWoodpeckerStatus(data)
}
} catch (err) {
console.error('Failed to load Woodpecker data:', err)
setWoodpeckerStatus({
status: 'offline',
pipelines: [],
lastUpdate: new Date().toISOString(),
error: 'Verbindung fehlgeschlagen'
})
}
}, [])
const loadAllData = useCallback(async () => {
setLoading(true)
setError(null)
await Promise.all([loadPipelineData(), loadContainerData(), loadWoodpeckerData()])
setLoading(false)
}, [loadPipelineData, loadContainerData, loadWoodpeckerData])
useEffect(() => {
loadAllData()
}, [loadAllData])
// Auto-refresh every 30 seconds
useEffect(() => {
const interval = setInterval(loadAllData, 30000)
return () => clearInterval(interval)
}, [loadAllData])
// ============================================================================
// Actions
// ============================================================================
const triggerPipeline = async () => {
setTriggeringPipeline(true)
try {
const response = await fetch(`${BACKEND_URL}/api/v1/security/sbom/pipeline/trigger`, {
method: 'POST',
})
if (response.ok) {
setMessage('Pipeline gestartet!')
setTimeout(loadPipelineData, 2000)
setTimeout(loadPipelineData, 5000)
}
} catch (err) {
setError('Pipeline-Trigger fehlgeschlagen')
} finally {
setTriggeringPipeline(false)
}
}
const triggerWoodpeckerPipeline = async () => {
setTriggeringWoodpecker(true)
setMessage(null)
try {
const response = await fetch('/api/admin/infrastructure/woodpecker', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ branch: 'main' })
})
if (response.ok) {
const result = await response.json()
setMessage(`Woodpecker Pipeline #${result.pipeline?.number || '?'} gestartet!`)
setTimeout(loadWoodpeckerData, 2000)
setTimeout(loadWoodpeckerData, 5000)
} else {
setError('Pipeline-Start fehlgeschlagen')
}
} catch (err) {
setError('Pipeline konnte nicht gestartet werden')
} finally {
setTriggeringWoodpecker(false)
}
}
const containerAction = async (containerId: string, action: 'start' | 'stop' | 'restart') => {
setActionLoading(`${containerId}-${action}`)
setMessage(null)
try {
const response = await fetch('/api/admin/infrastructure/mac-mini', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ container_id: containerId, action }),
})
if (!response.ok) {
throw new Error('Aktion fehlgeschlagen')
}
setMessage(`Container ${action} erfolgreich`)
setTimeout(loadContainerData, 1000)
setTimeout(loadContainerData, 3000)
} catch (err) {
setError(err instanceof Error ? err.message : 'Fehler')
} finally {
setActionLoading(null)
}
}
// ============================================================================
// Derived
// ============================================================================
const filteredContainers = dockerStats?.containers.filter(c => {
if (containerFilter === 'all') return true
if (containerFilter === 'running') return c.state === 'running'
if (containerFilter === 'stopped') return c.state !== 'running'
return true
}) || []
return {
activeTab,
setActiveTab,
pipelineStatus,
pipelineHistory,
triggeringPipeline,
triggerPipeline,
systemStats,
dockerStats,
containerFilter,
setContainerFilter,
filteredContainers,
actionLoading,
containerAction,
loadContainerData,
woodpeckerStatus,
triggeringWoodpecker,
triggerWoodpeckerPipeline,
loading,
error,
message,
}
}