From 3c12ebba165d664307f7253040eb0adcb51ea313 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Mon, 27 Jul 2026 16:50:07 -0400 Subject: [PATCH] Remove all native alert()/confirm() calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Native confirm() dialogs block browser automation entirely (discovered via a frozen tab while browser-testing the app.messages/app.modal adoption), and native alert()/confirm() are visually inconsistent with the rest of the UI. Replaced every call site with app.messages.action/confirm/toast: - directory.ejs: rotateSecret/deleteResource confirms and all inline save/add/remove-group/edge error alerts now target #resourceModal's actionMessage (or, for deleteResource — called from the outer table row, not the modal — the page's own card). - impersonate_modal.ejs, onboarding.ejs: no local .actionMessage target exists on these pages, so their alerts became page-wide toasts. - executive.ejs: two alerts in sendNotification's validation now use the existing $compose target; saveTos's alert now reuses the function's own msgEl inline-message element instead of introducing a second mechanism. - users.ejs, profile.ejs, proxy's profile.ejs: toggleActive's alert (no row context available at the call site) became a toast; revokeInvite/revokeToken/rotateToken use the row/card element already in scope. - app.js: removed app.user.remove and app.oauthClient.remove, which contained native confirm() guards and had zero callers anywhere in the app — dead code, deleted rather than converted. Co-Authored-By: Claude Sonnet 5 --- nodejs/public/js/app.js | 18 ++------------- nodejs/views/directory.ejs | 36 +++++++++++++++++------------- nodejs/views/executive.ejs | 11 ++++++--- nodejs/views/impersonate_modal.ejs | 2 +- nodejs/views/onboarding.ejs | 14 ++++++------ nodejs/views/profile.ejs | 2 +- nodejs/views/users.ejs | 4 ++-- 7 files changed, 42 insertions(+), 45 deletions(-) diff --git a/nodejs/public/js/app.js b/nodejs/public/js/app.js index 97d7e3a..c1461ea 100755 --- a/nodejs/public/js/app.js +++ b/nodejs/public/js/app.js @@ -67,13 +67,6 @@ app.user = (function(app){ }); } - function remove(args, callack){ - if(!confirm('Delete '+ args.uid+ 'user?')) return false; - app.api.delete('user/'+ args.uid, function(error, data){ - callack(error, data); - }); - } - function changePassword(args, callack){ app.api.put('users/'+ arg.uid || '', args, function(error, data){ callack(error, data); @@ -110,7 +103,7 @@ app.user = (function(app){ return m ? m[1] : dn; } - return {list, remove, createInvite, setActive, dnToUid}; + return {list, createInvite, setActive, dnToUid}; })(app); @@ -306,13 +299,6 @@ app.oauthClient = (function(app){ }); } - function remove(args, callack){ - if(!confirm('Delete OAuth client "' + args.client_id + '"?')) return false; - app.api.delete('oauth/client/' + args.client_id, function(error, data){ - callack(error, data); - }); - } - function update(args, callack){ app.api.put('oauth/client/' + args.client_id, args, function(error, data){ callack(error, data); @@ -325,7 +311,7 @@ app.oauthClient = (function(app){ }); } - return { list, add, remove, update, rotateSecret }; + return { list, add, update, rotateSecret }; })(app); app.tos = (function(app){ diff --git a/nodejs/views/directory.ejs b/nodejs/views/directory.ejs index b8d5d4c..d7f5003 100644 --- a/nodejs/views/directory.ejs +++ b/nodejs/views/directory.ejs @@ -369,7 +369,7 @@ renderTable(); } catch (err) { console.error(err); - alert('Failed to load data'); + app.messages.toast('Failed to load data', 'danger'); } } @@ -718,21 +718,22 @@ } } catch (err) { console.error(err); - alert(err.message || 'Failed to save'); + app.messages.action(err.message || 'Failed to save', $('#resourceModal'), 'danger'); } } async function rotateSecret() { const id = $('#res-id').val(); if (!id) return; - if (!confirm('Are you sure you want to rotate the OAuth secret? Any existing integrations using the old secret will break.')) return; - + const ok = await app.messages.confirm('Are you sure you want to rotate the OAuth secret? Any existing integrations using the old secret will break.', $('#resourceModal'), 'warning'); + if (!ok) return; + try { const res = await app.api.post(`directory-admin/resources/${id}/rotate-secret`); app.modal.open({title: 'Secret Rotated', bodyHtml: 'Save this NEW client secret, it will not be shown again:

' + res.secret + ''}); } catch (err) { console.error(err); - alert(err.message || 'Failed to rotate secret'); + app.messages.action(err.message || 'Failed to rotate secret', $('#resourceModal'), 'danger'); } } @@ -741,7 +742,7 @@ const groupCn = $('#new-group-cn').val().trim(); const accessLevel = $('#new-group-level').val(); - if (!groupCn) return alert('Group CN is required'); + if (!groupCn) return app.messages.action('Group CN is required', $('#resourceModal'), 'danger'); try { const res = await app.api.post('directory-admin/groups', { resourceId, @@ -753,10 +754,10 @@ $('#new-group-cn').val(''); } catch (err) { console.error(err); - alert('Failed to add group'); + app.messages.action('Failed to add group', $('#resourceModal'), 'danger'); } } - + async function removeGroup(id) { try { await app.api.delete('directory-admin/groups/' + id); @@ -764,7 +765,7 @@ refreshGroupsUI($('#res-id').val()); } catch (err) { console.error(err); - alert('Failed to remove group'); + app.messages.action('Failed to remove group', $('#resourceModal'), 'danger'); } } @@ -774,7 +775,7 @@ const targetId = $('#new-edge-target').val(); const relation = $('#new-edge-relation').val().trim() || 'hosts'; - if (!targetId) return alert('Select a target resource'); + if (!targetId) return app.messages.action('Select a target resource', $('#resourceModal'), 'danger'); const data = { relation }; if (dir === 'parent') { @@ -792,10 +793,10 @@ $('#new-edge-target').val(''); } catch (err) { console.error(err); - alert('Failed to add edge'); + app.messages.action('Failed to add edge', $('#resourceModal'), 'danger'); } } - + async function removeEdge(id) { try { await app.api.delete('directory-admin/edges/' + id); @@ -803,18 +804,23 @@ refreshEdgesUI($('#res-id').val()); } catch (err) { console.error(err); - alert('Failed to remove edge'); + app.messages.action('Failed to remove edge', $('#resourceModal'), 'danger'); } } async function deleteResource(id) { - if (!confirm('Are you sure you want to delete this resource? All relationships will be destroyed.')) return; + // Called from the outer table's row button, not from inside + // #resourceModal — target the page's own card so the confirm/error + // renders somewhere actually visible. + const $target = $('#resources-list'); + const ok = await app.messages.confirm('Are you sure you want to delete this resource? All relationships will be destroyed.', $target, 'danger'); + if (!ok) return; try { await app.api.delete('directory-admin/resources/' + id); await loadResources(); } catch (err) { console.error(err); - alert('Failed to delete'); + app.messages.action('Failed to delete', $target, 'danger'); } } diff --git a/nodejs/views/executive.ejs b/nodejs/views/executive.ejs index efbe15b..30e7ef8 100644 --- a/nodejs/views/executive.ejs +++ b/nodejs/views/executive.ejs @@ -117,8 +117,8 @@ const msgEl = document.getElementById('notif-result'); const $compose = $('#notif-subject').closest('.card-body'); - if (!subject || !message) { alert('Subject and message are required.'); return; } - if (!filterCheck) { alert('Choose who to send this to.'); return; } + if (!subject || !message) { app.messages.action('Subject and message are required.', $compose, 'danger'); return; } + if (!filterCheck) { app.messages.action('Choose who to send this to.', $compose, 'danger'); return; } const filterType = filterCheck.value; let filter_value = ''; @@ -179,7 +179,12 @@ const resetAcceptance = document.getElementById('tos-reset-acceptance').checked; const msgEl = document.getElementById('tos-result'); - if (!content) { alert('Terms of Service text cannot be empty.'); return; } + if (!content) { + msgEl.className = 'alert alert-danger mt-2'; + msgEl.textContent = 'Terms of Service text cannot be empty.'; + msgEl.style.display = ''; + return; + } app.tos.update({content, resetAcceptance}, function(error, data) { if (error) { diff --git a/nodejs/views/impersonate_modal.ejs b/nodejs/views/impersonate_modal.ejs index 4d19803..783a541 100644 --- a/nodejs/views/impersonate_modal.ejs +++ b/nodejs/views/impersonate_modal.ejs @@ -68,7 +68,7 @@ function startImpersonate(uid){ app.impersonate.create(uid, function(error, data){ if(error){ - alert('Could not start impersonation: ' + (data && data.message ? data.message : 'Unknown error')); + app.messages.toast('Could not start impersonation: ' + (data && data.message ? data.message : 'Unknown error'), 'danger'); return; } $('#impersonateModalTitle').text(data.uid); diff --git a/nodejs/views/onboarding.ejs b/nodejs/views/onboarding.ejs index edecd7b..7a5c3a4 100644 --- a/nodejs/views/onboarding.ejs +++ b/nodejs/views/onboarding.ejs @@ -38,7 +38,7 @@ async function acceptTos() { var checkbox = document.getElementById('tosCheckbox'); if (!checkbox.checked) { - alert('Please read and check the box to accept the Terms of Service.'); + app.messages.toast('Please read and check the box to accept the Terms of Service.', 'danger'); return; } try { @@ -50,14 +50,14 @@ document.getElementById('section-tos').style.display = 'none'; checkAllDone(); } catch(e) { - alert('Could not save TOS acceptance. Please try again.'); + app.messages.toast('Could not save TOS acceptance. Please try again.', 'danger'); } } async function saveDob() { var dob = document.getElementById('dobInput').value; if (!dob) { - alert('Please enter your date of birth.'); + app.messages.toast('Please enter your date of birth.', 'danger'); return; } try { @@ -73,7 +73,7 @@ document.getElementById('section-dob').style.display = 'none'; checkAllDone(); } catch(e) { - alert('Could not save date of birth. Please try again.'); + app.messages.toast('Could not save date of birth. Please try again.', 'danger'); } } @@ -81,11 +81,11 @@ var pw = document.getElementById('pwInput').value; var pw2 = document.getElementById('pwInput2').value; if (!pw || pw.length < 5) { - alert('Password must be at least 5 characters.'); + app.messages.toast('Password must be at least 5 characters.', 'danger'); return; } if (pw !== pw2) { - alert('Passwords do not match.'); + app.messages.toast('Passwords do not match.', 'danger'); return; } try { @@ -101,7 +101,7 @@ document.getElementById('section-password').style.display = 'none'; checkAllDone(); } catch(e) { - alert('Could not change password. Please try again.'); + app.messages.toast('Could not change password. Please try again.', 'danger'); } } diff --git a/nodejs/views/profile.ejs b/nodejs/views/profile.ejs index db4de7c..25a6c8a 100644 --- a/nodejs/views/profile.ejs +++ b/nodejs/views/profile.ejs @@ -176,7 +176,7 @@ async function toggleActive(uid, active){ app.user.setActive(uid, active, async function(error, data){ - if(error) return alert('Failed to update user status'); + if(error) return app.messages.toast('Failed to update user status', 'danger'); currentUser = await determinUser(); renderProfile(currentUser); }); diff --git a/nodejs/views/users.ejs b/nodejs/views/users.ejs index 0b5cc27..9be4adb 100755 --- a/nodejs/views/users.ejs +++ b/nodejs/views/users.ejs @@ -19,7 +19,7 @@ function toggleActive(uid, active){ app.user.setActive(uid, active, function(error, data){ - if(error) return alert('Failed to update user status'); + if(error) return app.messages.toast('Failed to update user status', 'danger'); renderUsers(); }); } @@ -137,7 +137,7 @@ await app.api.delete(`user/invite/${tokenId}`); loadInvites(); } catch(e) { - alert('Failed to revoke invite.'); + app.messages.action('Failed to revoke invite.', $thisRow, 'danger'); } }