implement bots
This commit is contained in:
parent
67ff874dd3
commit
8cbbc1f1ff
3 changed files with 272 additions and 25 deletions
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
160
src/ts/main.ts
160
src/ts/main.ts
|
@ -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";
|
||||||
|
@ -168,3 +284,11 @@ function loadLoginForm() {
|
||||||
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";
|
||||||
|
}
|
Loading…
Reference in a new issue