improve admin release and deployment safeguards
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-09 09:46:36 +09:00
parent fd590540bc
commit f67017623f
9 changed files with 394 additions and 45 deletions
+37 -5
View File
@@ -60,7 +60,9 @@ export function LauncherAppsPage() {
const [loading, setLoading] = useState(true);
const [workingId, setWorkingId] = useState('');
const [syncing, setSyncing] = useState(false);
const [unlinking, setUnlinking] = useState(false);
const [editingLink, setEditingLink] = useState<LauncherAppLink | null>(null);
const [unlinkTarget, setUnlinkTarget] = useState<LauncherAppLink | null>(null);
const [displayForm, setDisplayForm] = useState<DisplayForm | null>(null);
const loadingVisible = useMinimumLoading(loading);
@@ -107,17 +109,23 @@ export function LauncherAppsPage() {
.finally(() => setWorkingId(''));
};
const unlink = (link: LauncherAppLink) => {
setWorkingId(link.id);
deleteLauncherAppLink(link.id)
const unlink = () => {
if (!unlinkTarget || unlinking) return;
setUnlinking(true);
setWorkingId(unlinkTarget.id);
deleteLauncherAppLink(unlinkTarget.id)
.then(() => {
toast({ type: 'success', message: '런처 앱 연결을 해제했습니다.' });
setUnlinkTarget(null);
refresh();
})
.catch((error: Error) => {
toast({ type: 'error', title: '연결 해제 실패', message: error.message });
})
.finally(() => setWorkingId(''));
.finally(() => {
setWorkingId('');
setUnlinking(false);
});
};
const syncAdminApps = () => {
@@ -240,7 +248,7 @@ export function LauncherAppsPage() {
<Button variant="outline" className="size-8 px-0" aria-label={`${link.app.name} 표시 설정`} onClick={() => openSettings(link)} disabled={workingId === `${link.id}-settings`}>
<MoreHorizontal className="size-4" />
</Button>
<Button variant="outline" className="size-8 border-rose-500/40 px-0 text-rose-300 hover:bg-rose-500/10" aria-label={`${link.app.name} 연결 해제`} onClick={() => unlink(link)} disabled={workingId === link.id}>
<Button variant="outline" className="size-8 border-rose-500/40 px-0 text-rose-300 hover:bg-rose-500/10" aria-label={`${link.app.name} 연결 해제`} onClick={() => setUnlinkTarget(link)} disabled={workingId === link.id}>
<Unlink className="size-4" />
</Button>
</div>
@@ -349,6 +357,30 @@ export function LauncherAppsPage() {
) : null}
</Modal>
<Modal
open={Boolean(unlinkTarget)}
title="앱 연결 해제"
onClose={() => {
if (!unlinking) setUnlinkTarget(null);
}}
footer={
<>
<Button variant="outline" disabled={unlinking} onClick={() => setUnlinkTarget(null)}>취소</Button>
<Button className="bg-rose-600 text-white hover:bg-rose-500" disabled={unlinking} onClick={unlink}>
{unlinking ? '해제 중' : '해제'}
</Button>
</>
}
>
<div className="space-y-3 text-sm">
<p>
<span className="font-semibold text-foreground">{unlinkTarget?.displayName || unlinkTarget?.app.name || '-'}</span>
{' '}앱의 런처 연결을 해제합니다.
</p>
<p className="text-muted-foreground">연결을 해제하면 런처 홈에 표시되지 않습니다.</p>
</div>
</Modal>
<LoadingModal open={loadingVisible} />
</>
);