import { Activity, Boxes, ExternalLink, FileKey, GitBranch, RefreshCw } from 'lucide-react'; import { useEffect, useMemo, useState } from 'react'; import { Badge, Button, Card, CardHeader } from '../../components/ui'; import { PageTitle } from '../../layout/AppLayout'; import { apiRequest } from '../../lib/api'; import { cn } from '../../lib/utils'; type CommonAppPipeline = { id: string; repository: string; message: string; branch: string; status: string; createdAt: number; url?: string; }; function relativeTime(timestamp: number) { if (!timestamp) return '-'; const seconds = Math.max(0, Math.floor((Date.now() - timestamp) / 1000)); if (seconds < 60) return '방금 전'; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes}분 전`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}시간 전`; const days = Math.floor(hours / 24); if (days < 30) return `${days}일 전`; return new Intl.DateTimeFormat('ko-KR', { month: 'short', day: 'numeric' }).format(new Date(timestamp)); } function pipelineTone(status: string): 'neutral' | 'green' | 'amber' | 'red' | 'blue' { const normalized = status.toLowerCase(); if (['success', 'succeeded', 'done'].includes(normalized)) return 'green'; if (['failure', 'failed', 'error', 'killed'].includes(normalized)) return 'red'; if (['running', 'started'].includes(normalized)) return 'blue'; if (['pending', 'blocked', 'created'].includes(normalized)) return 'amber'; return 'neutral'; } async function loadCommonAppPipelines(): Promise { const response = await apiRequest<{ repos: CommonAppPipeline[] }>('/woodpecker/repos'); return response.repos.slice(0, 8); } export function DashboardPage() { const [commonApps, setCommonApps] = useState([]); const [commonAppsLoading, setCommonAppsLoading] = useState(true); const [commonAppsFailed, setCommonAppsFailed] = useState(false); const refreshCommonApps = () => { setCommonAppsLoading(true); loadCommonAppPipelines() .then((pipelines) => { setCommonApps(pipelines); setCommonAppsFailed(false); }) .catch(() => { setCommonApps([]); setCommonAppsFailed(true); }) .finally(() => setCommonAppsLoading(false)); }; useEffect(() => { refreshCommonApps(); }, []); const launcherRows = useMemo( () => [ ['최신 버전', '런처 버전 관리'], ['빌드/배포', '런처 빌드 관리'], ['소스 연결', '런처 소스 관리'] ], [] ); return ( <>
{[ ['활성 앱', '10', Boxes], ['라이센스 사용률', '72%', FileKey], ['런타임 정상률', '96.4%', Activity] ].map(([label, value, Icon]) => (

{label as string}

{value as string}

))}
{launcherRows.map(([label, value]) => (
{label} {value}
))}
} />
{commonAppsFailed ? (
Woodpecker 저장소를 불러오지 못했습니다.
) : commonAppsLoading && commonApps.length === 0 ? (
저장소를 불러오는 중입니다.
) : commonApps.length === 0 ? (
표시할 저장소가 없습니다.
) : (
{commonApps.map((pipeline) => (

{pipeline.repository}

{pipeline.message}

{pipeline.url ? ( ) : null}
{pipeline.status} {pipeline.branch} {relativeTime(pipeline.createdAt)}
))}
)}
); }