diff --git a/src/pages/launcher/LauncherBuildPage.tsx b/src/pages/launcher/LauncherBuildPage.tsx index 38e4b6d..3e7f7aa 100644 --- a/src/pages/launcher/LauncherBuildPage.tsx +++ b/src/pages/launcher/LauncherBuildPage.tsx @@ -7,8 +7,10 @@ import { formatDateTime } from '../../lib/date-format'; import { LoadingModal, useMinimumLoading } from '../operations/operation-utils'; import { createLauncherBuildJob, + getLauncherSource, listLauncherBuildJobs, listLauncherVersions, + type LauncherSource, type LauncherBuildJob, type LauncherVersion } from './launcher-api'; @@ -43,6 +45,7 @@ function jobTime(job: LauncherBuildJob) { } const launcherBuildJobPageSize = 3; +type BuildTarget = 'launcher-client' | 'launcher-backend'; function sanitizeObjectKeyPart(value: string) { return value @@ -70,7 +73,9 @@ function launcherArtifactKey(version: string, branch: string, commitSha: string, export function LauncherBuildPage() { const { toast } = useToast(); + const [target, setTarget] = useState('launcher-client'); const [versions, setVersions] = useState([]); + const [source, setSource] = useState(null); const [version, setVersion] = useState(''); const [branch, setBranch] = useState('release'); const [commitSha, setCommitSha] = useState(''); @@ -81,10 +86,16 @@ export function LauncherBuildPage() { const loadingVisible = useMinimumLoading(loading); const activeVersion = useMemo(() => versions.find((item) => item.isActive) ?? versions[0], [versions]); + const isBackend = target === 'launcher-backend'; + const backendSource = source?.backend; + const clientSource = source?.client; const artifactName = `autonomy-studio-launcher-${version || activeVersion?.version || '0.0.0'}.deb`; + const backendImageName = backendSource?.image || 'localhost:5000/studio-launcher-be'; + const backendImageTag = `${backendImageName.replace(/\/$/, '')}:${version || '0.1.0'}`; + const backendDockerfile = backendSource?.dockerfile || 'Dockerfile'; const uploadPath = launcherArtifactKey(version || activeVersion?.version || '0.0.0', branch, commitSha, artifactName); const downloadUrl = r2PublicBaseUrl ? `${r2PublicBaseUrl.replace(/\/$/, '')}/${encodeObjectKey(uploadPath)}` : '백엔드 R2 설정 기준으로 생성됩니다.'; - const sortedJobs = useMemo(() => [...jobs].sort((a, b) => jobTime(b) - jobTime(a)), [jobs]); + const sortedJobs = useMemo(() => [...jobs].filter((job) => job.target === target).sort((a, b) => jobTime(b) - jobTime(a)), [jobs, target]); const totalPages = Math.max(1, Math.ceil(sortedJobs.length / launcherBuildJobPageSize)); const currentPage = Math.min(page, totalPages); const pageStart = (currentPage - 1) * launcherBuildJobPageSize; @@ -93,14 +104,15 @@ export function LauncherBuildPage() { const refresh = () => { setLoading(true); - Promise.all([listLauncherVersions(), listLauncherBuildJobs()]) - .then(([nextVersions, nextJobs]) => { + Promise.all([listLauncherVersions(), listLauncherBuildJobs(), getLauncherSource()]) + .then(([nextVersions, nextJobs, nextSource]) => { setVersions(nextVersions); setJobs(nextJobs); + setSource(nextSource.source); const nextVersion = nextVersions.find((item) => item.isActive) ?? nextVersions[0]; setVersion(nextVersion?.version ?? ''); - setBranch(nextVersion?.branch ?? 'release'); - setCommitSha(nextVersion?.commitSha ?? ''); + setBranch(target === 'launcher-backend' ? nextSource.source.backend?.branch ?? 'release' : nextSource.source.client?.branch ?? nextVersion?.branch ?? 'release'); + setCommitSha(target === 'launcher-backend' ? nextSource.source.backend?.commitSha ?? '' : nextSource.source.client?.commitSha ?? nextVersion?.commitSha ?? ''); }) .catch((error: Error) => { toast({ type: 'error', title: '런처 버전 조회 실패', message: error.message }); @@ -112,6 +124,18 @@ export function LauncherBuildPage() { refresh(); }, []); + useEffect(() => { + if (!source) return; + if (target === 'launcher-backend') { + setBranch(source.backend?.branch ?? 'release'); + setCommitSha(source.backend?.commitSha ?? ''); + return; + } + + setBranch(source.client?.branch ?? activeVersion?.branch ?? 'release'); + setCommitSha(source.client?.commitSha ?? activeVersion?.commitSha ?? ''); + }, [activeVersion?.branch, activeVersion?.commitSha, source, target]); + useEffect(() => { if (!hasActiveJob) return; const timer = window.setInterval(() => { @@ -135,14 +159,14 @@ export function LauncherBuildPage() { const createJob = () => { setLoading(true); createLauncherBuildJob({ - target: 'launcher-client', - repository: activeVersion?.repository ?? undefined, + target, + repository: isBackend ? backendSource?.repository ?? undefined : clientSource?.repository ?? activeVersion?.repository ?? undefined, branch, commitSha: commitSha.trim() || undefined, version: version.trim(), - uploadPath, - artifact: artifactName, - downloadUrl: r2PublicBaseUrl ? downloadUrl : undefined + uploadPath: isBackend ? backendDockerfile : uploadPath, + artifact: isBackend ? backendImageName.replace(/^localhost:5000\//, '') : artifactName, + downloadUrl: isBackend ? backendImageTag : r2PublicBaseUrl ? downloadUrl : undefined }) .then(({ job }) => { setJobs((prev) => [job, ...prev]); @@ -161,7 +185,7 @@ export function LauncherBuildPage() { @@ -170,12 +194,21 @@ export function LauncherBuildPage() { } /> +
+ + +
+
- +
-

예상 산출물

-

{artifactName}

-

R2 Object Key

-

{uploadPath}

-

다운로드 URL

-

{downloadUrl}

+

{isBackend ? '이미지' : '예상 산출물'}

+

{isBackend ? backendImageTag : artifactName}

+

{isBackend ? 'Dockerfile' : 'R2 Object Key'}

+

{isBackend ? backendDockerfile : uploadPath}

+

{isBackend ? 'Repository' : '다운로드 URL'}

+

{isBackend ? backendSource?.repository || '-' : downloadUrl}

- +
+ ))} +
+
- +
+ {activeTarget === 'launcher-backend' ? ( + <> + + + + ) : null}
- @@ -148,17 +259,19 @@ export function LauncherSourcePage() { - +
- 버전 - {activeVersion?.version ?? '-'} + 기준 + + {activeTarget === 'launcher-client' ? activeVersion?.version ?? '-' : activeForm.image || '-'} +

HTTP Clone URL

-

{httpCloneUrl(repository)}

-
@@ -166,14 +279,14 @@ export function LauncherSourcePage() {

SSH Clone URL

-

{sshCloneUrl(repository)}

-
- {repository ? ( - + {activeForm.repository ? ( + 저장소 열기 diff --git a/src/pages/launcher/launcher-api.ts b/src/pages/launcher/launcher-api.ts index 5b211aa..76bd8b9 100644 --- a/src/pages/launcher/launcher-api.ts +++ b/src/pages/launcher/launcher-api.ts @@ -40,6 +40,18 @@ export type LauncherSource = { repository?: string | null; branch?: string | null; commitSha?: string | null; + client?: { + repository?: string | null; + branch?: string | null; + commitSha?: string | null; + }; + backend?: { + repository?: string | null; + branch?: string | null; + commitSha?: string | null; + image?: string | null; + dockerfile?: string | null; + }; }; export type LauncherAppLink = { @@ -115,7 +127,14 @@ export function getLauncherSource() { return apiRequest<{ source: LauncherSource }>('/launcher/source'); } -export function updateLauncherSource(input: { repository: string; branch?: string; commitSha?: string }) { +export function updateLauncherSource(input: { + target?: 'launcher-client' | 'launcher-backend'; + repository: string; + branch?: string; + commitSha?: string; + image?: string; + dockerfile?: string; +}) { return apiRequest<{ source: LauncherSource }>('/launcher/source', { method: 'PATCH', body: JSON.stringify(input) @@ -127,6 +146,11 @@ export function listPlatformRepos(limit = 50) { .then((result) => result.repos); } +export function listStudioRepos(limit = 50) { + return apiRequest<{ owner: string | null; repos: GiteaRepo[] }>(`/gitea/repos?owner=studio&limit=${limit}`) + .then((result) => result.repos); +} + export function listLauncherBuildJobs() { return apiRequest('/launcher/builds'); }