import { Plus, RotateCcw } from 'lucide-react'; import { useEffect, useMemo, useState, type FormEvent } from 'react'; import { Badge, Button, Card, CardHeader, Modal } from '../../components/ui'; import { useToast } from '../../components/toast'; import { PageTitle } from '../../layout/AppLayout'; import { LoadingModal, useMinimumLoading } from '../operations/operation-utils'; import { createLauncherVersion, listLauncherBuildJobs, listLauncherVersions, type LauncherBuildJob, type LauncherVersion } from './launcher-api'; function artifactName(version: string) { return `autonomy-studio-launcher-${version || '0.0.0'}.deb`; } export function LauncherVersionsPage() { const { toast } = useToast(); const [versions, setVersions] = useState([]); const [buildJobs, setBuildJobs] = useState([]); const [loading, setLoading] = useState(true); const [releaseOpen, setReleaseOpen] = useState(false); const [saving, setSaving] = useState(false); const [releaseForm, setReleaseForm] = useState({ buildJobId: '', version: '', minimumVersion: '', downloadUrl: '', releaseNotes: '' }); const loadingVisible = useMinimumLoading(loading); const launcher = useMemo(() => versions.find((item) => item.isActive) ?? versions[0], [versions]); const successfulBuildJobs = useMemo(() => buildJobs.filter((job) => job.status === 'SUCCEEDED'), [buildJobs]); const selectedBuildJob = useMemo( () => successfulBuildJobs.find((job) => job.id === releaseForm.buildJobId), [releaseForm.buildJobId, successfulBuildJobs] ); const currentArtifact = selectedBuildJob?.artifact || artifactName(releaseForm.version); const refresh = () => { setLoading(true); Promise.all([listLauncherVersions(), listLauncherBuildJobs()]) .then(([nextVersions, nextBuildJobs]) => { setVersions(nextVersions); setBuildJobs(nextBuildJobs); }) .catch((error: Error) => { toast({ type: 'error', title: '런처 버전 조회 실패', message: error.message }); }) .finally(() => setLoading(false)); }; useEffect(() => { refresh(); }, []); const openRelease = () => { const nextVersion = ''; const latestBuild = successfulBuildJobs[0]; setReleaseForm({ buildJobId: latestBuild?.id ?? '', version: nextVersion, minimumVersion: launcher?.minimumVersion ?? '', downloadUrl: latestBuild?.downloadUrl ?? '', releaseNotes: '' }); setReleaseOpen(true); }; const selectBuildJob = (buildJobId: string) => { const buildJob = successfulBuildJobs.find((job) => job.id === buildJobId); setReleaseForm((prev) => ({ ...prev, buildJobId, version: buildJob?.version ?? prev.version, downloadUrl: buildJob?.downloadUrl ?? prev.downloadUrl })); }; const saveRelease = (event: FormEvent) => { event.preventDefault(); if (!releaseForm.version.trim() || !releaseForm.downloadUrl.trim()) return; setSaving(true); createLauncherVersion({ version: releaseForm.version.trim(), minimumVersion: releaseForm.minimumVersion.trim() || undefined, artifact: currentArtifact, downloadUrl: releaseForm.downloadUrl.trim(), releaseNotes: releaseForm.releaseNotes.trim() || undefined, repository: selectedBuildJob?.repository ?? launcher?.repository ?? undefined, branch: selectedBuildJob?.branch ?? launcher?.branch ?? 'release', commitSha: selectedBuildJob?.commitSha ?? launcher?.commitSha ?? undefined }) .then(() => { toast({ type: 'success', message: '릴리즈를 등록했습니다.' }); setReleaseOpen(false); refresh(); }) .catch((error: Error) => { toast({ type: 'error', title: '릴리즈 등록 실패', message: error.message }); }) .finally(() => setSaving(false)); }; return ( <> } /> {!loading && !launcher ? 등록된 런처 버전이 없습니다. : null} {launcher ? (

Autonomy Studio Launcher

{launcher.isActive ? '활성' : '비활성'}

최신 {launcher.version} · 최소 지원 {launcher.minimumVersion || '-'}

배포 파일

{launcher.artifact}

다운로드 URL

{launcher.downloadUrl || '-'}

소스 기준

Repository

{launcher.repository || '-'}

Branch

{launcher.branch || '-'}

Commit SHA

{launcher.commitSha || '-'}

{launcher.releaseNotes || '-'}
) : null} setReleaseOpen(false)} className="!max-w-3xl" footer={<>} >

배포 파일 기준 등록

빌드가 끝난 `.deb` 파일의 버전과 다운로드 URL을 런처 배포 기준으로 저장합니다.

배포 파일

{currentArtifact}