Worked ban frontend and api.
This commit is contained in:
parent
5c936462a6
commit
c848994c1d
18 changed files with 513 additions and 41 deletions
|
|
@ -108,4 +108,9 @@ img.admin-list-entry-item{
|
|||
#new-rank-select{
|
||||
margin: auto;
|
||||
height: 1.3em;
|
||||
}
|
||||
|
||||
.admin-user-list-icon{
|
||||
cursor: pointer;
|
||||
margin: 0 0.2em;
|
||||
}
|
||||
|
|
@ -48,4 +48,43 @@ p.navbar-item, input.navbar-item{
|
|||
|
||||
.navbar-item input{
|
||||
padding: 0.2em;
|
||||
}
|
||||
|
||||
.popup-backer{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.popup-div{
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.popup-close-icon{
|
||||
text-align: right;
|
||||
position:absolute;
|
||||
right: 0;
|
||||
margin: 0.5em;
|
||||
margin-bottom: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.popup-content-div{
|
||||
margin: 1em;
|
||||
padding-top: 0.2em;
|
||||
}
|
||||
|
||||
.popup-title{
|
||||
margin-top: 0;
|
||||
}
|
||||
13
www/css/popup/userBan.css
Normal file
13
www/css/popup/userBan.css
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
.ban-popup-content{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#ban-popup-button-span{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#ban-popup-button-span-spacer{
|
||||
flex: 1;
|
||||
}
|
||||
|
|
@ -281,4 +281,22 @@ select.panel-head-element{
|
|||
#new-rank-input:focus{
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
.popup-backer{
|
||||
background-color: var(--bg0-alpha1);
|
||||
backdrop-filter: var(--background-panel-effect-fast);
|
||||
}
|
||||
|
||||
.popup-div{
|
||||
background-color: var(--bg1);
|
||||
color: var(--accent1);
|
||||
box-shadow: 3px 3px 1px var(--bg1-alt0) inset;
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
#ban-popup-ban-button{
|
||||
background-color: var(--accent0-warning);
|
||||
color: var(--accent1);
|
||||
}
|
||||
|
|
@ -19,6 +19,59 @@ class canopyAdminUtils{
|
|||
|
||||
}
|
||||
|
||||
//Statics
|
||||
static banUserPopup = class{
|
||||
constructor(target){
|
||||
this.target = target;
|
||||
this.popup = new canopyUXUtils.popup("userBan", true, this.asyncConstruction.bind(this));
|
||||
}
|
||||
|
||||
asyncConstruction(){
|
||||
this.title = document.querySelector(".popup-title");
|
||||
//Setup title text real quick-like :P
|
||||
this.title.innerHTML = `Ban ${this.target}`;
|
||||
|
||||
this.permBan = document.querySelector("#ban-popup-perm");
|
||||
this.expiration = document.querySelector("#ban-popup-expiration");
|
||||
this.expirationPrefix = document.querySelector("#ban-popup-expiration-prefix");
|
||||
this.banButton = document.querySelector("#ban-popup-ban-button");
|
||||
this.cancelButton = document.querySelector("#ban-popup-cancel-button");
|
||||
|
||||
this.setupInput();
|
||||
}
|
||||
|
||||
setupInput(){
|
||||
this.permBan.addEventListener("change", this.permaBanLabel.bind(this));
|
||||
this.cancelButton.addEventListener("click", this.popup.closePopup.bind(this.popup));
|
||||
this.banButton.addEventListener("click",this.ban.bind(this));
|
||||
}
|
||||
|
||||
permaBanLabel(event){
|
||||
if(event.target.checked){
|
||||
this.expirationPrefix.innerHTML = "Account Deletion In: "
|
||||
this.expiration.value = 30;
|
||||
}else{
|
||||
this.expirationPrefix.innerHTML = "Ban Expires In: "
|
||||
this.expiration.value = 14;
|
||||
}
|
||||
}
|
||||
|
||||
async ban(event){
|
||||
//Close out the popup
|
||||
this.popup.closePopup();
|
||||
|
||||
//Submit the user ban based on input
|
||||
const bans = await adminUtil.banUser(this.target, this.permBan.checked, this.expiration.value);
|
||||
|
||||
//For some reason comparing this against undefined or null wasnt working in and of itself...
|
||||
if(typeof userBanList != "undefined" && bans != null){
|
||||
//Why add an extra get request when we already have the data? :P
|
||||
await userBanList.renderBanList(bans);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Methods
|
||||
async setUserRank(user, rank){
|
||||
var response = await fetch(`/api/admin/changeRank`,{
|
||||
method: "POST",
|
||||
|
|
@ -82,13 +135,30 @@ class canopyAdminUtils{
|
|||
}
|
||||
}
|
||||
|
||||
async banUser(user){
|
||||
async banUser(user, permanent, expirationDays){
|
||||
var response = await fetch(`/api/admin/ban`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
//Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
|
||||
body: JSON.stringify({user, permanent, expirationDays})
|
||||
});
|
||||
|
||||
if(response.status == 200){
|
||||
return await response.json();
|
||||
}else{
|
||||
utils.ux.displayResponseError(await response.json());
|
||||
}
|
||||
}
|
||||
|
||||
async unbanUser(user){
|
||||
var response = await fetch(`/api/admin/ban`,{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
//Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
|
||||
body: JSON.stringify({user})
|
||||
});
|
||||
|
||||
|
|
@ -103,6 +173,7 @@ class canopyAdminUtils{
|
|||
class adminUserList{
|
||||
constructor(){
|
||||
this.rankSelectors = document.querySelectorAll(".admin-user-list-rank-select");
|
||||
this.banIcons = document.querySelectorAll(".admin-user-list-ban-icon");
|
||||
|
||||
this.setupInput();
|
||||
}
|
||||
|
|
@ -111,6 +182,10 @@ class adminUserList{
|
|||
this.rankSelectors.forEach((rankSelector)=>{
|
||||
rankSelector.addEventListener("change", this.setRank.bind(this))
|
||||
});
|
||||
|
||||
this.banIcons.forEach((banIcon) => {
|
||||
banIcon.addEventListener("click", this.banPopup.bind(this));
|
||||
})
|
||||
}
|
||||
|
||||
async setRank(event){
|
||||
|
|
@ -120,6 +195,12 @@ class adminUserList{
|
|||
this.updateSelect(await adminUtil.setUserRank(user, rank), event.target);
|
||||
}
|
||||
|
||||
banPopup(event){
|
||||
const user = event.target.id.replace("admin-user-list-ban-icon-","");
|
||||
|
||||
new canopyAdminUtils.banUserPopup(user);
|
||||
}
|
||||
|
||||
updateSelect(update, select){
|
||||
if(update != null){
|
||||
select.value = update.rank;
|
||||
|
|
@ -186,7 +267,22 @@ class adminUserBanList{
|
|||
this.renderBanList(await adminUtil.getBans());
|
||||
}
|
||||
|
||||
clearBanList(){
|
||||
const oldRows = this.table.querySelectorAll('tr.admin-list-entry');
|
||||
oldRows.forEach((row) => {
|
||||
row.remove();
|
||||
});
|
||||
}
|
||||
|
||||
async unban(event){
|
||||
//Get username from target id
|
||||
const user = event.target.id.replace("admin-user-list-unban-icon-","");
|
||||
//Send unban command to server and display the resulting banlist
|
||||
this.renderBanList(await adminUtil.unbanUser(user));
|
||||
}
|
||||
|
||||
renderBanList(banList){
|
||||
this.clearBanList();
|
||||
banList.forEach((ban) => {
|
||||
//Create entry row
|
||||
const entryRow = document.createElement('tr');
|
||||
|
|
@ -197,26 +293,40 @@ class adminUserBanList{
|
|||
imgNode.classList.add("admin-list-entry","admin-list-entry-item");
|
||||
imgNode.src = ban.user.img;
|
||||
|
||||
console.log(new Date(ban.user.date).toDateString());
|
||||
|
||||
//Calculate expiration date and expiration days
|
||||
const expirationDate = new Date(ban.expirationDate);
|
||||
const expirationDays = ((expirationDate - new Date()) / (1000 * 60 * 60 * 24)).toFixed(1);
|
||||
|
||||
const expirationDays = Math.floor((expirationDate - new Date()) / (1000 * 60 * 60 * 24));
|
||||
//Create unban icon
|
||||
const unbanIcon = document.createElement('i');
|
||||
unbanIcon.classList.add("bi-emoji-smile-fill","admin-user-list-icon","admin-user-list-unban-icon");
|
||||
unbanIcon.id = `admin-user-list-unban-icon-${ban.user.user}`;
|
||||
unbanIcon.title = `Unban ${ban.user.user}`;
|
||||
unbanIcon.addEventListener("click", this.unban.bind(this));
|
||||
|
||||
//Create nuke account icon
|
||||
const nukeAccount = document.createElement('i');
|
||||
nukeAccount.classList.add("bi-radioactive","admin-user-list-icon","admin-user-list-unban-icon");
|
||||
nukeAccount.id = `admin-user-list-unban-icon-${ban.user.user}`;
|
||||
nukeAccount.title = `Nuke accounts`;
|
||||
nukeAccount.addEventListener("click",console.log);
|
||||
|
||||
//Append cells to row
|
||||
entryRow.appendChild(newCell(imgNode, true, true));
|
||||
entryRow.appendChild(newCell(imgNode, true));
|
||||
entryRow.appendChild(newCell(ban.user.id));
|
||||
entryRow.appendChild(newCell(ban.user.user));
|
||||
entryRow.appendChild(newCell(new Date(ban.user.date).toDateString()));
|
||||
entryRow.appendChild(newCell(new Date(ban.banDate).toDateString()));
|
||||
entryRow.appendChild(newCell(`${expirationDate.toDateString()} (${expirationDays} days left)`));
|
||||
entryRow.appendChild(newCell(ban.deleteAccountOnExpire ? "Delete" : "Un-Ban"));
|
||||
entryRow.appendChild(newCell(ban.permanent ? "Account Deletion" : "Un-Ban"));
|
||||
entryRow.appendChild(newCell([unbanIcon, nukeAccount]));
|
||||
|
||||
//Append row to table
|
||||
this.table.appendChild(entryRow);
|
||||
});
|
||||
|
||||
function newCell(content, addAsNode = false, firstCol = false){
|
||||
//We should really move this over to uxutils along with newrow & newtable functions
|
||||
function newCell(content, firstCol = false){
|
||||
//Create a new 'td' element
|
||||
const cell = document.createElement('td');
|
||||
cell.classList.add("admin-list-entry","admin-list-entry-item");
|
||||
|
|
@ -226,17 +336,33 @@ class adminUserBanList{
|
|||
cell.classList.add("admin-list-entry-not-first-col");
|
||||
}
|
||||
|
||||
//If we're adding as node
|
||||
if(addAsNode){
|
||||
//append it like it's a node
|
||||
cell.appendChild(content);
|
||||
//check for arrays
|
||||
if(content.forEach == null){
|
||||
//add single items
|
||||
addContent(content);
|
||||
}else{
|
||||
//otherwise use it as innerHTML
|
||||
cell.innerHTML = content;
|
||||
//Crawl through content array
|
||||
content.forEach((item)=>{
|
||||
//add each item
|
||||
addContent(item);
|
||||
});
|
||||
}
|
||||
|
||||
//return the resulting cell
|
||||
return cell;
|
||||
|
||||
|
||||
function addContent(ct){
|
||||
//If we're adding as node
|
||||
if(ct.cloneNode != null){
|
||||
//append it like it's a node
|
||||
cell.appendChild(ct);
|
||||
}else{
|
||||
//otherwise use it as innerHTML
|
||||
cell.innerHTML = ct;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,13 +25,68 @@ class canopyUXUtils{
|
|||
constructor(){
|
||||
}
|
||||
|
||||
async displayResponseError(body){
|
||||
displayResponseError(body){
|
||||
const errors = body.errors;
|
||||
errors.forEach((err)=>{
|
||||
window.alert(`ERROR: ${err.msg} \nTYPE: ${err.type} \nDATE: ${err.date}`);
|
||||
});
|
||||
}
|
||||
|
||||
static popup = class{
|
||||
constructor(content, ajaxPopup = false, cb){
|
||||
//Define non-popup node values
|
||||
this.content = content;
|
||||
this.ajaxPopup = ajaxPopup;
|
||||
this.cb = cb;
|
||||
//define popup nodes
|
||||
this.createPopup();
|
||||
|
||||
//fill popup nodes
|
||||
this.fillPopupContent();
|
||||
}
|
||||
|
||||
createPopup(){
|
||||
this.popupBacker = document.createElement('div');
|
||||
this.popupBacker.classList.add('popup-backer');
|
||||
|
||||
this.popupDiv = document.createElement('div');
|
||||
this.popupDiv.classList.add('popup-div');
|
||||
|
||||
this.closeIcon = document.createElement('i');
|
||||
this.closeIcon.classList.add('bi-x','popup-close-icon');
|
||||
this.closeIcon.addEventListener("click", this.closePopup.bind(this));
|
||||
|
||||
this.contentDiv = document.createElement('div');
|
||||
this.contentDiv.classList.add('popup-content-div');
|
||||
|
||||
this.popupDiv.appendChild(this.closeIcon);
|
||||
this.popupDiv.appendChild(this.contentDiv);
|
||||
}
|
||||
|
||||
async fillPopupContent(){
|
||||
if(this.ajaxPopup){
|
||||
this.contentDiv.innerHTML = await utils.ajax.getPopup(this.content);
|
||||
}else{
|
||||
this.contentDiv.innerHTML = this.content;
|
||||
}
|
||||
|
||||
//display popup nodes
|
||||
this.displayPopup();
|
||||
|
||||
//Callbacks are kinda out of vogue, but there really isn't a good way to handle asynchronously constructed objects/classes
|
||||
this.cb();
|
||||
}
|
||||
|
||||
displayPopup(){
|
||||
document.body.prepend(this.popupDiv);
|
||||
document.body.prepend(this.popupBacker);
|
||||
}
|
||||
|
||||
closePopup(){
|
||||
this.popupDiv.remove();
|
||||
this.popupBacker.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static clickDragger = class{
|
||||
constructor(handle, element, leftHandle = true){
|
||||
|
|
@ -309,6 +364,18 @@ class canopyAjaxUtils{
|
|||
utils.ux.displayResponseError(await response.json());
|
||||
}
|
||||
}
|
||||
|
||||
async getPopup(popup){
|
||||
var response = await fetch(`/popup/${popup}`,{
|
||||
method: "GET"
|
||||
});
|
||||
|
||||
if(response.status == 200){
|
||||
return (await response.text())
|
||||
}else{
|
||||
utils.ux.displayResponseError(await response.json());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const utils = new canopyUtils()
|
||||
Loading…
Add table
Add a link
Reference in a new issue