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 버튼으로 대시보드 화면 꺼짐 방지
- 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`를 읽어 현재 저전압 여부와 부팅 후 이력, 최근 감지 시각, 현재/최근 지속시간을 표시합니다.
+43 -28
View File
@@ -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;
}
}
+1 -1
View File
@@ -319,7 +319,7 @@ html[data-theme="light"] .btn.secondary{background:#d9e2ef;color:#172033}html[da
</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="/push_subscribe.js?v=20260607_prefs1"></script>
<?php endif; ?>