This commit is contained in:
@@ -74,6 +74,16 @@ export function DeploymentManagementPage() {
|
|||||||
() => releases.filter((release) => !selectedAppId || release.appId === selectedAppId),
|
() => releases.filter((release) => !selectedAppId || release.appId === selectedAppId),
|
||||||
[releases, selectedAppId]
|
[releases, selectedAppId]
|
||||||
);
|
);
|
||||||
|
const deployedReleaseIds = useMemo(
|
||||||
|
() => new Set(deployments
|
||||||
|
.filter((deployment) => deployment.status === 'SUCCEEDED' && deployment.releaseId)
|
||||||
|
.map((deployment) => deployment.releaseId!)),
|
||||||
|
[deployments]
|
||||||
|
);
|
||||||
|
const deployableReleases = useMemo(
|
||||||
|
() => filteredReleases.filter((release) => !deployedReleaseIds.has(release.id)),
|
||||||
|
[filteredReleases, deployedReleaseIds]
|
||||||
|
);
|
||||||
const filteredDeployments = useMemo(
|
const filteredDeployments = useMemo(
|
||||||
() => deployments
|
() => deployments
|
||||||
.filter((deployment) => !selectedAppId || deployment.appId === selectedAppId)
|
.filter((deployment) => !selectedAppId || deployment.appId === selectedAppId)
|
||||||
@@ -110,11 +120,11 @@ export function DeploymentManagementPage() {
|
|||||||
}, [selectedRelease?.id]);
|
}, [selectedRelease?.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setReleaseId((prev) => filteredReleases.some((release) => release.id === prev) ? prev : filteredReleases[0]?.id || '');
|
setReleaseId((prev) => deployableReleases.some((release) => release.id === prev) ? prev : deployableReleases[0]?.id || '');
|
||||||
setNamespace(filteredReleases[0]?.app?.namespace || '');
|
setNamespace(deployableReleases[0]?.app?.namespace || '');
|
||||||
}, [filteredReleases]);
|
}, [deployableReleases]);
|
||||||
|
|
||||||
const deployableCards = useMemo(() => filteredReleases.slice(0, 8), [filteredReleases]);
|
const deployableCards = useMemo(() => deployableReleases.slice(0, 8), [deployableReleases]);
|
||||||
|
|
||||||
const openDeployModal = (release?: AppRelease) => {
|
const openDeployModal = (release?: AppRelease) => {
|
||||||
if (release) {
|
if (release) {
|
||||||
@@ -145,7 +155,7 @@ export function DeploymentManagementPage() {
|
|||||||
title="배포 관리"
|
title="배포 관리"
|
||||||
description="생성된 릴리즈를 운영 환경에 배포하고 재배포와 롤백을 관리합니다."
|
description="생성된 릴리즈를 운영 환경에 배포하고 재배포와 롤백을 관리합니다."
|
||||||
actions={
|
actions={
|
||||||
<Button className="h-8 px-3 text-[13px]" disabled={filteredReleases.length === 0} onClick={() => openDeployModal()}>
|
<Button className="h-8 px-3 text-[13px]" disabled={deployableReleases.length === 0} onClick={() => openDeployModal()}>
|
||||||
<Plus className="size-4" />
|
<Plus className="size-4" />
|
||||||
배포 실행
|
배포 실행
|
||||||
</Button>
|
</Button>
|
||||||
@@ -236,7 +246,7 @@ export function DeploymentManagementPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="min-w-0 overflow-hidden">
|
<Card className="min-w-0 overflow-hidden">
|
||||||
<CardHeader title="배포 가능 릴리즈" description="성공 상태의 릴리즈만 배포 대상으로 사용할 수 있습니다." />
|
<CardHeader title="배포 가능 릴리즈" description="아직 성공 배포되지 않은 릴리즈만 표시합니다." />
|
||||||
<div className="space-y-3 p-4">
|
<div className="space-y-3 p-4">
|
||||||
{deployableCards.length === 0 ? (
|
{deployableCards.length === 0 ? (
|
||||||
<div className="rounded-md border border-border px-3 py-4 text-center text-sm text-muted-foreground">
|
<div className="rounded-md border border-border px-3 py-4 text-center text-sm text-muted-foreground">
|
||||||
@@ -269,7 +279,7 @@ export function DeploymentManagementPage() {
|
|||||||
setNamespace(release?.app.namespace || '');
|
setNamespace(release?.app.namespace || '');
|
||||||
}}>
|
}}>
|
||||||
<option value="">릴리즈 선택</option>
|
<option value="">릴리즈 선택</option>
|
||||||
{filteredReleases.map((release) => <option key={release.id} value={release.id}>{release.app.name} {release.version}</option>)}
|
{deployableReleases.map((release) => <option key={release.id} value={release.id}>{release.app.name} {release.version}</option>)}
|
||||||
</select>
|
</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} 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">
|
<label className="flex items-center gap-2 rounded-md border border-border bg-muted/20 px-3 py-2 text-sm text-foreground">
|
||||||
|
|||||||
@@ -209,9 +209,11 @@ export function RuntimeConfigPage() {
|
|||||||
|
|
||||||
const addVariable = () => {
|
const addVariable = () => {
|
||||||
if (!selectedProfile || !variableComponentId || !variableKey.trim() || !variableValue.trim()) return;
|
if (!selectedProfile || !variableComponentId || !variableKey.trim() || !variableValue.trim()) return;
|
||||||
|
const key = variableKey.trim();
|
||||||
|
setError('');
|
||||||
createRuntimeVariable(selectedProfile.id, {
|
createRuntimeVariable(selectedProfile.id, {
|
||||||
componentId: variableComponentId,
|
componentId: variableComponentId,
|
||||||
key: variableKey.trim(),
|
key,
|
||||||
value: variableValue.trim(),
|
value: variableValue.trim(),
|
||||||
isSecret: variableIsSecret
|
isSecret: variableIsSecret
|
||||||
})
|
})
|
||||||
@@ -219,9 +221,17 @@ export function RuntimeConfigPage() {
|
|||||||
setVariableKey('');
|
setVariableKey('');
|
||||||
setVariableValue('');
|
setVariableValue('');
|
||||||
setVariableIsSecret(false);
|
setVariableIsSecret(false);
|
||||||
|
toast({
|
||||||
|
type: 'success',
|
||||||
|
title: '설정값 등록',
|
||||||
|
message: `${key} 설정값을 추가했습니다. 실행 중인 파드에는 적용 버튼을 눌러야 반영됩니다.`
|
||||||
|
});
|
||||||
refreshProfiles(appId, selectedProfile.id);
|
refreshProfiles(appId, selectedProfile.id);
|
||||||
})
|
})
|
||||||
.catch((createError: Error) => setError(createError.message));
|
.catch((createError: Error) => {
|
||||||
|
setError(createError.message);
|
||||||
|
toast({ type: 'error', title: '설정값 등록 실패', message: createError.message });
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeVariable = (variableId: string) => {
|
const removeVariable = (variableId: string) => {
|
||||||
@@ -497,7 +507,7 @@ export function RuntimeConfigPage() {
|
|||||||
<label className="block">
|
<label className="block">
|
||||||
<span className="text-xs font-medium text-muted-foreground">Key</span>
|
<span className="text-xs font-medium text-muted-foreground">Key</span>
|
||||||
<div className="mt-2 flex items-center gap-2">
|
<div className="mt-2 flex items-center gap-2">
|
||||||
<input className="h-9 min-w-0 flex-1 rounded-md border border-input bg-background px-3 font-mono text-sm text-foreground outline-none placeholder:text-muted-foreground focus:ring-2 focus:ring-ring" value={variableKey} onChange={(event) => setVariableKey(event.target.value)} placeholder="DATABASE_URL" />
|
<input className="h-9 min-w-0 flex-1 rounded-md border border-input bg-background px-3 font-mono text-sm text-foreground outline-none placeholder:text-muted-foreground focus:ring-2 focus:ring-ring" value={variableKey} onChange={(event) => setVariableKey(event.target.value)} placeholder="키 입력" />
|
||||||
<label className="flex shrink-0 items-center gap-2 text-xs text-muted-foreground">
|
<label className="flex shrink-0 items-center gap-2 text-xs text-muted-foreground">
|
||||||
<input type="checkbox" className="size-4 accent-primary" checked={variableIsSecret} onChange={(event) => setVariableIsSecret(event.target.checked)} />
|
<input type="checkbox" className="size-4 accent-primary" checked={variableIsSecret} onChange={(event) => setVariableIsSecret(event.target.checked)} />
|
||||||
Secret
|
Secret
|
||||||
|
|||||||
Reference in New Issue
Block a user