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>
<div class="collapse navbar-collapse" id="navbarNav" style="display:none!important">
<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>
<div class="navbar-text dropdown">
<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>Created At</th>
<th>Registration IP</th>
<th>Developer?</th>
</tr>
</thead>
<tbody id="usersTableBody"></tbody>
@ -102,6 +108,36 @@
</div>
</form>
</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>
<script src="https://js.hcaptcha.com/1/api.js"></script>
<script type="module" src="../ts/main.ts" type="application/javascript"></script>

View file

@ -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<UpdateRankResult>(async res => {
updateUser(username : string, newRank : number | undefined = undefined, developer : boolean | undefined = undefined) {
return new Promise<UpdateUserResult>(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<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",
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;
}

View file

@ -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 = `<option value="1">User</option><option value="2">Administrator</option><option value="3">Moderator</option>`;
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 = `<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 () => {
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";
}