diff --git a/server/web/index.html b/server/web/index.html index 4e7fbb8..357b01d 100644 --- a/server/web/index.html +++ b/server/web/index.html @@ -574,6 +574,14 @@ color: #fff; } .confirm-modal-actions .danger-btn:hover { box-shadow: 0 4px 12px rgba(192, 57, 43, 0.4); transform: translateY(-1px); } + /* Info variant for the generic alert dialog (single OK button). */ + .confirm-modal-content.info { border-color: rgba(230, 126, 34, 0.25); } + .confirm-modal-icon.info { background: rgba(230, 126, 34, 0.15); color: #e67e22; } + .confirm-modal-actions .primary-btn { + background: linear-gradient(135deg, #b9770e 0%, #e67e22 100%); + color: #fff; + } + .confirm-modal-actions .primary-btn:hover { box-shadow: 0 4px 12px rgba(230, 126, 34, 0.4); transform: translateY(-1px); } .device-grid { display: grid; @@ -1974,6 +1982,23 @@ + +
+ @@ -2703,7 +2728,7 @@ // mode=removed:历史记录已删除,刷新后卡片消失。 getDevices(); } else { - alert(msg.msg || 'Failed to delete device'); + appAlert(msg.msg || 'Failed to delete device', 'Unable to Delete Device'); } break; case 'list_users_result': @@ -3289,7 +3314,10 @@ const existing = wall.sessions.get(l32); if (existing && existing.fullId !== id) { // 两台设备的低 32 位相同:绝不能共用一个解码器。 - if (!restoring) alert('Device ID collision (low 32 bits). Cannot open both on the same wall.'); + if (!restoring) { + appAlert('Device ID collision (low 32 bits). Cannot open both on the same wall.', + 'Device ID Collision'); + } return null; } const uid = 'w' + (wall.uidSeq++); @@ -3311,11 +3339,11 @@ function addWallTile(deviceId) { if (!serverCaps.multiWatch) { - alert('Screen Wall is not supported by this server'); + appAlert('Screen Wall is not supported by this server.', 'Screen Wall Unavailable'); return; } if (wall.tiles.length >= WALL_MAX_TILES) { - alert('Wall is full (max ' + WALL_MAX_TILES + ' tiles)'); + appAlert('Wall is full (max ' + WALL_MAX_TILES + ' tiles).', 'Screen Wall Full'); return; } const dev = devices.find(d => String(d.id) === String(deviceId)); @@ -3355,9 +3383,11 @@ wallUpdateSidebarBadges(); } - function clearWall() { + async function clearWall() { if (!wall.tiles.length) return; - if (!confirm('Close all tiles on the wall?')) return; + const ok = await appConfirm('Close all tiles on the wall?', + { title: 'Clear Screen Wall', okText: 'Clear' }); + if (!ok) return; const tiles = wall.tiles.slice(); wall.tiles = []; for (const tile of tiles) { @@ -3569,8 +3599,8 @@ const tile = wall.tiles.find(t => t.uid === uid); if (!tile) return; const dev = devices.find(d => String(d.id) === tile.deviceId); - if (!dev) { alert('Device not found'); return; } - if (!dev.online) { alert('Device is offline'); return; } + if (!dev) { appAlert('Device not found.', 'Device Not Found'); return; } + if (!dev.online) { appAlert('Device is offline.', 'Device Offline'); return; } const s = wall.sessions.get(tile.low32); if (s && s.status === 'offline') { // 设备重新上线:离线时服务端已移除 watch,且 streams 条目被整体删除, @@ -3609,12 +3639,13 @@ function showWall() { if (!serverCaps.multiWatch) { - alert('Screen Wall requires a newer server with multi-view support.'); + appAlert('Screen Wall requires a newer server with multi-view support.', + 'Screen Wall Unavailable'); return; } const compat = checkWebCodecs(); if (!compat.supported) { - alert('Browser does not support H.264: ' + compat.reason); + appAlert('Browser does not support H.264: ' + compat.reason, 'H.264 Not Supported'); return; } if (!wallRestored) wallRestoreState(); @@ -4450,6 +4481,74 @@ updateAdminButtons(); } + // ===== In-app alert / confirmation dialog ===== + // Native alert()/confirm() use the browser locale for their buttons + // (e.g. "Abbrechen"/"OK" on a German system) and ignore the app theme. + // Both now go through the same themed modal as the logout prompt. + const APP_DIALOG_ICONS = { + danger: '', + info: '' + }; + let appDialogResolve = null; + + function openAppDialog(opts) { + // A pending dialog is superseded (e.g. an async error arrives while + // a confirm is open): resolve it as cancelled so no caller hangs. + if (appDialogResolve) { + const previous = appDialogResolve; + appDialogResolve = null; + previous(false); + } + const danger = opts.danger !== false; + const content = document.getElementById('app-dialog-content'); + const icon = document.getElementById('app-dialog-icon'); + const cancel = document.getElementById('app-dialog-cancel'); + const ok = document.getElementById('app-dialog-ok'); + content.className = 'confirm-modal-content' + (danger ? '' : ' info'); + icon.className = 'confirm-modal-icon' + (danger ? '' : ' info'); + icon.innerHTML = danger ? APP_DIALOG_ICONS.danger : APP_DIALOG_ICONS.info; + document.getElementById('app-dialog-title').textContent = + opts.title || (danger ? 'Confirm' : 'Notice'); + // textContent: server/device/user supplied strings must never be + // interpreted as markup. + document.getElementById('app-dialog-message').textContent = opts.message || ''; + cancel.style.display = opts.cancelText ? '' : 'none'; + cancel.textContent = opts.cancelText || 'Cancel'; + ok.className = danger ? 'danger-btn' : 'primary-btn'; + ok.textContent = opts.okText || 'OK'; + document.getElementById('app-dialog').classList.add('active'); + setTimeout(() => ok.focus(), 0); + return new Promise(resolve => { appDialogResolve = resolve; }); + } + + function closeAppDialog(result) { + document.getElementById('app-dialog').classList.remove('active'); + const resolve = appDialogResolve; + appDialogResolve = null; + if (resolve) resolve(!!result); + } + + // Fire-and-forget replacement for alert(). + function appAlert(message, title) { + return openAppDialog({ title: title || 'Notice', message, okText: 'OK', danger: false }); + } + + // Promise