merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-07 09:24:37 +09:00
2 changed files with 38 additions and 17 deletions
+18 -16
View File
@@ -10,6 +10,7 @@ import {
getLauncherSource, getLauncherSource,
listLauncherBuildEnv, listLauncherBuildEnv,
listLauncherBuildJobs, listLauncherBuildJobs,
listLauncherBuildJobsPage,
listLauncherVersions, listLauncherVersions,
saveLauncherBuildEnv, saveLauncherBuildEnv,
type LauncherSource, type LauncherSource,
@@ -104,7 +105,7 @@ export function LauncherBuildPage() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [range, setRange] = useState<BuildRange>('week'); const [range, setRange] = useState<BuildRange>('week');
const [hasNextPage, setHasNextPage] = useState(false); const [totalJobs, setTotalJobs] = useState(0);
const loadingVisible = useMinimumLoading(loading); const loadingVisible = useMinimumLoading(loading);
const activeVersion = useMemo(() => versions.find((item) => item.isActive) ?? versions[0], [versions]); const activeVersion = useMemo(() => versions.find((item) => item.isActive) ?? versions[0], [versions]);
@@ -117,18 +118,19 @@ export function LauncherBuildPage() {
const uploadPath = launcherArtifactKey(version || activeVersion?.version || '0.0.0', branch, commitSha, artifactName); const uploadPath = launcherArtifactKey(version || activeVersion?.version || '0.0.0', branch, commitSha, artifactName);
const downloadUrl = r2PublicBaseUrl ? `${r2PublicBaseUrl.replace(/\/$/, '')}/${encodeObjectKey(uploadPath)}` : '백엔드 R2 설정 기준으로 생성됩니다.'; const downloadUrl = r2PublicBaseUrl ? `${r2PublicBaseUrl.replace(/\/$/, '')}/${encodeObjectKey(uploadPath)}` : '백엔드 R2 설정 기준으로 생성됩니다.';
const currentPage = page; const currentPage = page;
const totalPages = Math.max(1, Math.ceil(totalJobs / launcherBuildJobPageSize));
const pagedJobs = jobs; const pagedJobs = jobs;
const hasActiveJob = jobs.some((job) => job.status === 'PENDING' || job.status === 'RUNNING'); const hasActiveJob = jobs.some((job) => job.status === 'PENDING' || job.status === 'RUNNING');
const loadBuildJobs = (nextPage = page, nextTarget = target, nextRange = range) => ( const loadBuildJobs = (nextPage = page, nextTarget = target, nextRange = range) => (
listLauncherBuildJobs({ listLauncherBuildJobsPage({
target: nextTarget, target: nextTarget,
page: nextPage, page: nextPage,
limit: launcherBuildJobPageSize + 1, limit: launcherBuildJobPageSize,
since: buildRangeSince(nextRange) since: buildRangeSince(nextRange)
}).then((nextJobs) => { }).then((result) => {
setJobs(nextJobs.slice(0, launcherBuildJobPageSize)); setJobs(result.jobs);
setHasNextPage(nextJobs.length > launcherBuildJobPageSize); setTotalJobs(result.total);
}) })
); );
@@ -136,10 +138,10 @@ export function LauncherBuildPage() {
setLoading(true); setLoading(true);
Promise.all([ Promise.all([
listLauncherVersions(), listLauncherVersions(),
listLauncherBuildJobs({ listLauncherBuildJobsPage({
target, target,
page, page,
limit: launcherBuildJobPageSize + 1, limit: launcherBuildJobPageSize,
since: buildRangeSince(range) since: buildRangeSince(range)
}), }),
getLauncherSource(), getLauncherSource(),
@@ -147,8 +149,8 @@ export function LauncherBuildPage() {
]) ])
.then(([nextVersions, nextJobs, nextSource, nextBuildEnv]) => { .then(([nextVersions, nextJobs, nextSource, nextBuildEnv]) => {
setVersions(nextVersions); setVersions(nextVersions);
setJobs(nextJobs.slice(0, launcherBuildJobPageSize)); setJobs(nextJobs.jobs);
setHasNextPage(nextJobs.length > launcherBuildJobPageSize); setTotalJobs(nextJobs.total);
setSource(nextSource.source); setSource(nextSource.source);
setBuildEnv(nextBuildEnv.variables ?? []); setBuildEnv(nextBuildEnv.variables ?? []);
const nextVersion = nextVersions.find((item) => item.isActive) ?? nextVersions[0]; const nextVersion = nextVersions.find((item) => item.isActive) ?? nextVersions[0];
@@ -173,7 +175,7 @@ export function LauncherBuildPage() {
useEffect(() => { useEffect(() => {
loadBuildJobs().catch((error: Error) => { loadBuildJobs().catch((error: Error) => {
setJobs([]); setJobs([]);
setHasNextPage(false); setTotalJobs(0);
toast({ type: 'error', title: '빌드 작업 조회 실패', message: error.message }); toast({ type: 'error', title: '빌드 작업 조회 실패', message: error.message });
}); });
}, [target, page, range]); }, [target, page, range]);
@@ -226,7 +228,7 @@ export function LauncherBuildPage() {
setPage(1); setPage(1);
setRange('week'); setRange('week');
setJobs((prev) => [job, ...prev].slice(0, launcherBuildJobPageSize)); setJobs((prev) => [job, ...prev].slice(0, launcherBuildJobPageSize));
setHasNextPage(jobs.length >= launcherBuildJobPageSize); setTotalJobs((prev) => prev + 1);
setSelectedJob(job); setSelectedJob(job);
toast({ type: 'success', title: '빌드 작업', message: '런처 빌드 작업이 생성되었습니다.' }); toast({ type: 'success', title: '빌드 작업', message: '런처 빌드 작업이 생성되었습니다.' });
}) })
@@ -385,7 +387,7 @@ export function LauncherBuildPage() {
새로고침 새로고침
</Button> </Button>
</div> </div>
<div className="space-y-3 p-4"> <div className="min-h-[638px] space-y-3 p-4">
{jobs.length === 0 ? ( {jobs.length === 0 ? (
<div className="flex min-h-44 items-center justify-center text-center text-sm text-muted-foreground"> <div className="flex min-h-44 items-center justify-center text-center text-sm text-muted-foreground">
빌드 작업이 없습니다. 빌드 작업이 없습니다.
@@ -429,7 +431,7 @@ export function LauncherBuildPage() {
</div> </div>
<div className="flex items-center justify-between gap-4 border-t border-border px-5 py-3 text-sm"> <div className="flex items-center justify-between gap-4 border-t border-border px-5 py-3 text-sm">
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
{loading ? '불러오는 중' : `페이지 ${currentPage}`} {loading ? '불러오는 중' : `총 ${totalJobs}건 · ${currentPage} / ${totalPages}`}
</span> </span>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button
@@ -444,8 +446,8 @@ export function LauncherBuildPage() {
<Button <Button
variant="outline" variant="outline"
className="h-8 px-3 text-[13px]" className="h-8 px-3 text-[13px]"
disabled={!hasNextPage} disabled={currentPage >= totalPages}
onClick={() => setPage((prev) => prev + 1)} onClick={() => setPage((prev) => Math.min(totalPages, prev + 1))}
> >
다음 다음
<ChevronRight className="size-4" /> <ChevronRight className="size-4" />
+20 -1
View File
@@ -239,7 +239,26 @@ export function listLauncherBuildJobs(options?: {
if (options?.limit) params.set('limit', String(options.limit)); if (options?.limit) params.set('limit', String(options.limit));
if (options?.since) params.set('since', options.since); if (options?.since) params.set('since', options.since);
const query = params.toString() ? `?${params.toString()}` : ''; const query = params.toString() ? `?${params.toString()}` : '';
return apiRequest<LauncherBuildJob[]>(`/launcher/builds${query}`); if (query) {
return apiRequest<{ jobs: LauncherBuildJob[]; total: number; page: number; limit: number }>(`/launcher/builds${query}`)
.then((result) => result.jobs);
}
return apiRequest<LauncherBuildJob[]>('/launcher/builds');
}
export function listLauncherBuildJobsPage(options: {
target: string;
page: number;
limit: number;
since?: string;
}) {
const params = new URLSearchParams({
target: options.target,
page: String(options.page),
limit: String(options.limit)
});
if (options.since) params.set('since', options.since);
return apiRequest<{ jobs: LauncherBuildJob[]; total: number; page: number; limit: number }>(`/launcher/builds?${params.toString()}`);
} }
export function getLauncherBackendDeployment() { export function getLauncherBackendDeployment() {