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
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>
245 lines
7.3 KiB
TypeScript
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,
|
|
}
|
|
}
|