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

This commit is contained in:
2026-07-07 17:08:33 +09:00
3 changed files with 26 additions and 9 deletions
@@ -80,6 +80,7 @@ export function DeploymentManagementPage() {
const [deployModalOpen, setDeployModalOpen] = useState(false);
const [selectedDeployment, setSelectedDeployment] = useState<DeploymentJob | null>(null);
const [loading, setLoading] = useState(true);
const [deploying, setDeploying] = useState(false);
const [page, setPage] = useState(1);
const [range, setRange] = useState<DeploymentRange>('week');
const [totalDeployments, setTotalDeployments] = useState(0);
@@ -159,6 +160,8 @@ export function DeploymentManagementPage() {
};
const deploy = async () => {
if (deploying) return;
setDeploying(true);
try {
const result = await createDeployment({ releaseId, environment: 'prod', namespace, updateLauncherAppVersion });
toast({ type: 'success', message: '배포 작업이 실행되었습니다.' });
@@ -167,6 +170,8 @@ export function DeploymentManagementPage() {
await refresh();
} catch (error) {
toast({ type: 'error', message: error instanceof Error ? error.message : '배포 실행에 실패했습니다.' });
} finally {
setDeploying(false);
}
};
@@ -310,9 +315,11 @@ export function DeploymentManagementPage() {
</Card>
</div>
<Modal open={deployModalOpen} title="배포 실행" onClose={() => setDeployModalOpen(false)} footer={<><Button variant="outline" onClick={() => setDeployModalOpen(false)}>취소</Button><Button disabled={!releaseId || !namespace} onClick={deploy}>실행</Button></>}>
<Modal open={deployModalOpen} title="배포 실행" onClose={() => {
if (!deploying) setDeployModalOpen(false);
}} footer={<><Button variant="outline" disabled={deploying} onClick={() => setDeployModalOpen(false)}>취소</Button><Button disabled={deploying || !releaseId || !namespace} onClick={deploy}>{deploying ? '실행 중' : '실행'}</Button></>}>
<div className="space-y-3">
<select className="h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none" value={releaseId} onChange={(event) => {
<select className="h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none" value={releaseId} disabled={deploying} onChange={(event) => {
const release = releases.find((item) => item.id === event.target.value);
setReleaseId(event.target.value);
setNamespace(release?.app.namespace || '');
@@ -320,12 +327,13 @@ export function DeploymentManagementPage() {
<option value="">릴리즈 선택</option>
{deployableReleases.map((release) => <option key={release.id} value={release.id}>{release.app.name} {release.version}</option>)}
</select>
<input className="h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none" value={namespace} onChange={(event) => setNamespace(event.target.value)} placeholder="stdo-lms" />
<input className="h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none" value={namespace} disabled={deploying} onChange={(event) => setNamespace(event.target.value)} placeholder="stdo-lms" />
<label className="flex items-center gap-2 rounded-md border border-border bg-muted/20 px-3 py-2 text-sm text-foreground">
<input
type="checkbox"
className="size-4 accent-primary"
checked={updateLauncherAppVersion}
disabled={deploying}
onChange={(event) => setUpdateLauncherAppVersion(event.target.checked)}
/>
런처 앱 버전 업데이트
@@ -75,6 +75,7 @@ export function MigrationManagementPage() {
const [selectedJob, setSelectedJob] = useState<RuntimeControlJob | null>(null);
const [confirmOpen, setConfirmOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [runningMigration, setRunningMigration] = useState(false);
const [page, setPage] = useState(1);
const [range, setRange] = useState<MigrationRange>('week');
const [totalJobs, setTotalJobs] = useState(0);
@@ -152,6 +153,8 @@ export function MigrationManagementPage() {
}, [range]);
const runMigration = async () => {
if (runningMigration) return;
setRunningMigration(true);
try {
setConfirmOpen(false);
const result = await createMigration({ releaseId, migrationComponentId, imageComponentName, namespace, command, envSecretName });
@@ -160,6 +163,8 @@ export function MigrationManagementPage() {
await refresh();
} catch (error) {
toast({ type: 'error', message: error instanceof Error ? error.message : '마이그레이션 실행에 실패했습니다.' });
} finally {
setRunningMigration(false);
}
};
@@ -364,11 +369,13 @@ export function MigrationManagementPage() {
<Modal
open={confirmOpen}
title="마이그레이션 실행"
onClose={() => setConfirmOpen(false)}
onClose={() => {
if (!runningMigration) setConfirmOpen(false);
}}
footer={(
<>
<Button variant="outline" onClick={() => setConfirmOpen(false)}>취소</Button>
<Button disabled={!canRun || loading} onClick={runMigration}>실행</Button>
<Button variant="outline" disabled={runningMigration} onClick={() => setConfirmOpen(false)}>취소</Button>
<Button disabled={!canRun || loading || runningMigration} onClick={runMigration}>{runningMigration ? '실행 중' : '실행'}</Button>
</>
)}
>
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
import { ChevronLeft, ChevronRight, Plus } from 'lucide-react';
import { ChevronLeft, ChevronRight, MoreHorizontal, Plus } from 'lucide-react';
import { Badge, Button, Card, CardHeader, Modal } from '../../components/ui';
import { useToast } from '../../components/toast';
import { PageTitle } from '../../layout/AppLayout';
@@ -317,11 +317,13 @@ export function ReleaseManagementPage() {
</div>
<Button
variant="outline"
className="h-8 px-3 text-[13px]"
className="size-8 px-0"
disabled={!release.logs}
title={isLogOpen ? '로그 닫기' : '로그'}
aria-label={isLogOpen ? '로그 닫기' : '로그'}
onClick={() => setOpenLogReleaseId((prev) => prev === release.id ? '' : release.id)}
>
{isLogOpen ? '로그 닫기' : '로그'}
<MoreHorizontal className="size-4" />
</Button>
</div>
<div className="mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs">