Prevent reboot dialog enter reentry

This commit is contained in:
seo
2026-06-17 19:54:42 +09:00
parent 3b71eb268d
commit 5bf4236784
3 changed files with 46 additions and 30 deletions
+2 -1
View File
@@ -25,6 +25,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
- WakeLock 버튼으로 대시보드 화면 꺼짐 방지 - WakeLock 버튼으로 대시보드 화면 꺼짐 방지
- Reboot 버튼으로 2단계 확인 후 시스템 재부팅 요청 - Reboot 버튼으로 2단계 확인 후 시스템 재부팅 요청
- 기본 브라우저 alert/confirm/prompt 대신 대시보드 디자인에 맞춘 custom dialog 사용 - 기본 브라우저 alert/confirm/prompt 대신 대시보드 디자인에 맞춘 custom dialog 사용
- custom dialog는 Enter/Escape 키 입력을 자체 처리하며, Reboot 확인 흐름은 중복 실행을 차단
- Translate 버튼으로 `en`, `ko` UI 언어 토글. 버튼 표시는 상태 suffix 없이 고정 - Translate 버튼으로 `en`, `ko` UI 언어 토글. 버튼 표시는 상태 suffix 없이 고정
- Theme 버튼으로 `dark`, `light` UI 테마 토글. 버튼 표시는 상태 suffix 없이 고정 - Theme 버튼으로 `dark`, `light` UI 테마 토글. 버튼 표시는 상태 suffix 없이 고정
@@ -150,7 +151,7 @@ control-wifi-observe.service
6. `control-wifi-observe.timer`는 사용자 접속과 무관하게 10초마다 5G 관측 세션을 갱신합니다. 6. `control-wifi-observe.timer`는 사용자 접속과 무관하게 10초마다 5G 관측 세션을 갱신합니다.
7. Push 등록은 권한 요청과 구독 저장이 끝나면 즉시 상태를 갱신합니다. 7. Push 등록은 권한 요청과 구독 저장이 끝나면 즉시 상태를 갱신합니다.
8. WakeLock 버튼은 활성 상태를 초록색 버튼으로 표시합니다. 8. WakeLock 버튼은 활성 상태를 초록색 버튼으로 표시합니다.
9. Reboot 버튼은 `재부팅` 단어 입력과 관리자 암호 재입력을 모두 통과한 뒤 서버 API로 재부팅을 요청합니다. 9. Reboot 버튼은 `재부팅` 단어 입력과 관리자 암호 재입력을 모두 통과한 뒤 서버 API로 재부팅을 요청합니다. 확인 단어 입력창에서 Enter를 눌러도 같은 Reboot 흐름이 다시 열리지 않도록 키 이벤트 기본 동작과 중복 실행을 차단합니다.
10. Translate 버튼은 최초 접속 시 디바이스 언어를 기본값으로 사용하고, 변경값은 `localStorage``controlLang`에 저장합니다. 라벨은 `Translate` 또는 `번역`으로만 표시합니다. 10. Translate 버튼은 최초 접속 시 디바이스 언어를 기본값으로 사용하고, 변경값은 `localStorage``controlLang`에 저장합니다. 라벨은 `Translate` 또는 `번역`으로만 표시합니다.
11. Theme 버튼은 기본값 `dark`로 시작하고, 변경값은 `localStorage``controlTheme`에 저장합니다. 라벨은 `Theme` 또는 `테마`로만 표시합니다. 11. Theme 버튼은 기본값 `dark`로 시작하고, 변경값은 `localStorage``controlTheme`에 저장합니다. 라벨은 `Theme` 또는 `테마`로만 표시합니다.
12. 시스템 상태는 `vcgencmd get_throttled`를 읽어 현재 저전압 여부와 부팅 후 이력, 최근 감지 시각, 현재/최근 지속시간을 표시합니다. 12. 시스템 상태는 `vcgencmd get_throttled`를 읽어 현재 저전압 여부와 부팅 후 이력, 최근 감지 시각, 현재/최근 지속시간을 표시합니다.
+43 -28
View File
@@ -66,6 +66,7 @@
wsReconnectTimer: null, wsReconnectTimer: null,
wsReconnectDelay: 1000, wsReconnectDelay: 1000,
pushDevicesLastRefresh: 0, pushDevicesLastRefresh: 0,
rebootRequesting: false,
lang: 'en', lang: 'en',
theme: 'dark', theme: 'dark',
}; };
@@ -513,8 +514,17 @@
if (event.target === layer) onCancel(); if (event.target === layer) onCancel();
}; };
const onKeydown = event => { const onKeydown = event => {
if (event.key === 'Escape') onCancel(); if (event.key === 'Escape') {
if (event.key === 'Enter' && (document.activeElement === input || !needsInput)) onOk(); 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'); els.customDialogTitle.textContent = options.title || t('dialogConfirm');
@@ -1576,34 +1586,37 @@
} }
async function requestReboot() { async function requestReboot() {
const phrase = await window.customPrompt(t('rebootPrompt'), { if (state.rebootRequesting) return;
title: t('rebootTitle'), state.rebootRequesting = true;
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;
try { 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; els.rebootBtn.disabled = true;
notice(t('rebootSending'), 'info'); notice(t('rebootSending'), 'info');
await api('reboot', { await api('reboot', {
@@ -1621,6 +1634,8 @@
title: t('rebootFailed'), title: t('rebootFailed'),
danger: true, danger: true,
}); });
} finally {
state.rebootRequesting = false;
} }
} }
+1 -1
View File
@@ -319,7 +319,7 @@ html[data-theme="light"] .btn.secondary{background:#d9e2ef;color:#172033}html[da
</div> </div>
</div> </div>
</div> </div>
<script src="/assets/app.js?v=20260607_prefs1"></script> <script src="/assets/app.js?v=20260617_reboot_enter1"></script>
<script src="/assets/wakelock.js?v=20260607_prefs1"></script> <script src="/assets/wakelock.js?v=20260607_prefs1"></script>
<script src="/push_subscribe.js?v=20260607_prefs1"></script> <script src="/push_subscribe.js?v=20260607_prefs1"></script>
<?php endif; ?> <?php endif; ?>