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

This commit is contained in:
2026-07-08 15:24:12 +09:00
3 changed files with 32 additions and 21 deletions
+27 -14
View File
@@ -122,7 +122,7 @@ export function LauncherComponentsPage() {
const [logs, setLogs] = useState('');
const [logLoading, setLogLoading] = useState(false);
const [envTarget, setEnvTarget] = useState<LauncherComponentDefinition | null>(null);
const [envVariables, setEnvVariables] = useState<Array<{ key: string; value: string; editable: boolean; source: string }>>([]);
const [envVariables, setEnvVariables] = useState<Array<{ key: string; value: string; isSecret: boolean; editable: boolean; source: string }>>([]);
const [envLoading, setEnvLoading] = useState(false);
const [envSaving, setEnvSaving] = useState(false);
const loadingVisible = useMinimumLoading(loading || logLoading);
@@ -187,12 +187,12 @@ export function LauncherComponentsPage() {
setEnvVariables(result.variables ?? []);
})
.catch((error: Error) => {
toast({ type: 'error', title: '환경변수 조회 실패', message: error.message });
toast({ type: 'error', title: '설정값 조회 실패', message: error.message });
})
.finally(() => setEnvLoading(false));
};
const updateEnvVariable = (index: number, patch: Partial<{ key: string; value: string }>) => {
const updateEnvVariable = (index: number, patch: Partial<{ key: string; value: string; isSecret: boolean }>) => {
setEnvVariables((prev) => prev.map((variable, variableIndex) => (
variableIndex === index ? { ...variable, ...patch } : variable
)));
@@ -211,16 +211,16 @@ export function LauncherComponentsPage() {
envTarget.workloadName,
envVariables
.filter((variable) => variable.editable)
.map((variable) => ({ key: variable.key.trim(), value: variable.value }))
.map((variable) => ({ key: variable.key.trim(), value: variable.value, isSecret: variable.isSecret }))
.filter((variable) => variable.key)
)
.then((result) => {
setEnvVariables(result.variables ?? []);
toast({ type: 'success', title: '환경변수 저장', message: `${envTarget.name} 환경변수를 반영했습니다.` });
toast({ type: 'success', title: '설정값 저장', message: `${envTarget.name} 설정값을 반영했습니다.` });
refresh();
})
.catch((error: Error) => {
toast({ type: 'error', title: '환경변수 저장 실패', message: error.message });
toast({ type: 'error', title: '설정값 저장 실패', message: error.message });
})
.finally(() => setEnvSaving(false));
};
@@ -329,7 +329,7 @@ export function LauncherComponentsPage() {
{component.type === 'backend' ? (
<Button variant="outline" onClick={() => openEnv(component)} disabled={!workload}>
<Settings className="size-4" />
환경변수
설정값
</Button>
) : null}
</div>
@@ -396,7 +396,7 @@ export function LauncherComponentsPage() {
<Modal
open={Boolean(envTarget)}
title={envTarget ? `${envTarget.name} 환경변수` : '환경변수'}
title={envTarget ? `${envTarget.name} 설정값` : '설정값'}
onClose={() => setEnvTarget(null)}
className="!max-w-4xl"
footer={
@@ -410,14 +410,15 @@ export function LauncherComponentsPage() {
>
<div className="space-y-4">
<p className="text-xs leading-5 text-muted-foreground">
저장하면 Kubernetes Deployment 환경변수가 변경되고 새 Pod로 롤아웃됩니다. Secret/ConfigMap에서 주입된 값은 여기서 직접 수정하지 않습니다.
Secret을 체크하면 Kubernetes Secret에 저장하고 secretKeyRef로 연결합니다. 체크하지 않은 값은 Deployment 환경변수로 저장합니다.
</p>
<div className="overflow-x-auto rounded-md border border-border">
<table className="w-full min-w-[720px] text-left text-sm">
<table className="w-full min-w-[820px] text-left text-sm">
<thead className="border-b border-border bg-muted/30 text-xs text-muted-foreground">
<tr>
<th className="px-4 py-3 font-medium">Key</th>
<th className="px-4 py-3 font-medium">Value</th>
<th className="w-24 px-4 py-3 font-medium">Secret</th>
<th className="w-24 px-4 py-3 font-medium">Source</th>
<th className="w-16 px-4 py-3 font-medium"></th>
</tr>
@@ -425,12 +426,12 @@ export function LauncherComponentsPage() {
<tbody className="divide-y divide-border">
{envLoading ? (
<tr>
<td colSpan={4} className="px-4 py-8 text-center text-muted-foreground">환경변수를 불러오는 중입니다.</td>
<td colSpan={5} className="px-4 py-8 text-center text-muted-foreground">설정값을 불러오는 중입니다.</td>
</tr>
) : null}
{!envLoading && envVariables.length === 0 ? (
<tr>
<td colSpan={4} className="px-4 py-8 text-center text-muted-foreground">등록된 환경변수가 없습니다.</td>
<td colSpan={5} className="px-4 py-8 text-center text-muted-foreground">등록된 설정값이 없습니다.</td>
</tr>
) : null}
{!envLoading && envVariables.map((variable, index) => (
@@ -451,6 +452,18 @@ export function LauncherComponentsPage() {
onChange={(event) => updateEnvVariable(index, { value: event.target.value })}
/>
</td>
<td className="px-4 py-3">
<label className="inline-flex items-center gap-2 text-xs text-muted-foreground">
<input
type="checkbox"
className="size-4 accent-primary"
checked={variable.isSecret}
disabled={!variable.editable}
onChange={(event) => updateEnvVariable(index, { isSecret: event.target.checked })}
/>
Secret
</label>
</td>
<td className="px-4 py-3">
<Badge tone={variable.editable ? 'blue' : 'neutral'}>{variable.source}</Badge>
</td>
@@ -473,11 +486,11 @@ export function LauncherComponentsPage() {
</div>
<Button
variant="outline"
onClick={() => setEnvVariables((prev) => [...prev, { key: '', value: '', editable: true, source: 'literal' }])}
onClick={() => setEnvVariables((prev) => [...prev, { key: '', value: '', isSecret: false, editable: true, source: 'literal' }])}
disabled={envLoading}
>
<Plus className="size-4" />
환경변수 추가
설정값 추가
</Button>
</div>
</Modal>
+2 -1
View File
@@ -425,6 +425,7 @@ export function deleteLauncherCommonSetting(id: string) {
export type RuntimeWorkloadEnvVariable = {
key: string;
value: string;
isSecret: boolean;
editable: boolean;
source: string;
};
@@ -447,7 +448,7 @@ export function saveRuntimeWorkloadEnv(
namespace: string,
kind: string,
name: string,
variables: Array<{ key: string; value: string }>
variables: Array<{ key: string; value: string; isSecret: boolean }>
) {
return apiRequest<{
namespace: string;
+3 -6
View File
@@ -466,9 +466,8 @@ export function RuntimeConfigPage() {
<table className="w-full table-fixed text-left text-sm">
<colgroup>
<col className="w-[22%]" />
<col className="w-[29%]" />
<col className="w-[19%]" />
<col className="w-[24%]" />
<col className="w-[30%]" />
<col />
<col className="w-20" />
</colgroup>
<thead className="sticky top-0 z-10 border-b border-border bg-card text-xs text-muted-foreground">
@@ -476,13 +475,12 @@ export function RuntimeConfigPage() {
<th className="px-5 py-3 font-medium">구성요소</th>
<th className="px-5 py-3 font-medium">Key</th>
<th className="px-5 py-3 font-medium">Value</th>
<th className="px-5 py-3 font-medium">등록일</th>
<th className="w-16 px-5 py-3 text-right font-medium">관리</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{variables.length === 0 ? (
<tr><td colSpan={5} className="px-5 py-14 text-center text-muted-foreground">등록된 설정값이 없습니다.</td></tr>
<tr><td colSpan={4} className="px-5 py-14 text-center text-muted-foreground">등록된 설정값이 없습니다.</td></tr>
) : null}
{variables.map((variable) => (
<tr key={variable.id} className="hover:bg-muted/20">
@@ -494,7 +492,6 @@ export function RuntimeConfigPage() {
</td>
<td className="break-all px-4 py-3 font-mono text-xs font-medium text-foreground">{variable.key}</td>
<td className="break-all px-4 py-3 font-mono text-xs text-muted-foreground">{variable.isSecret ? '********' : variable.value}</td>
<td className="px-4 py-3 text-xs text-muted-foreground">{formatDate(variable.createdAt)}</td>
<td className="px-3 py-3 text-right">
<Button variant="ghost" className="h-8 w-8 p-0 text-rose-300 hover:text-rose-200" title="삭제" aria-label={`${variable.key} 삭제`} onClick={() => removeVariable(variable.id)} disabled={Boolean(actionKey)}>
<Trash2 className="size-4" />