diff --git a/src/html/index.html b/src/html/index.html
index a1689cb..751756c 100644
--- a/src/html/index.html
+++ b/src/html/index.html
@@ -17,7 +17,12 @@
Date of Birth |
Created At |
Registration IP |
+ Developer? |
@@ -102,6 +108,36 @@
+
+
Bots
+
+
Create Bot
+
+
diff --git a/src/ts/AuthManager.ts b/src/ts/AuthManager.ts
index 55dc7a8..b9a4ca6 100644
--- a/src/ts/AuthManager.ts
+++ b/src/ts/AuthManager.ts
@@ -37,7 +37,8 @@ export default class AuthManager {
this.account = {
username: json.username!,
email: json.email!,
- sessionToken: json.token!
+ sessionToken: json.token!,
+ rank: json.rank!
}
}
res(json);
@@ -61,6 +62,7 @@ export default class AuthManager {
sessionToken: token,
username: json.username!,
email: json.email!,
+ rank: json.rank!
};
}
res(json);
@@ -107,10 +109,67 @@ export default class AuthManager {
});
}
- updateRank(username : string, newRank : number) {
- return new Promise(async res => {
+ updateUser(username : string, newRank : number | undefined = undefined, developer : boolean | undefined = undefined) {
+ return new Promise(async res => {
if (!this.account) throw new Error("Cannot update rank without logging in first");
- var data = await fetch(this.apiEndpoint + "/api/v1/admin/rank", {
+ var data = await fetch(this.apiEndpoint + "/api/v1/admin/updateuser", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ token: this.account.sessionToken,
+ username: username,
+ rank: newRank,
+ developer: developer,
+ })
+ });
+ var json = await data.json() as UpdateUserResult;
+ res(json);
+ });
+ }
+
+ listBots(resultsPerPage : number, page : number, owner : string | undefined) {
+ return new Promise(async res => {
+ var data = await fetch(this.apiEndpoint + "/api/v1/bots/list", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ token: this.account!.sessionToken,
+ resultsPerPage: resultsPerPage,
+ page: page,
+ owner: owner
+ })
+ });
+ var json = await data.json() as ListBotsResult;
+ res(json);
+ });
+ }
+
+ createBot(username : string) {
+ return new Promise(async res => {
+ if (!this.account) throw new Error("Cannot create bot without logging in first");
+ var data = await fetch(this.apiEndpoint + "/api/v1/bots/create", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ token: this.account.sessionToken,
+ username: username
+ })
+ });
+ var json = await data.json() as CreateBotResult;
+ res(json);
+ });
+ }
+
+ adminUpdateBot(username : string, newRank : number | undefined) {
+ return new Promise(async res => {
+ if (!this.account) throw new Error("Cannot update bot without logging in first");
+ var data = await fetch(this.apiEndpoint + "/api/v1/admin/updatebot", {
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -121,7 +180,7 @@ export default class AuthManager {
rank: newRank
})
});
- var json = await data.json() as UpdateRankResult;
+ var json = await data.json() as AdminUpdateBotResult;
res(json);
});
}
@@ -163,6 +222,7 @@ export interface Account {
username : string;
email : string;
sessionToken : string;
+ rank : number;
}
export interface User {
@@ -174,6 +234,7 @@ export interface User {
dateOfBirth : string;
dateJoined : string;
registrationIp : string;
+ developer : boolean;
}
export interface ListUsersResult {
@@ -183,7 +244,33 @@ export interface ListUsersResult {
users : User[] | undefined;
}
-export interface UpdateRankResult {
+export interface UpdateUserResult {
+ success : boolean;
+ error : string | undefined;
+}
+
+export interface Bot {
+ id : number;
+ username : string;
+ rank : number;
+ owner : string;
+ created : string;
+}
+
+export interface ListBotsResult {
+ success : boolean;
+ error : string | undefined;
+ totalPageCount : number | undefined;
+ bots : Bot[] | undefined;
+}
+
+export interface CreateBotResult {
+ success : boolean;
+ error : string | undefined;
+ token : string | undefined;
+}
+
+export interface AdminUpdateBotResult {
success : boolean;
error : string | undefined;
}
\ No newline at end of file
diff --git a/src/ts/main.ts b/src/ts/main.ts
index ab8ae47..6d59925 100644
--- a/src/ts/main.ts
+++ b/src/ts/main.ts
@@ -26,6 +26,19 @@ const elements = {
usersPerPage: document.getElementById('usersPerPage') as HTMLInputElement,
usersPageCount: document.getElementById('usersPageCount') as HTMLSpanElement,
usersTableBody: document.getElementById('usersTableBody') as HTMLTableSectionElement,
+
+ usersNavLink: document.getElementById('usersNavLink') as HTMLAnchorElement,
+ botsNavLink: document.getElementById('botsNavLink') as HTMLAnchorElement,
+
+ usersView: document.getElementById('usersView') as HTMLDivElement,
+ botsView: document.getElementById('botsView') as HTMLDivElement,
+
+ searchBotsForm: document.getElementById('searchBotsForm') as HTMLFormElement,
+ searchBotsOwner: document.getElementById('searchBotsOwner') as HTMLInputElement,
+ botsTableBody: document.getElementById('botsTableBody') as HTMLTableSectionElement,
+
+ createBotForm: document.getElementById('createBotForm') as HTMLFormElement,
+ createBotUsername: document.getElementById('createBotUsername') as HTMLInputElement,
};
const RankString = {
@@ -89,18 +102,19 @@ elements.searchUsersForm.addEventListener('submit', async e => {
elements.usersPageCount.innerText = data.totalPageCount!.toString(10);
elements.usersPage.max = data.totalPageCount!.toString(10);
for (var user of data.users!) {
- var row = elements.usersTableBody.insertRow();
- var cell = row.insertCell();
- cell.innerText = user.id.toString(10);
- cell = row.insertCell();
- cell.innerText = user.username;
- cell = row.insertCell();
- cell.innerText = user.email;
- cell = row.insertCell();
- // Rank dropdown
(() => {
var _user = user;
+ var row = elements.usersTableBody.insertRow();
+ var cell = row.insertCell();
+ cell.innerText = user.id.toString(10);
+ cell = row.insertCell();
+ cell.innerText = user.username;
+ cell = row.insertCell();
+ cell.innerText = user.email;
+ cell = row.insertCell();
+ // Rank dropdown
var rankSelect = document.createElement('select');
+ rankSelect.classList.add('form-select');
rankSelect.innerHTML = ``;
rankSelect.value = _user.rank.toString(10);
rankSelect.addEventListener('change', async e => {
@@ -111,25 +125,106 @@ elements.searchUsersForm.addEventListener('submit', async e => {
rankSelect.value = _user.rank.toString(10);
return false;
}
- var result = await auth.updateRank(_user.username, newRank);
+ var result = await auth.updateUser(_user.username, newRank);
if (!result.success) {
alert("Failed to set rank: " + result.error);
}
});
cell.appendChild(rankSelect);
+ cell = row.insertCell();
+ cell.innerText = user.banned ? "Yes" : "No";
+ cell = row.insertCell();
+ cell.innerText = user.dateOfBirth;
+ cell = row.insertCell();
+ cell.innerText = user.dateJoined;
+ cell = row.insertCell();
+ cell.innerText = user.registrationIp;
+ cell = row.insertCell();
+ var developerCheckbox = document.createElement('input');
+ developerCheckbox.type = 'checkbox';
+ developerCheckbox.checked = _user.developer;
+ developerCheckbox.addEventListener('change', async e => {
+ var developer = developerCheckbox.checked;
+ // @ts-ignore
+ if (!window.confirm(`Are you sure you want to ${developer ? "grant" : "revoke"} developer status ${developer ? "to" : "from"} ${_user.username}?`)) {
+ e.preventDefault();
+ developerCheckbox.checked = !developer;
+ return false;
+ }
+ var result = await auth.updateUser(_user.username, undefined, developer);
+ if (!result.success) {
+ alert("Failed to update developer status: " + result.error);
+ }
+ });
+ cell.appendChild(developerCheckbox);
})();
- cell = row.insertCell();
- cell.innerText = user.banned ? "Yes" : "No";
- cell = row.insertCell();
- cell.innerText = user.dateOfBirth;
- cell = row.insertCell();
- cell.innerText = user.dateJoined;
- cell = row.insertCell();
- cell.innerText = user.registrationIp;
}
return false;
});
+elements.searchBotsForm.addEventListener('submit', async e => {
+ e.preventDefault();
+ var owner = elements.searchBotsOwner.value;
+ var data = await auth.listBots(10, 1, owner === "" ? undefined : owner);
+ if (!data.success) {
+ alert("Failed to list bots: " + data.error);
+ return false;
+ }
+ elements.botsTableBody.innerHTML = "";
+ for (const bot of data.bots!) {
+ (()=>{
+ var row = elements.botsTableBody.insertRow();
+ var cell = row.insertCell();
+ cell.innerText = bot.id.toString(10);
+ cell = row.insertCell();
+ cell.innerText = bot.username;
+ cell = row.insertCell();
+ if (auth!.account!.rank === 2) {
+ var rankSelect = document.createElement('select');
+ rankSelect.classList.add('form-select');
+ rankSelect.innerHTML = ``;
+ rankSelect.value = bot.rank.toString(10);
+ rankSelect.addEventListener('change', async e => {
+ var newRank = parseInt(rankSelect.value);
+ // @ts-ignore
+ if (!window.confirm(`Are you sure you want to set ${bot.username}'s rank to ${RankString[newRank]}?`)) {
+ e.preventDefault();
+ rankSelect.value = bot.rank.toString(10);
+ return false;
+ }
+ var result = await auth.adminUpdateBot(bot.username, newRank);
+ if (!result.success) {
+ alert("Failed to set rank: " + result.error);
+ }
+ });
+ cell.appendChild(rankSelect);
+ } else {
+ // @ts-ignore
+ cell.innerText = RankString[bot.rank];
+ }
+ cell = row.insertCell();
+ cell.innerText = bot.owner;
+ cell = row.insertCell();
+ cell.innerText = bot.created;
+ })();
+ }
+ return false;
+});
+
+elements.createBotForm.addEventListener('submit', async e => {
+ e.preventDefault();
+ var username = elements.createBotUsername.value;
+ var result = await auth.createBot(username);
+ if (!result.success) {
+ alert("Failed to create bot: " + result.error);
+ } else {
+ alert(`Bot created successfully! Your bot's token is:\n\n${result.token}\n\nPlease save this token as it will not be shown again.`);
+ }
+ elements.createBotUsername.value = "";
+ elements.searchBotsForm.requestSubmit();
+ return false;
+});
+
(async () => {
await auth.getAPIInformation();
if (auth!.info!.hcaptcha.required) {
@@ -146,6 +241,7 @@ elements.searchUsersForm.addEventListener('submit', async e => {
localStorage.removeItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`);
loadLoginForm();
}
+ loadUsersView();
loadAdminView();
} else {
localStorage.removeItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`);
@@ -154,6 +250,26 @@ elements.searchUsersForm.addEventListener('submit', async e => {
} else loadLoginForm();
})();
+// nav link event listeners
+elements.usersNavLink.addEventListener('click', e => {
+ e.preventDefault();
+ loadUsersView();
+ return false;
+});
+elements.botsNavLink.addEventListener('click', e => {
+ e.preventDefault();
+ loadBotsView();
+ return false;
+});
+
+function loadBotsView() {
+ elements.searchBotsForm.requestSubmit();
+ elements.botsNavLink.classList.add('active');
+ elements.usersNavLink.classList.remove('active');
+ elements.usersView.style.display = "none";
+ elements.botsView.style.display = "block";
+}
+
function loadAdminView() {
elements.loginView.style.display = "none";
elements.adminView.style.display = "block";
@@ -167,4 +283,12 @@ function loadLoginForm() {
elements.navbarNav.setAttribute("style", "display:none!important");
elements.loadingText.style.display = "none";
elements.adminLoginForm.style.display = "block";
+}
+
+function loadUsersView() {
+ elements.searchUsersForm.requestSubmit();
+ elements.usersNavLink.classList.add('active');
+ elements.botsNavLink.classList.remove('active');
+ elements.usersView.style.display = "block";
+ elements.botsView.style.display = "none";
}
\ No newline at end of file