chore: Woodpecker CI entfernt — nur noch Gitea Actions
All checks were successful
CI / test-bqas (push) Successful in 27s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 27s
CI / test-python-voice (push) Successful in 28s

Woodpecker wird nicht mehr verwendet. Wir migrieren vollstaendig
auf Gitea Actions (gitea.meghsakha.com).

Entfernt:
- woodpecker-server + woodpecker-agent Container (docker-compose.yml)
- woodpecker_data Volume
- backend-core/woodpecker_proxy_api.py (SQLite-DB Proxy)
- admin-core/app/api/admin/infrastructure/woodpecker/route.ts
- admin-core/app/api/webhooks/woodpecker/route.ts
- .woodpecker/main.yml (alte CI-Pipeline-Konfiguration)

Bereinigt:
- ci-cd/page.tsx: Woodpecker-Tab + Status-Karte + State entfernt
- types/infrastructure-modules.ts: Woodpecker-Typen + API-Endpunkte
- DevOpsPipelineSidebar.tsx: Textbeschreibungen auf Gitea Actions
- dashboard/page.tsx: Woodpecker aus Service-Health-Liste
- sbom/page.tsx: Woodpecker aus SBOM-Liste
- navigation.ts: Beschreibung aktualisiert
- .env.example: WOODPECKER_* Variablen entfernt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-05 23:05:08 +01:00
parent 2801e44d39
commit 8dc1b4c67f
14 changed files with 13 additions and 1681 deletions

View File

@@ -85,38 +85,7 @@ interface DockerStats {
stopped_containers: number
}
type TabType = 'overview' | 'woodpecker' | 'pipelines' | 'deployments' | 'setup' | 'scheduler'
// Woodpecker Types
interface WoodpeckerStep {
name: string
state: 'pending' | 'running' | 'success' | 'failure' | 'skipped'
exit_code: number
error?: string
}
interface WoodpeckerPipeline {
id: number
number: number
status: 'pending' | 'running' | 'success' | 'failure' | 'error'
event: string
branch: string
commit: string
message: string
author: string
created: number
started: number
finished: number
steps: WoodpeckerStep[]
errors?: string[]
}
interface WoodpeckerStatus {
status: 'online' | 'offline'
pipelines: WoodpeckerPipeline[]
lastUpdate: string
error?: string
}
type TabType = 'overview' | 'pipelines' | 'deployments' | 'setup' | 'scheduler'
// ============================================================================
// Helper Components
@@ -168,10 +137,6 @@ export default function CICDPage() {
const [containerFilter, setContainerFilter] = useState<'all' | 'running' | 'stopped'>('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)
@@ -214,54 +179,12 @@ export default function CICDPage() {
}
}, [])
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 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 loadAllData = useCallback(async () => {
setLoading(true)
setError(null)
await Promise.all([loadPipelineData(), loadContainerData(), loadWoodpeckerData()])
await Promise.all([loadPipelineData(), loadContainerData()])
setLoading(false)
}, [loadPipelineData, loadContainerData, loadWoodpeckerData])
}, [loadPipelineData, loadContainerData])
useEffect(() => {
loadAllData()
@@ -402,11 +325,6 @@ export default function CICDPage() {
<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>
)},
{ id: 'woodpecker', name: 'Woodpecker CI', icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
)},
{ id: 'pipelines', name: 'Gitea Pipelines', 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 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
@@ -458,95 +376,6 @@ export default function CICDPage() {
{/* ================================================================ */}
{activeTab === 'overview' && (
<div className="space-y-6">
{/* Woodpecker CI Status - Prominent */}
<div className={`p-4 rounded-xl border-2 ${
woodpeckerStatus?.status === 'online'
? woodpeckerStatus.pipelines?.[0]?.status === 'success'
? 'border-green-300 bg-green-50'
: woodpeckerStatus.pipelines?.[0]?.status === 'failure' || woodpeckerStatus.pipelines?.[0]?.status === 'error'
? 'border-red-300 bg-red-50'
: woodpeckerStatus.pipelines?.[0]?.status === 'running'
? 'border-blue-300 bg-blue-50'
: 'border-slate-300 bg-slate-50'
: 'border-red-300 bg-red-50'
}`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className={`p-3 rounded-lg ${
woodpeckerStatus?.status === 'online'
? woodpeckerStatus.pipelines?.[0]?.status === 'success'
? 'bg-green-100'
: woodpeckerStatus.pipelines?.[0]?.status === 'failure' || woodpeckerStatus.pipelines?.[0]?.status === 'error'
? 'bg-red-100'
: 'bg-blue-100'
: 'bg-red-100'
}`}>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
<div>
<div className="flex items-center gap-2">
<h3 className="font-semibold text-slate-900">Woodpecker CI</h3>
<span className={`px-2 py-0.5 text-xs font-medium rounded-full ${
woodpeckerStatus?.status === 'online' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}>
{woodpeckerStatus?.status === 'online' ? 'Online' : 'Offline'}
</span>
</div>
{woodpeckerStatus?.pipelines?.[0] && (
<p className="text-sm text-slate-600 mt-1">
Pipeline #{woodpeckerStatus.pipelines[0].number}: {' '}
<span className={`font-medium ${
woodpeckerStatus.pipelines[0].status === 'success' ? 'text-green-600' :
woodpeckerStatus.pipelines[0].status === 'failure' || woodpeckerStatus.pipelines[0].status === 'error' ? 'text-red-600' :
woodpeckerStatus.pipelines[0].status === 'running' ? 'text-blue-600' : 'text-slate-600'
}`}>
{woodpeckerStatus.pipelines[0].status}
</span>
{' '}auf {woodpeckerStatus.pipelines[0].branch}
</p>
)}
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => setActiveTab('woodpecker')}
className="px-3 py-1.5 text-sm border border-slate-300 text-slate-700 rounded-lg hover:bg-white"
>
Details
</button>
<button
onClick={triggerWoodpeckerPipeline}
disabled={triggeringWoodpecker}
className="px-3 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 flex items-center gap-1"
>
{triggeringWoodpecker ? (
<div className="animate-spin rounded-full h-3 w-3 border-b-2 border-white" />
) : (
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
</svg>
)}
Starten
</button>
</div>
</div>
{/* Failed steps preview */}
{woodpeckerStatus?.pipelines?.[0]?.steps?.some(s => s.state === 'failure') && (
<div className="mt-3 pt-3 border-t border-red-200">
<p className="text-xs font-medium text-red-700 mb-2">Fehlgeschlagene Steps:</p>
<div className="flex flex-wrap gap-2">
{woodpeckerStatus.pipelines[0].steps.filter(s => s.state === 'failure').map((step, i) => (
<span key={i} className="px-2 py-1 bg-red-100 text-red-700 text-xs rounded">
{step.name}
</span>
))}
</div>
</div>
)}
</div>
{/* Status Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className={`p-4 rounded-lg ${pipelineStatus?.gitea_connected ? 'bg-green-50' : 'bg-yellow-50'}`}>
@@ -679,299 +508,6 @@ export default function CICDPage() {
</div>
)}
{/* ================================================================ */}
{/* Woodpecker Tab */}
{/* ================================================================ */}
{activeTab === 'woodpecker' && (
<div className="space-y-6">
{/* Woodpecker Status Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<h3 className="text-lg font-semibold text-slate-800">Woodpecker CI Pipeline</h3>
<span className={`flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium ${
woodpeckerStatus?.status === 'online'
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}>
<span className={`w-2 h-2 rounded-full ${
woodpeckerStatus?.status === 'online' ? 'bg-green-500' : 'bg-red-500'
}`} />
{woodpeckerStatus?.status === 'online' ? 'Online' : 'Offline'}
</span>
</div>
<div className="flex items-center gap-2">
<a
href="http://macmini:8090"
target="_blank"
rel="noopener noreferrer"
className="px-3 py-2 text-sm border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 flex items-center gap-2"
>
<svg className="w-4 h-4" 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>
Woodpecker UI
</a>
<button
onClick={triggerWoodpeckerPipeline}
disabled={triggeringWoodpecker}
className="px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 disabled:opacity-50 transition-colors flex items-center gap-2"
>
{triggeringWoodpecker ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white" />
Startet...
</>
) : (
<>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Pipeline starten
</>
)}
</button>
</div>
</div>
{/* Pipeline Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-blue-50 p-4 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<svg className="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
<span className="text-sm font-medium">Gesamt</span>
</div>
<p className="text-2xl font-bold text-blue-700">{woodpeckerStatus?.pipelines?.length || 0}</p>
</div>
<div className="bg-green-50 p-4 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<svg className="w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span className="text-sm font-medium">Erfolgreich</span>
</div>
<p className="text-2xl font-bold text-green-700">
{woodpeckerStatus?.pipelines?.filter(p => p.status === 'success').length || 0}
</p>
</div>
<div className="bg-red-50 p-4 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<svg className="w-4 h-4 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
<span className="text-sm font-medium">Fehlgeschlagen</span>
</div>
<p className="text-2xl font-bold text-red-700">
{woodpeckerStatus?.pipelines?.filter(p => p.status === 'failure' || p.status === 'error').length || 0}
</p>
</div>
<div className="bg-yellow-50 p-4 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<svg className="w-4 h-4 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="text-sm font-medium">Laufend</span>
</div>
<p className="text-2xl font-bold text-yellow-700">
{woodpeckerStatus?.pipelines?.filter(p => p.status === 'running' || p.status === 'pending').length || 0}
</p>
</div>
</div>
{/* Pipeline List */}
{woodpeckerStatus?.pipelines && woodpeckerStatus.pipelines.length > 0 ? (
<div className="bg-slate-50 rounded-lg p-4">
<h4 className="font-medium text-slate-800 mb-4">Pipeline Historie</h4>
<div className="space-y-3">
{woodpeckerStatus.pipelines.map((pipeline) => (
<div
key={pipeline.id}
className={`border rounded-xl p-4 transition-colors ${
pipeline.status === 'success'
? 'border-green-200 bg-green-50/30'
: pipeline.status === 'failure' || pipeline.status === 'error'
? 'border-red-200 bg-red-50/30'
: pipeline.status === 'running'
? 'border-blue-200 bg-blue-50/30'
: 'border-slate-200 bg-white'
}`}
>
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<span className={`w-3 h-3 rounded-full ${
pipeline.status === 'success' ? 'bg-green-500' :
pipeline.status === 'failure' || pipeline.status === 'error' ? 'bg-red-500' :
pipeline.status === 'running' ? 'bg-blue-500 animate-pulse' : 'bg-slate-400'
}`} />
<span className="font-semibold text-slate-900">Pipeline #{pipeline.number}</span>
<span className={`px-2 py-0.5 text-xs font-medium rounded-full ${
pipeline.status === 'success' ? 'bg-green-100 text-green-800' :
pipeline.status === 'failure' || pipeline.status === 'error' ? 'bg-red-100 text-red-800' :
pipeline.status === 'running' ? 'bg-blue-100 text-blue-800' :
'bg-slate-100 text-slate-600'
}`}>
{pipeline.status}
</span>
</div>
<div className="text-sm text-slate-600 mb-2">
<span className="font-mono">{pipeline.branch}</span>
<span className="mx-2 text-slate-400"></span>
<span className="font-mono text-slate-500">{pipeline.commit}</span>
<span className="mx-2 text-slate-400"></span>
<span>{pipeline.event}</span>
</div>
{pipeline.message && (
<p className="text-sm text-slate-500 mb-2 truncate max-w-xl">{pipeline.message}</p>
)}
{/* Steps Progress */}
{pipeline.steps && pipeline.steps.length > 0 && (
<div className="mt-3">
<div className="flex gap-1 mb-2">
{pipeline.steps.map((step, i) => (
<div
key={i}
className={`h-2 flex-1 rounded-full ${
step.state === 'success' ? 'bg-green-500' :
step.state === 'failure' ? 'bg-red-500' :
step.state === 'running' ? 'bg-blue-500 animate-pulse' :
step.state === 'skipped' ? 'bg-slate-200' : 'bg-slate-300'
}`}
title={`${step.name}: ${step.state}`}
/>
))}
</div>
<div className="flex flex-wrap gap-2 text-xs">
{pipeline.steps.map((step, i) => (
<span
key={i}
className={`px-2 py-1 rounded ${
step.state === 'success' ? 'bg-green-100 text-green-700' :
step.state === 'failure' ? 'bg-red-100 text-red-700' :
step.state === 'running' ? 'bg-blue-100 text-blue-700' :
'bg-slate-100 text-slate-600'
}`}
>
{step.name}
</span>
))}
</div>
</div>
)}
{/* Errors */}
{pipeline.errors && pipeline.errors.length > 0 && (
<div className="mt-3 p-3 bg-red-50 border border-red-200 rounded-lg">
<h5 className="text-sm font-medium text-red-800 mb-1">Fehler:</h5>
<ul className="text-xs text-red-700 space-y-1">
{pipeline.errors.map((err, i) => (
<li key={i} className="font-mono">{err}</li>
))}
</ul>
</div>
)}
</div>
<div className="text-right text-sm text-slate-500">
<p>{new Date(pipeline.created * 1000).toLocaleDateString('de-DE')}</p>
<p className="text-xs">{new Date(pipeline.created * 1000).toLocaleTimeString('de-DE')}</p>
{pipeline.started && pipeline.finished && (
<p className="text-xs mt-1">
Dauer: {Math.round((pipeline.finished - pipeline.started) / 60)}m
</p>
)}
</div>
</div>
</div>
))}
</div>
</div>
) : (
<div className="bg-slate-50 rounded-lg p-8 text-center">
<svg className="w-12 h-12 text-slate-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
<p className="text-slate-500">Keine Pipelines gefunden</p>
<p className="text-sm text-slate-400 mt-1">Starte eine neue Pipeline oder pruefe die Woodpecker-Konfiguration</p>
</div>
)}
{/* Pipeline Configuration Info */}
<div className="bg-slate-50 rounded-lg p-4">
<h4 className="font-medium text-slate-800 mb-3">Pipeline Konfiguration</h4>
<pre className="bg-slate-800 text-slate-100 p-4 rounded-lg overflow-x-auto text-sm">
{`Woodpecker CI Pipeline (.woodpecker/main.yml)
├── 1. go-lint → Go Linting (PR only)
├── 2. python-lint → Python Linting (PR only)
├── 3. secrets-scan → GitLeaks Secrets Scan
├── 4. test-go-consent → Go Unit Tests
├── 5. test-go-billing → Billing Service Tests
├── 6. test-go-school → School Service Tests
├── 7. test-python → Python Backend Tests
├── 8. build-images → Docker Image Build
├── 9. generate-sbom → SBOM Generation (Syft)
├── 10. vuln-scan → Vulnerability Scan (Grype)
├── 11. container-scan → Container Scan (Trivy)
├── 12. sign-images → Cosign Image Signing
├── 13. attest-sbom → SBOM Attestation
├── 14. provenance → SLSA Provenance
└── 15. deploy-prod → Production Deployment`}
</pre>
</div>
{/* Workflow Anleitung */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h4 className="font-medium text-blue-800 mb-3 flex items-center gap-2">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Workflow-Anleitung
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
<div>
<h5 className="font-medium text-blue-700 mb-2">🤖 Automatisch (bei jedem Push/PR):</h5>
<ul className="space-y-1 text-blue-600">
<li> <strong>Linting</strong> - Code-Qualitaet pruefen (nur PRs)</li>
<li> <strong>Unit Tests</strong> - Go & Python Tests</li>
<li> <strong>Test-Dashboard</strong> - Ergebnisse werden gesendet</li>
<li> <strong>Backlog</strong> - Fehlgeschlagene Tests werden erfasst</li>
</ul>
</div>
<div>
<h5 className="font-medium text-blue-700 mb-2">👆 Manuell (Button oder Tag):</h5>
<ul className="space-y-1 text-blue-600">
<li> <strong>Docker Builds</strong> - Container erstellen</li>
<li> <strong>SBOM/Scans</strong> - Sicherheitsanalyse</li>
<li> <strong>Deployment</strong> - In Produktion deployen</li>
<li> <strong>Pipeline starten</strong> - Diesen Button verwenden</li>
</ul>
</div>
</div>
<div className="mt-4 pt-3 border-t border-blue-200">
<h5 className="font-medium text-blue-700 mb-2"> Setup: API Token konfigurieren</h5>
<p className="text-blue-600 text-sm">
Um Pipelines ueber das Dashboard zu starten, muss ein <strong>WOODPECKER_TOKEN</strong> konfiguriert werden:
</p>
<ol className="mt-2 space-y-1 text-blue-600 text-sm list-decimal list-inside">
<li>Woodpecker UI oeffnen: <a href="http://macmini:8090" target="_blank" rel="noopener noreferrer" className="underline hover:text-blue-800">http://macmini:8090</a></li>
<li>Mit Gitea-Account einloggen</li>
<li>Klick auf Profil <strong>User Settings</strong> <strong>Personal Access Tokens</strong></li>
<li>Neues Token erstellen und in <code className="bg-blue-100 px-1 rounded">.env</code> eintragen: <code className="bg-blue-100 px-1 rounded">WOODPECKER_TOKEN=...</code></li>
<li>Container neu starten: <code className="bg-blue-100 px-1 rounded">docker compose up -d admin-v2</code></li>
</ol>
</div>
</div>
</div>
)}
{/* ================================================================ */}
{/* Pipelines Tab */}
{/* ================================================================ */}

View File

@@ -110,8 +110,7 @@ const INFRASTRUCTURE_COMPONENTS: Component[] = [
{ type: 'service', name: 'ERPNext', version: 'v15', category: 'erp', port: '8090', description: 'Open Source ERP System', license: 'GPL-3.0', sourceUrl: 'https://github.com/frappe/erpnext' },
// ===== CI/CD & VERSION CONTROL =====
{ type: 'service', name: 'Woodpecker CI', version: '2.x', category: 'cicd', port: '8082', description: 'Self-hosted CI/CD Pipeline (Drone Fork)', license: 'Apache-2.0', sourceUrl: 'https://github.com/woodpecker-ci/woodpecker' },
{ type: 'service', name: 'Gitea', version: '1.21', category: 'cicd', port: '3003', description: 'Self-hosted Git Service', license: 'MIT', sourceUrl: 'https://github.com/go-gitea/gitea' },
{ type: 'service', name: 'Gitea', version: '1.21', category: 'cicd', port: '3003', description: 'Self-hosted Git Service with Actions CI/CD', license: 'MIT', sourceUrl: 'https://github.com/go-gitea/gitea' },
{ type: 'service', name: 'Dokploy', version: '0.26.7', category: 'cicd', port: '3000', description: 'Self-hosted PaaS (Vercel/Heroku Alternative)', license: 'Apache-2.0', sourceUrl: 'https://github.com/Dokploy/dokploy' },
// ===== DEVELOPMENT =====

View File

@@ -639,7 +639,7 @@ Tests bleiben wo sie sind:
<div className="mt-4 pt-4 border-t border-blue-200">
<p className="text-sm text-blue-600">
<strong>Daten-Fluss:</strong> Woodpecker CI POST /api/tests/ci-result PostgreSQL Test Dashboard
<strong>Daten-Fluss:</strong> Gitea Actions POST /api/tests/ci-result PostgreSQL Test Dashboard
</p>
</div>
</div>