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";
+ for (username of userList) {
+ // Failure usernames come with a reason of failure also: username - reason
+ reponse_text += "- " + username + "
\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