diff --git a/.gitea/workflows/build-push.yml b/.gitea/workflows/build-push.yml index b0a2242..41cd9c0 100644 --- a/.gitea/workflows/build-push.yml +++ b/.gitea/workflows/build-push.yml @@ -63,3 +63,5 @@ jobs: docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}" docker push "${IMAGE}:latest" echo "✓ Image pushed: ${IMAGE}:${{ steps.sha.outputs.short_sha }}" + + diff --git a/app/api/ci-status/route.ts b/app/api/ci-status/route.ts index c6d44e9..9ca06b1 100644 --- a/app/api/ci-status/route.ts +++ b/app/api/ci-status/route.ts @@ -1,9 +1,14 @@ -import { NextResponse } from 'next/server' +import { NextRequest, NextResponse } from 'next/server' const FORGEJO_URL = 'https://forgejo.riotpiao.com' -const REPO = 'rock/riotpiao.com' +const DEFAULT_REPO = 'rock/riotpiao.com' +const ALLOWED_REPOS = ['rock/riotpiao.com', 'rock/homelab', 'rock/homelab-frontend', 'rock/poimen', 'rock/poimen-memory', 'rock/poimen-workflows'] -export async function GET() { +export async function GET(request: NextRequest) { + const repo = request.nextUrl.searchParams.get('repo') || DEFAULT_REPO + if (!ALLOWED_REPOS.includes(repo)) { + return NextResponse.json({ error: 'Repo not allowed' }, { status: 403 }) + } const token = process.env.FORGEJO_TOKEN if (!token) { @@ -15,7 +20,7 @@ export async function GET() { try { const response = await fetch( - `${FORGEJO_URL}/api/v1/repos/${REPO}/actions/runs?limit=1`, + `${FORGEJO_URL}/api/v1/repos/${repo}/actions/runs?limit=1`, { headers: { Authorization: `token ${token}`, @@ -42,6 +47,7 @@ export async function GET() { return NextResponse.json({ status: latestRun.status, conclusion: latestRun.conclusion, + sha: latestRun.head_sha, title: latestRun.display_title || latestRun.head_branch, branch: latestRun.head_branch, url: latestRun.html_url, diff --git a/components/ExperienceTimeline.tsx b/components/ExperienceTimeline.tsx index ec245ed..0251222 100644 --- a/components/ExperienceTimeline.tsx +++ b/components/ExperienceTimeline.tsx @@ -1,8 +1,52 @@ 'use client' import { motion } from 'framer-motion' +import { useState, useEffect } from 'react' +import { GitBranch, CheckCircle, XCircle, Loader2 } from 'lucide-react' import { useLanguage } from '@/lib/LanguageContext' +interface RepoCI { + sha: string + status: string + url: string +} + +function CIBadge({ repo, forgejoUrl }: { repo: string; forgejoUrl: string }) { + const [ci, setCI] = useState(null) + + useEffect(() => { + fetch(`/api/ci-status?repo=${repo}`) + .then(r => r.json()) + .then(data => setCI({ + sha: data.sha?.substring(0, 7) || '', + status: data.conclusion || data.status || 'unknown', + url: data.url || `${forgejoUrl}/actions`, + })) + .catch(() => null) + }, [repo, forgejoUrl]) + + if (!ci || !ci.sha) return null + + const icon = ci.status === 'success' + ? + : ci.status === 'failure' + ? + : + + return ( + + + {ci.sha} + {icon} + + ) +} + const colors = [ 'from-green-500 to-green-600', 'from-red-500 to-red-600', @@ -93,9 +137,14 @@ export function ExperienceTimeline() { {item.role}

- - {item.period} - +
+ + {item.period} + + {index === 0 && ( + + )} +