implement bots

This commit is contained in:
Elijah R 2024-04-07 14:42:36 -04:00
parent 67ff874dd3
commit 8cbbc1f1ff
3 changed files with 272 additions and 25 deletions

View file

@ -17,7 +17,12 @@
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav" style="display:none!important"> <div class="collapse navbar-collapse" id="navbarNav" style="display:none!important">
<ul class="navbar-nav me-auto"> <ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="#" id="usersNavLink">Users</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" id="botsNavLink">Bots</a>
</li>
</ul> </ul>
<div class="navbar-text dropdown"> <div class="navbar-text dropdown">
<a class="nav-link dropdown-toggle" href="#" id="accountDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="#" id="accountDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
@ -79,6 +84,7 @@
<th>Date of Birth</th> <th>Date of Birth</th>
<th>Created At</th> <th>Created At</th>
<th>Registration IP</th> <th>Registration IP</th>
<th>Developer?</th>
</tr> </tr>
</thead> </thead>
<tbody id="usersTableBody"></tbody> <tbody id="usersTableBody"></tbody>
@ -102,6 +108,36 @@
</div> </div>
</form> </form>
</div> </div>
<div class="container-lg" id="botsView">
<h1>Bots</h1>
<form id="searchBotsForm">
<div class="input-group">
<span class="input-group-text">Owner</span>
<input type="text" class="form-control" id="searchBotsOwner" name="owner"/>
<button type="submit" class="btn btn-primary">Search</button>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Rank</th>
<th>Owner</th>
<th>Created At</th>
</tr>
</thead>
<tbody id="botsTableBody"></tbody>
</table>
</form>
<h2>Create Bot</h2>
<form id="createBotForm">
<div class="input-group">
<span class="input-group-text">Username</span>
<input type="text" class="form-control" id="createBotUsername" name="username" required maxlength="20"/>
<button type="submit" class="btn btn-primary">Create</button>
</div>
</form>
</div>
</div> </div>
<script src="https://js.hcaptcha.com/1/api.js"></script> <script src="https://js.hcaptcha.com/1/api.js"></script>
<script type="module" src="../ts/main.ts" type="application/javascript"></script> <script type="module" src="../ts/main.ts" type="application/javascript"></script>

View file

@ -37,7 +37,8 @@ export default class AuthManager {
this.account = { this.account = {
username: json.username!, username: json.username!,
email: json.email!, email: json.email!,
sessionToken: json.token! sessionToken: json.token!,
rank: json.rank!
} }
} }
res(json); res(json);
@ -61,6 +62,7 @@ export default class AuthManager {
sessionToken: token, sessionToken: token,
username: json.username!, username: json.username!,
email: json.email!, email: json.email!,
rank: json.rank!
}; };
} }
res(json); res(json);
@ -107,10 +109,67 @@ export default class AuthManager {
}); });
} }
updateRank(username : string, newRank : number) { updateUser(username : string, newRank : number | undefined = undefined, developer : boolean | undefined = undefined) {
return new Promise<UpdateRankResult>(async res => { return new Promise<UpdateUserResult>(async res => {
if (!this.account) throw new Error("Cannot update rank without logging in first"); 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<ListBotsResult>(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<CreateBotResult>(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<AdminUpdateBotResult>(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", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
@ -121,7 +180,7 @@ export default class AuthManager {
rank: newRank rank: newRank
}) })
}); });
var json = await data.json() as UpdateRankResult; var json = await data.json() as AdminUpdateBotResult;
res(json); res(json);
}); });
} }
@ -163,6 +222,7 @@ export interface Account {
username : string; username : string;
email : string; email : string;
sessionToken : string; sessionToken : string;
rank : number;
} }
export interface User { export interface User {
@ -174,6 +234,7 @@ export interface User {
dateOfBirth : string; dateOfBirth : string;
dateJoined : string; dateJoined : string;
registrationIp : string; registrationIp : string;
developer : boolean;
} }
export interface ListUsersResult { export interface ListUsersResult {
@ -183,7 +244,33 @@ export interface ListUsersResult {
users : User[] | undefined; 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; success : boolean;
error : string | undefined; error : string | undefined;
} }

View file

@ -26,6 +26,19 @@ const elements = {
usersPerPage: document.getElementById('usersPerPage') as HTMLInputElement, usersPerPage: document.getElementById('usersPerPage') as HTMLInputElement,
usersPageCount: document.getElementById('usersPageCount') as HTMLSpanElement, usersPageCount: document.getElementById('usersPageCount') as HTMLSpanElement,
usersTableBody: document.getElementById('usersTableBody') as HTMLTableSectionElement, 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 = { const RankString = {
@ -89,18 +102,19 @@ elements.searchUsersForm.addEventListener('submit', async e => {
elements.usersPageCount.innerText = data.totalPageCount!.toString(10); elements.usersPageCount.innerText = data.totalPageCount!.toString(10);
elements.usersPage.max = data.totalPageCount!.toString(10); elements.usersPage.max = data.totalPageCount!.toString(10);
for (var user of data.users!) { 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 _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'); var rankSelect = document.createElement('select');
rankSelect.classList.add('form-select');
rankSelect.innerHTML = `<option value="1">User</option><option value="2">Administrator</option><option value="3">Moderator</option>`; rankSelect.innerHTML = `<option value="1">User</option><option value="2">Administrator</option><option value="3">Moderator</option>`;
rankSelect.value = _user.rank.toString(10); rankSelect.value = _user.rank.toString(10);
rankSelect.addEventListener('change', async e => { rankSelect.addEventListener('change', async e => {
@ -111,25 +125,106 @@ elements.searchUsersForm.addEventListener('submit', async e => {
rankSelect.value = _user.rank.toString(10); rankSelect.value = _user.rank.toString(10);
return false; return false;
} }
var result = await auth.updateRank(_user.username, newRank); var result = await auth.updateUser(_user.username, newRank);
if (!result.success) { if (!result.success) {
alert("Failed to set rank: " + result.error); alert("Failed to set rank: " + result.error);
} }
}); });
cell.appendChild(rankSelect); 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; 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 = `<option value="1">User</option><option value="2">Administrator</option><option value="3">Moderator</option>`;
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 () => { (async () => {
await auth.getAPIInformation(); await auth.getAPIInformation();
if (auth!.info!.hcaptcha.required) { if (auth!.info!.hcaptcha.required) {
@ -146,6 +241,7 @@ elements.searchUsersForm.addEventListener('submit', async e => {
localStorage.removeItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`); localStorage.removeItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`);
loadLoginForm(); loadLoginForm();
} }
loadUsersView();
loadAdminView(); loadAdminView();
} else { } else {
localStorage.removeItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`); localStorage.removeItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`);
@ -154,6 +250,26 @@ elements.searchUsersForm.addEventListener('submit', async e => {
} else loadLoginForm(); } 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() { function loadAdminView() {
elements.loginView.style.display = "none"; elements.loginView.style.display = "none";
elements.adminView.style.display = "block"; elements.adminView.style.display = "block";
@ -167,4 +283,12 @@ function loadLoginForm() {
elements.navbarNav.setAttribute("style", "display:none!important"); elements.navbarNav.setAttribute("style", "display:none!important");
elements.loadingText.style.display = "none"; elements.loadingText.style.display = "none";
elements.adminLoginForm.style.display = "block"; 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";
} }