This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
|||||||
createRuntimeProfile,
|
createRuntimeProfile,
|
||||||
createRuntimeVariable,
|
createRuntimeVariable,
|
||||||
deleteRuntimeIngressRule,
|
deleteRuntimeIngressRule,
|
||||||
|
deleteRuntimeProfile,
|
||||||
deleteRuntimeVariable,
|
deleteRuntimeVariable,
|
||||||
loadApps,
|
loadApps,
|
||||||
loadRuntimeProfiles,
|
loadRuntimeProfiles,
|
||||||
@@ -80,6 +81,7 @@ export function RuntimeConfigPage() {
|
|||||||
const [actionKey, setActionKey] = useState('');
|
const [actionKey, setActionKey] = useState('');
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [profileModalOpen, setProfileModalOpen] = useState(false);
|
const [profileModalOpen, setProfileModalOpen] = useState(false);
|
||||||
|
const [deleteProfileTarget, setDeleteProfileTarget] = useState<RuntimeEnvProfile | null>(null);
|
||||||
const [deleteVariableTarget, setDeleteVariableTarget] = useState<RuntimeEnvVariable | null>(null);
|
const [deleteVariableTarget, setDeleteVariableTarget] = useState<RuntimeEnvVariable | null>(null);
|
||||||
const [deleteIngressTarget, setDeleteIngressTarget] = useState<RuntimeIngressRule | null>(null);
|
const [deleteIngressTarget, setDeleteIngressTarget] = useState<RuntimeIngressRule | null>(null);
|
||||||
const loadingVisible = useMinimumLoading(loading);
|
const loadingVisible = useMinimumLoading(loading);
|
||||||
@@ -116,6 +118,7 @@ export function RuntimeConfigPage() {
|
|||||||
profile.environment === profileEnvironment &&
|
profile.environment === profileEnvironment &&
|
||||||
profile.namespace.trim() === profileNamespace.trim()
|
profile.namespace.trim() === profileNamespace.trim()
|
||||||
));
|
));
|
||||||
|
const showDuplicateProfileWarning = Boolean(profileName.trim() && duplicateProfile);
|
||||||
|
|
||||||
const refreshResources = (targetNamespace = namespace) => {
|
const refreshResources = (targetNamespace = namespace) => {
|
||||||
if (!targetNamespace.trim()) {
|
if (!targetNamespace.trim()) {
|
||||||
@@ -235,6 +238,23 @@ export function RuntimeConfigPage() {
|
|||||||
.catch((createError: Error) => setError(createError.message));
|
.catch((createError: Error) => setError(createError.message));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeProfile = () => {
|
||||||
|
if (!deleteProfileTarget) return;
|
||||||
|
const target = deleteProfileTarget;
|
||||||
|
setActionKey(`profile-delete-${target.id}`);
|
||||||
|
deleteRuntimeProfile(target.id)
|
||||||
|
.then(() => {
|
||||||
|
toast({ type: 'success', title: '프로필 삭제', message: `${target.name} 프로필을 삭제했습니다.` });
|
||||||
|
setDeleteProfileTarget(null);
|
||||||
|
return refreshProfiles(appId);
|
||||||
|
})
|
||||||
|
.catch((deleteError: Error) => {
|
||||||
|
setError(deleteError.message);
|
||||||
|
toast({ type: 'error', title: '프로필 삭제 실패', message: deleteError.message });
|
||||||
|
})
|
||||||
|
.finally(() => setActionKey(''));
|
||||||
|
};
|
||||||
|
|
||||||
const addVariable = () => {
|
const addVariable = () => {
|
||||||
if (!selectedProfile || !variableComponentId || !variableKey.trim() || !variableValue.trim()) return;
|
if (!selectedProfile || !variableComponentId || !variableKey.trim() || !variableValue.trim()) return;
|
||||||
const profile = selectedProfile;
|
const profile = selectedProfile;
|
||||||
@@ -367,6 +387,13 @@ export function RuntimeConfigPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openProfileModal = () => {
|
||||||
|
setProfileName('');
|
||||||
|
setProfileEnvironment('dev');
|
||||||
|
setProfileNamespace(selectedApp?.namespace ?? '');
|
||||||
|
setProfileModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageTitle
|
<PageTitle
|
||||||
@@ -374,7 +401,7 @@ export function RuntimeConfigPage() {
|
|||||||
title="실행 설정"
|
title="실행 설정"
|
||||||
description="앱 구성요소별 환경변수와 시크릿을 프로필로 묶어 배포 환경에 적용합니다."
|
description="앱 구성요소별 환경변수와 시크릿을 프로필로 묶어 배포 환경에 적용합니다."
|
||||||
actions={(
|
actions={(
|
||||||
<Button className="h-8 px-3 text-[13px]" onClick={() => setProfileModalOpen(true)} disabled={!appId}>
|
<Button className="h-8 px-3 text-[13px]" onClick={openProfileModal} disabled={!appId}>
|
||||||
<Plus className="size-4" />
|
<Plus className="size-4" />
|
||||||
프로필 생성
|
프로필 생성
|
||||||
</Button>
|
</Button>
|
||||||
@@ -475,6 +502,19 @@ export function RuntimeConfigPage() {
|
|||||||
<span>수정 <b className="text-foreground">{formatDate(profile.updatedAt ?? profile.createdAt)}</b></span>
|
<span>수정 <b className="text-foreground">{formatDate(profile.updatedAt ?? profile.createdAt)}</b></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="size-8 shrink-0 self-end px-0 text-rose-300 hover:text-rose-200 lg:self-center"
|
||||||
|
title="프로필 삭제"
|
||||||
|
aria-label={`${profile.name} 프로필 삭제`}
|
||||||
|
disabled={Boolean(actionKey)}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setDeleteProfileTarget(profile);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 className="size-4" />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -699,7 +739,7 @@ export function RuntimeConfigPage() {
|
|||||||
footer={(
|
footer={(
|
||||||
<>
|
<>
|
||||||
<Button variant="outline" onClick={() => setProfileModalOpen(false)}>취소</Button>
|
<Button variant="outline" onClick={() => setProfileModalOpen(false)}>취소</Button>
|
||||||
<Button disabled={!profileName.trim() || !profileNamespace.trim() || Boolean(duplicateProfile)} onClick={addProfile}>생성</Button>
|
<Button disabled={!profileName.trim() || !profileNamespace.trim() || showDuplicateProfileWarning} onClick={addProfile}>생성</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -728,13 +768,36 @@ export function RuntimeConfigPage() {
|
|||||||
{namespaceOptions.map((item) => <option key={item} value={item} />)}
|
{namespaceOptions.map((item) => <option key={item} value={item} />)}
|
||||||
</datalist>
|
</datalist>
|
||||||
</label>
|
</label>
|
||||||
{duplicateProfile ? (
|
{showDuplicateProfileWarning ? (
|
||||||
<p className="rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-xs text-amber-200">
|
<p className="rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-xs text-amber-200">
|
||||||
이미 같은 환경과 네임스페이스의 프로필이 있습니다. 기존 프로필을 선택해 설정값을 수정하세요.
|
이미 같은 환경과 네임스페이스의 프로필이 있습니다. 기존 프로필을 선택해 설정값을 수정하세요.
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
open={Boolean(deleteProfileTarget)}
|
||||||
|
title="프로필 삭제"
|
||||||
|
onClose={() => {
|
||||||
|
if (!actionKey) setDeleteProfileTarget(null);
|
||||||
|
}}
|
||||||
|
footer={(
|
||||||
|
<>
|
||||||
|
<Button variant="outline" disabled={Boolean(actionKey)} onClick={() => setDeleteProfileTarget(null)}>취소</Button>
|
||||||
|
<Button className="bg-rose-600 text-white hover:bg-rose-500" disabled={Boolean(actionKey)} onClick={removeProfile}>
|
||||||
|
{actionKey.startsWith('profile-delete-') ? '삭제 중' : '삭제'}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="space-y-3 text-sm">
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold text-foreground">{deleteProfileTarget?.name ?? '-'}</span>
|
||||||
|
{' '}프로필을 삭제합니다.
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground">프로필에 등록된 설정값과 접속 설정도 함께 삭제됩니다.</p>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
<Modal
|
<Modal
|
||||||
open={Boolean(deleteVariableTarget)}
|
open={Boolean(deleteVariableTarget)}
|
||||||
title="설정값 삭제"
|
title="설정값 삭제"
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ export function createRuntimeProfile(input: { appId: string; name: string; envir
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteRuntimeProfile(profileId: string) {
|
||||||
|
return apiRequest<{ deleted: boolean; profileId: string; name: string }>(`/apps/runtime-profiles/${profileId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function createRuntimeVariable(
|
export function createRuntimeVariable(
|
||||||
profileId: string,
|
profileId: string,
|
||||||
input: { componentId: string; key: string; value: string; isSecret: boolean }
|
input: { componentId: string; key: string; value: string; isSecret: boolean }
|
||||||
|
|||||||
Reference in New Issue
Block a user