From de28a34ac20582660f4ffd0943ff9a3e73f16085 Mon Sep 17 00:00:00 2001 From: ochan1 Date: Thu, 4 Nov 2021 14:55:08 -0700 Subject: [PATCH 1/5] Add chkbox + buttons for bulk verify + restyle div --- static/js/admin.js | 137 +++++++++++++++++++++++++++++++++++++-- templates/user_admin.tmp | 24 +++++++ 2 files changed, 155 insertions(+), 6 deletions(-) diff --git a/static/js/admin.js b/static/js/admin.js index 4eda105..12c2a0e 100644 --- a/static/js/admin.js +++ b/static/js/admin.js @@ -1,3 +1,18 @@ +function generalButton (label, action, extraClass) { + var buttonDiv = document.createElement('div'), + button = document.createElement('a'); + button.setAttribute('localizable', true); + button.classList.add('pure-button'); + button.classList.add(label.toLowerCase().replace(/\s/g,'')); + if (extraClass) { + button.classList.add(extraClass); + } + button.innerHTML = localizer.localize(label); + button.onclick = action; + buttonDiv.appendChild(button); + return buttonDiv; +} + function userButton (user, label, action, extraClass) { var button = document.createElement('a'); button.setAttribute('localizable', true); @@ -35,6 +50,96 @@ function verifyButton (user) { ); }; +function verifyUserAutomated (username, onSuccess, onError) { + SnapCloud.withCredentialsRequest( + 'GET', + '/users/' + encodeURIComponent(username) + + '/verify_user/0', // token is irrelevant for admins + onSuccess, + onError, + '', + true + ); +}; + +function htmlListUsers(userList, label) { + if (userList.length == 0) { + return ""; + } + var reponse_text = "

The following users were " + label + ":

\n"; + reponse_text += "\n"; + return reponse_text; +} + +function verifyMultipleUsers(checkboxCollection) { + var already_verified_users = []; + var successful_users = []; + var failed_users = []; + const promises = []; + checkboxCollection.forEach( + function(user_checkbox) { + const promise = new Promise((resolve) => { + if (user_checkbox.checked) { + var username = user_checkbox.value; + verifyUserAutomated(username, + function(response) { + // Success + try { + response = JSON.parse(response); + } catch {} + if (response.status_code == "Already Verified") { + already_verified_users.push(username); + } else if (response.status_code == "New Verified") { + successful_users.push(username); + } else { + console.log("Invalid response status code", response); + } + resolve(); + }, + function(response) { + // Failure + failed_users.push(username + " - " + response); + resolve(); + } + ); + } else { + // Nothing happens to users that are not checked + resolve(); + } + }); + promises.push(promise); + } + ); + Promise.all(promises).then(() => { + var reponse_text = "" + + reponse_text += htmlListUsers(successful_users, "successfully verified"); + reponse_text += htmlListUsers(already_verified_users, "already verified"); + reponse_text += htmlListUsers(failed_users, "not successfully verified (failed verification)"); + + alert( + reponse_text, + () => { location.reload(); } + ); + }); +}; + +function verifyUser (username, onSuccess) { + SnapCloud.withCredentialsRequest( + 'GET', + '/users/' + encodeURIComponent(username) + + '/verify_user/0', // token is irrelevant for admins + onSuccess, + genericError, + 'Could not verify user' + ); +}; + function banButton (user) { return userButton( user, @@ -338,6 +443,9 @@ function setRole (user, role) { function basicUserDiv (user) { var userWrapperDiv = document.createElement('div'), + userInfoCheckboxSpan = document.createElement('span'), + userInfoDiv = document.createElement('div'), + checkboxDiv = document.createElement('div'), detailsDiv = document.createElement('div'), usernameAnchor = userAnchor(user.username), emailSpan = document.createElement('span'), @@ -352,16 +460,29 @@ function basicUserDiv (user) { user.project_count; joinedSpan.innerHTML = 'Joined in ' + formatDate(user.created); + + checkboxDiv.innerHTML = ''; [ usernameAnchor, emailSpan, idSpan, projectCountSpan, joinedSpan ].forEach( - function (e) { detailsDiv.appendChild(e); } + function (e) { userInfoDiv.appendChild(e); } ); userWrapperDiv.classList.add('user'); userWrapperDiv.classList.add('pure-u-1-2'); + userInfoCheckboxSpan.classList.add('userInfoCheckbox'); + userInfoDiv.classList.add('userInfo'); + checkboxDiv.classList.add('checkbox'); detailsDiv.classList.add('details'); - - userWrapperDiv.appendChild(detailsDiv); + + [ userInfoDiv, checkboxDiv ].forEach( + function (e) { userInfoCheckboxSpan.appendChild(e); } + ); + + userInfoDiv.style.float = "left"; + checkboxDiv.style.float = "right"; + + detailsDiv.appendChild(userInfoCheckboxSpan); + userWrapperDiv.appendChild(detailsDiv) return userWrapperDiv; }; @@ -369,11 +490,13 @@ function basicUserDiv (user) { function userDiv (user) { var userWrapperDiv = basicUserDiv(user); detailsDiv = userWrapperDiv.querySelector('.details'), + userInfoDiv = userWrapperDiv.querySelector('.userInfo'), + detailsAndCheckbox = document.createElement('div'), roleSpan = document.createElement('span'), roleSelect = document.createElement('select'), buttonsDiv = document.createElement('div'); - roleSpan.innerHTML = 'Role:'; + roleSpan.innerHTML = 'Role:' + ' '; ['standard', 'reviewer', 'moderator', 'admin', 'banned'].forEach( function (role) { var roleOption = document.createElement('option'); @@ -393,10 +516,12 @@ function userDiv (user) { buttonsDiv.classList.add('buttons'); - [ roleSpan, buttonsDiv ].forEach( - function (e) { detailsDiv.appendChild(e); } + [ roleSpan ].forEach( + function (e) { userInfoDiv.appendChild(e); } ); + detailsDiv.appendChild(buttonsDiv); + if (user.role == 'admin') { detailsDiv.classList.add('admin'); detailsDiv.title += localizer.localize('Administrator') + '\n'; diff --git a/templates/user_admin.tmp b/templates/user_admin.tmp index 89208bb..b1f5242 100644 --- a/templates/user_admin.tmp +++ b/templates/user_admin.tmp @@ -8,6 +8,7 @@

User Administration

+ @param pageSize=150 @param withSearch=true @param keepIfEmpty=true @@ -63,5 +64,28 @@ } ), paginator); + bar.insertBefore( + generalButton( + "Bulk Edit", + function () { + document.getElementsByName("user_checkbox").forEach( + function(user_checkbox) { + user_checkbox.hidden = !user_checkbox.hidden; + } + ); + var admin_container = document.querySelector('.admin-container'); + admin_container.hidden = !admin_container.hidden; + } + ), + paginator); + + var admin_select_ctrls = document.querySelector('.admin-container'); + admin_select_ctrls.appendChild( + generalButton( + "Verify Selected", + () => { verifyMultipleUsers(document.getElementsByName("user_checkbox")); } + ) + ); + }); From 2c33e5552abb1a94ad966ab81dcb662c1f23f6e8 Mon Sep 17 00:00:00 2001 From: ochan1 Date: Fri, 5 Nov 2021 17:32:01 -0700 Subject: [PATCH 2/5] From Status Code to State Message --- static/js/admin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/js/admin.js b/static/js/admin.js index 12c2a0e..abb680b 100644 --- a/static/js/admin.js +++ b/static/js/admin.js @@ -92,9 +92,9 @@ function verifyMultipleUsers(checkboxCollection) { try { response = JSON.parse(response); } catch {} - if (response.status_code == "Already Verified") { + if (response.state_message == "Already Verified") { already_verified_users.push(username); - } else if (response.status_code == "New Verified") { + } else if (response.state_message == "New Verified") { successful_users.push(username); } else { console.log("Invalid response status code", response); From 863c46e710630d57a3e4a8559dd45db55b309b40 Mon Sep 17 00:00:00 2001 From: ochan1 Date: Mon, 15 Nov 2021 21:35:06 -0800 Subject: [PATCH 3/5] Code + CSS to summon custom contextmenu --- static/js/admin.js | 14 +++++++++++++- static/style/admin.css | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/static/js/admin.js b/static/js/admin.js index abb680b..25dd821 100644 --- a/static/js/admin.js +++ b/static/js/admin.js @@ -461,7 +461,19 @@ function basicUserDiv (user) { joinedSpan.innerHTML = 'Joined in ' + formatDate(user.created); - checkboxDiv.innerHTML = ''; + checkboxDiv.innerHTML = ''; + checkboxDiv.oncontextmenu = function (e) { + e.preventDefault(); + if (document.getElementById("contextMenu").style.display == "block") { + hideMenu(); + } else { + var menu = document.getElementById("contextMenu") + menu.style.display = 'block'; + menu.style.left = e.pageX + "px"; + menu.style.top = e.pageY + "px"; + } + }; [ usernameAnchor, emailSpan, idSpan, projectCountSpan, joinedSpan ].forEach( function (e) { userInfoDiv.appendChild(e); } diff --git a/static/style/admin.css b/static/style/admin.css index 3bb0f1f..c54ae33 100644 --- a/static/style/admin.css +++ b/static/style/admin.css @@ -106,3 +106,36 @@ .filter select:focus { outline: none; } + +/* Checkbox Context Menu */ + +.checkbox-context-menu { + position: absolute; + text-align: center; + background: lightgray; + border: 1px solid black; +} + +.checkbox-context-menu ul { + padding: 0px; + margin: 0px; + min-width: 85px; + list-style: none; +} + +.checkbox-context-menu ul li { + margin: 0 0; + padding-bottom: 7px; + padding-top: 7px; + border: 1px solid black; +} + +.checkbox-context-menu ul li a { + text-decoration: none; + color: black; + display: block; +} + +.checkbox-context-menu ul li:hover { + background: darkgray; +} From f471da247b3561b2f103ed86adc9003d69ee6468 Mon Sep 17 00:00:00 2001 From: ochan1 Date: Mon, 15 Nov 2021 21:35:39 -0800 Subject: [PATCH 4/5] Add context menu for checking all or none --- templates/grid.tmp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/templates/grid.tmp b/templates/grid.tmp index eb1f326..d9524d0 100644 --- a/templates/grid.tmp +++ b/templates/grid.tmp @@ -29,7 +29,20 @@ + @include=paginator From 7139a414dde737ee1c4a026baace0059a8b5e2f3 Mon Sep 17 00:00:00 2001 From: ochan1 Date: Tue, 16 Nov 2021 03:03:13 -0800 Subject: [PATCH 5/5] Mv chkbx contextmenu as standalone, not with grid --- templates/checkbox_contextmenu.tmp | 23 +++++++++++++++++++++++ templates/grid.tmp | 23 +---------------------- templates/user_admin.tmp | 1 + 3 files changed, 25 insertions(+), 22 deletions(-) create mode 100644 templates/checkbox_contextmenu.tmp diff --git a/templates/checkbox_contextmenu.tmp b/templates/checkbox_contextmenu.tmp new file mode 100644 index 0000000..6eac0a2 --- /dev/null +++ b/templates/checkbox_contextmenu.tmp @@ -0,0 +1,23 @@ + + + diff --git a/templates/grid.tmp b/templates/grid.tmp index d9524d0..4b2593a 100644 --- a/templates/grid.tmp +++ b/templates/grid.tmp @@ -29,20 +29,8 @@ - - @include=paginator diff --git a/templates/user_admin.tmp b/templates/user_admin.tmp index b1f5242..405e506 100644 --- a/templates/user_admin.tmp +++ b/templates/user_admin.tmp @@ -89,3 +89,4 @@ }); +@include=checkbox_contextmenu