manage launcher client build env
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-06 10:39:22 +09:00
parent 048f39246e
commit 42cb5904c9
2 changed files with 96 additions and 4 deletions
+79 -4
View File
@@ -1,4 +1,4 @@
import { ChevronLeft, ChevronRight, MoreHorizontal, Play, RotateCcw } from 'lucide-react';
import { ChevronLeft, ChevronRight, MoreHorizontal, Play, Plus, RotateCcw, Trash2 } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { Badge, Button, Card, CardHeader, Modal } from '../../components/ui';
import { useToast } from '../../components/toast';
@@ -8,10 +8,13 @@ import { LoadingModal, useMinimumLoading } from '../operations/operation-utils';
import {
createLauncherBuildJob,
getLauncherSource,
listLauncherBuildEnv,
listLauncherBuildJobs,
listLauncherVersions,
saveLauncherBuildEnv,
type LauncherSource,
type LauncherBuildJob,
type LauncherBuildEnvVariable,
type LauncherVersion
} from './launcher-api';
@@ -80,6 +83,8 @@ export function LauncherBuildPage() {
const [branch, setBranch] = useState('release');
const [commitSha, setCommitSha] = useState('');
const [jobs, setJobs] = useState<LauncherBuildJob[]>([]);
const [buildEnv, setBuildEnv] = useState<LauncherBuildEnvVariable[]>([]);
const [buildEnvSaving, setBuildEnvSaving] = useState(false);
const [selectedJob, setSelectedJob] = useState<LauncherBuildJob | null>(null);
const [confirmOpen, setConfirmOpen] = useState(false);
const [loading, setLoading] = useState(true);
@@ -104,11 +109,12 @@ export function LauncherBuildPage() {
const refresh = () => {
setLoading(true);
Promise.all([listLauncherVersions(), listLauncherBuildJobs(), getLauncherSource()])
.then(([nextVersions, nextJobs, nextSource]) => {
Promise.all([listLauncherVersions(), listLauncherBuildJobs(), getLauncherSource(), listLauncherBuildEnv()])
.then(([nextVersions, nextJobs, nextSource, nextBuildEnv]) => {
setVersions(nextVersions);
setJobs(nextJobs);
setSource(nextSource.source);
setBuildEnv(nextBuildEnv.variables ?? []);
const nextVersion = nextVersions.find((item) => item.isActive) ?? nextVersions[0];
setVersion(nextVersion?.version ?? '');
setBranch(target === 'launcher-backend' ? nextSource.source.backend?.branch ?? 'release' : nextSource.source.client?.branch ?? nextVersion?.branch ?? 'release');
@@ -181,6 +187,33 @@ export function LauncherBuildPage() {
.finally(() => setLoading(false));
};
const updateBuildEnv = (index: number, patch: Partial<LauncherBuildEnvVariable>) => {
setBuildEnv((prev) => prev.map((variable, variableIndex) => (
variableIndex === index ? { ...variable, ...patch } : variable
)));
};
const removeBuildEnv = (index: number) => {
setBuildEnv((prev) => prev.filter((_, variableIndex) => variableIndex !== index));
};
const persistBuildEnv = () => {
setBuildEnvSaving(true);
saveLauncherBuildEnv(
buildEnv
.map((variable) => ({ ...variable, key: variable.key.trim() }))
.filter((variable) => variable.key)
)
.then((result) => {
setBuildEnv(result.variables ?? []);
toast({ type: 'success', title: '빌드 환경변수 저장', message: '런처 클라이언트 빌드 환경변수를 저장했습니다.' });
})
.catch((error: Error) => {
toast({ type: 'error', title: '빌드 환경변수 저장 실패', message: error.message });
})
.finally(() => setBuildEnvSaving(false));
};
return (
<>
<PageTitle
@@ -232,7 +265,49 @@ export function LauncherBuildPage() {
<p className="mt-2 text-xs text-muted-foreground">Repository</p>
<p className="mt-1 break-all font-mono text-xs text-foreground">{backendSource?.repository || '-'}</p>
</div>
) : null}
) : (
<div className="rounded-md border border-border bg-background p-3">
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-xs font-medium text-muted-foreground">클라이언트 빌드 환경변수</p>
<p className="mt-1 text-xs leading-5 text-muted-foreground">Vite 빌드 시점에 주입되는 값입니다. 저장 후 실행한 빌드부터 반영됩니다.</p>
</div>
<Button variant="outline" className="h-8 px-3 text-[13px]" onClick={persistBuildEnv} disabled={buildEnvSaving}>
{buildEnvSaving ? '저장 중' : '저장'}
</Button>
</div>
<div className="mt-3 space-y-2">
{buildEnv.map((variable, index) => (
<div key={`${variable.key}-${index}`} className="grid gap-2 md:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)_auto]">
<input
className="h-9 rounded-md border border-input bg-background px-3 font-mono text-xs outline-none focus:ring-2 focus:ring-ring"
value={variable.key}
onChange={(event) => updateBuildEnv(index, { key: event.target.value })}
placeholder="VITE_ATNMY_STDO_API_BASE_URL"
/>
<input
className="h-9 rounded-md border border-input bg-background px-3 font-mono text-xs outline-none focus:ring-2 focus:ring-ring"
value={variable.value}
onChange={(event) => updateBuildEnv(index, { value: event.target.value })}
placeholder="https://portal-api.autonomystudio.net"
/>
<Button variant="outline" className="size-9 px-0" title="삭제" aria-label="삭제" onClick={() => removeBuildEnv(index)}>
<Trash2 className="size-4" />
</Button>
</div>
))}
{buildEnv.length === 0 ? <p className="rounded-md border border-dashed border-border px-3 py-4 text-center text-xs text-muted-foreground">등록된 빌드 환경변수가 없습니다.</p> : null}
</div>
<Button
variant="outline"
className="mt-3 h-8 px-3 text-[13px]"
onClick={() => setBuildEnv((prev) => [...prev, { key: '', value: '', isSecret: false }])}
>
<Plus className="size-4" />
추가
</Button>
</div>
)}
</div>
</Card>