diff --git a/static/js/admin.js b/static/js/admin.js index 4eda105..25dd821 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.state_message == "Already Verified") { + already_verified_users.push(username); + } else if (response.state_message == "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,41 @@ function basicUserDiv (user) { user.project_count; joinedSpan.innerHTML = 'Joined in ' + formatDate(user.created); + + 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) { 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 +502,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 +528,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/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; +} 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 eb1f326..4b2593a 100644 --- a/templates/grid.tmp +++ b/templates/grid.tmp @@ -29,6 +29,7 @@ + @include=paginator diff --git a/templates/user_admin.tmp b/templates/user_admin.tmp index 89208bb..405e506 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,29 @@ } ), 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")); } + ) + ); + }); +@include=checkbox_contextmenu