diff --git a/src/pages/common-apps/CommonAppBuildPage.tsx b/src/pages/common-apps/CommonAppBuildPage.tsx index e6afb7b..6fa6cb4 100644 --- a/src/pages/common-apps/CommonAppBuildPage.tsx +++ b/src/pages/common-apps/CommonAppBuildPage.tsx @@ -5,20 +5,28 @@ import { useToast } from '../../components/toast'; import { PageTitle } from '../../layout/AppLayout'; import { cn } from '../../lib/utils'; import { LoadingModal, useMinimumLoading } from '../operations/operation-utils'; -import { listCommonAppPipelines, type CommonAppPipeline } from './common-app-api'; +import { listCommonAppPipelineHistory, listCommonAppPipelines, type CommonAppPipeline } from './common-app-api'; import { formatDuration, formatPipelineRelativeTime, formatRepositoryName, formatShortSha, isActivePipelineStatus, pipelineStatusDotClass, pipelineStatusTone } from './common-app-utils'; +const historyPageSize = 5; + export function CommonAppBuildPage() { const { toast } = useToast(); const [pipelines, setPipelines] = useState([]); + const [selectedRepoId, setSelectedRepoId] = useState(''); + const [history, setHistory] = useState([]); + const [historyPage, setHistoryPage] = useState(1); + const [hasNextHistoryPage, setHasNextHistoryPage] = useState(false); const [loading, setLoading] = useState(true); + const [historyLoading, setHistoryLoading] = useState(false); const [lastLoadedAt, setLastLoadedAt] = useState(null); - const loadingVisible = useMinimumLoading(loading); + const loadingVisible = useMinimumLoading(loading || historyLoading); const runningCount = useMemo( () => pipelines.filter((pipeline) => isActivePipelineStatus(pipeline.status)).length, [pipelines] ); + const selectedPipeline = pipelines.find((pipeline) => pipeline.repoId === selectedRepoId) ?? pipelines[0]; const refresh = (silent = false) => { if (!silent) setLoading(true); @@ -26,6 +34,10 @@ export function CommonAppBuildPage() { .then((nextPipelines) => { setPipelines(nextPipelines); setLastLoadedAt(Date.now()); + setSelectedRepoId((current) => { + if (current && nextPipelines.some((pipeline) => pipeline.repoId === current)) return current; + return nextPipelines.find((pipeline) => pipeline.repoId)?.repoId ?? ''; + }); }) .catch((error: Error) => { if (!silent) toast({ type: 'error', title: '공통앱 빌드 조회 실패', message: error.message }); @@ -44,6 +56,40 @@ export function CommonAppBuildPage() { return () => window.clearInterval(timer); }, []); + useEffect(() => { + setHistoryPage(1); + }, [selectedRepoId]); + + useEffect(() => { + let canceled = false; + if (!selectedRepoId) { + setHistory([]); + setHasNextHistoryPage(false); + return; + } + + setHistoryLoading(true); + listCommonAppPipelineHistory(selectedRepoId, historyPage, historyPageSize) + .then((result) => { + if (canceled) return; + setHistory(result.pipelines ?? []); + setHasNextHistoryPage(Boolean(result.hasNext)); + }) + .catch((error: Error) => { + if (canceled) return; + setHistory([]); + setHasNextHistoryPage(false); + toast({ type: 'error', title: '파이프라인 이력 조회 실패', message: error.message }); + }) + .finally(() => { + if (!canceled) setHistoryLoading(false); + }); + + return () => { + canceled = true; + }; + }, [historyPage, selectedRepoId, toast]); + return ( <> - - - - {runningCount > 0 ? `진행 중 ${runningCount}개` : lastLoadedAt ? `${formatPipelineRelativeTime(lastLoadedAt)} 갱신` : '5초마다 갱신'} - - 0 ? 'blue' : 'neutral'}>{pipelines.length}개 +
+ + + + {runningCount > 0 ? `진행 중 ${runningCount}개` : lastLoadedAt ? `${formatPipelineRelativeTime(lastLoadedAt)} 갱신` : '5초마다 갱신'} + + 0 ? 'blue' : 'neutral'}>{pipelines.length}개 +
+ } + /> +
+ {pipelines.length === 0 && !loading ? ( +
표시할 저장소가 없습니다.
+ ) : null} +
+ {pipelines.map((pipeline) => { + const selected = pipeline.repoId === selectedRepoId; + return ( + + ); + })}
- } - /> -
- {pipelines.length === 0 && !loading ? ( -
표시할 저장소가 없습니다.
- ) : null} -
- {pipelines.map((pipeline) => ( +
+ + + + 5개씩} + /> +
+ {history.length === 0 && !historyLoading ? ( +
표시할 파이프라인 이력이 없습니다.
+ ) : null} + {history.map((pipeline) => (
-

{formatRepositoryName(pipeline.repository)}

+ #{pipeline.number ?? pipeline.id} +

{pipeline.message || '-'}

-

{pipeline.message || '-'}

@@ -100,8 +181,19 @@ export function CommonAppBuildPage() {
))}
-
-
+
+ 현재 {historyPage}페이지 +
+ + +
+
+ +
diff --git a/src/pages/common-apps/common-app-api.ts b/src/pages/common-apps/common-app-api.ts index 1def70a..f3efe03 100644 --- a/src/pages/common-apps/common-app-api.ts +++ b/src/pages/common-apps/common-app-api.ts @@ -2,7 +2,9 @@ import { apiRequest } from '../../lib/api'; export type CommonAppPipeline = { id: string; + repoId?: string | null; repository: string; + number?: number | null; message: string; branch: string; commitSha?: string | null; @@ -17,3 +19,16 @@ export function listCommonAppPipelines() { return apiRequest<{ repos?: CommonAppPipeline[]; baseUrl?: string | null }>('/woodpecker/repos') .then((result) => (result.repos ?? []).map((repo) => ({ ...repo, ciUrl: repo.ciUrl ?? result.baseUrl ?? null }))); } + +export function listCommonAppPipelineHistory(repoId: string, page = 1, limit = 5) { + const params = new URLSearchParams({ + page: String(page), + limit: String(limit) + }); + return apiRequest<{ + page: number; + pageSize: number; + hasNext: boolean; + pipelines: CommonAppPipeline[]; + }>(`/woodpecker/repos/${encodeURIComponent(repoId)}/pipelines?${params.toString()}`); +}