From 5bf4236784eb595b813ca0b6963a90cf02f58d8f Mon Sep 17 00:00:00 2001 From: seo Date: Wed, 17 Jun 2026 19:54:42 +0900 Subject: [PATCH] Prevent reboot dialog enter reentry --- README.md | 3 +- public/assets/app.js | 71 +++++++++++++++++++++++++++----------------- public/index.php | 2 +- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 2ff8857..b04381d 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를 - WakeLock 버튼으로 대시보드 화면 꺼짐 방지 - Reboot 버튼으로 2단계 확인 후 시스템 재부팅 요청 - 기본 브라우저 alert/confirm/prompt 대신 대시보드 디자인에 맞춘 custom dialog 사용 +- custom dialog는 Enter/Escape 키 입력을 자체 처리하며, Reboot 확인 흐름은 중복 실행을 차단 - Translate 버튼으로 `en`, `ko` UI 언어 토글. 버튼 표시는 상태 suffix 없이 고정 - Theme 버튼으로 `dark`, `light` UI 테마 토글. 버튼 표시는 상태 suffix 없이 고정 @@ -150,7 +151,7 @@ control-wifi-observe.service 6. `control-wifi-observe.timer`는 사용자 접속과 무관하게 10초마다 5G 관측 세션을 갱신합니다. 7. Push 등록은 권한 요청과 구독 저장이 끝나면 즉시 상태를 갱신합니다. 8. WakeLock 버튼은 활성 상태를 초록색 버튼으로 표시합니다. -9. Reboot 버튼은 `재부팅` 단어 입력과 관리자 암호 재입력을 모두 통과한 뒤 서버 API로 재부팅을 요청합니다. +9. Reboot 버튼은 `재부팅` 단어 입력과 관리자 암호 재입력을 모두 통과한 뒤 서버 API로 재부팅을 요청합니다. 확인 단어 입력창에서 Enter를 눌러도 같은 Reboot 흐름이 다시 열리지 않도록 키 이벤트 기본 동작과 중복 실행을 차단합니다. 10. Translate 버튼은 최초 접속 시 디바이스 언어를 기본값으로 사용하고, 변경값은 `localStorage`의 `controlLang`에 저장합니다. 라벨은 `Translate` 또는 `번역`으로만 표시합니다. 11. Theme 버튼은 기본값 `dark`로 시작하고, 변경값은 `localStorage`의 `controlTheme`에 저장합니다. 라벨은 `Theme` 또는 `테마`로만 표시합니다. 12. 시스템 상태는 `vcgencmd get_throttled`를 읽어 현재 저전압 여부와 부팅 후 이력, 최근 감지 시각, 현재/최근 지속시간을 표시합니다. diff --git a/public/assets/app.js b/public/assets/app.js index 78396fb..9d21e75 100644 --- a/public/assets/app.js +++ b/public/assets/app.js @@ -66,6 +66,7 @@ wsReconnectTimer: null, wsReconnectDelay: 1000, pushDevicesLastRefresh: 0, + rebootRequesting: false, lang: 'en', theme: 'dark', }; @@ -513,8 +514,17 @@ if (event.target === layer) onCancel(); }; const onKeydown = event => { - if (event.key === 'Escape') onCancel(); - if (event.key === 'Enter' && (document.activeElement === input || !needsInput)) onOk(); + if (event.key === 'Escape') { + event.preventDefault(); + event.stopPropagation(); + onCancel(); + return; + } + if (event.key === 'Enter' && (document.activeElement === input || !needsInput)) { + event.preventDefault(); + event.stopPropagation(); + onOk(); + } }; els.customDialogTitle.textContent = options.title || t('dialogConfirm'); @@ -1576,34 +1586,37 @@ } async function requestReboot() { - const phrase = await window.customPrompt(t('rebootPrompt'), { - title: t('rebootTitle'), - okText: t('next'), - danger: true, - placeholder: t('rebootPhrase'), - autocomplete: 'off', - }); - - if (phrase === null) return; - if (String(phrase).trim() !== t('rebootPhrase')) { - await window.customAlert(t('rebootPhraseMismatch'), { - title: t('rebootCanceled'), - danger: true, - }); - return; - } - - const password = await window.customPrompt(t('passwordPrompt'), { - title: t('adminConfirm'), - okText: t('rebootButton'), - danger: true, - inputType: 'password', - autocomplete: 'current-password', - }); - - if (password === null) return; + if (state.rebootRequesting) return; + state.rebootRequesting = true; try { + const phrase = await window.customPrompt(t('rebootPrompt'), { + title: t('rebootTitle'), + okText: t('next'), + danger: true, + placeholder: t('rebootPhrase'), + autocomplete: 'off', + }); + + if (phrase === null) return; + if (String(phrase).trim() !== t('rebootPhrase')) { + await window.customAlert(t('rebootPhraseMismatch'), { + title: t('rebootCanceled'), + danger: true, + }); + return; + } + + const password = await window.customPrompt(t('passwordPrompt'), { + title: t('adminConfirm'), + okText: t('rebootButton'), + danger: true, + inputType: 'password', + autocomplete: 'current-password', + }); + + if (password === null) return; + els.rebootBtn.disabled = true; notice(t('rebootSending'), 'info'); await api('reboot', { @@ -1621,6 +1634,8 @@ title: t('rebootFailed'), danger: true, }); + } finally { + state.rebootRequesting = false; } } diff --git a/public/index.php b/public/index.php index 0ff6c0b..816fb17 100644 --- a/public/index.php +++ b/public/index.php @@ -319,7 +319,7 @@ html[data-theme="light"] .btn.secondary{background:#d9e2ef;color:#172033}html[da - +