init
This commit is contained in:
commit
67ff874dd3
11 changed files with 622 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.parcel-cache/
|
1
.npmrc
Normal file
1
.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package-lock=false
|
3
Config.ts
Normal file
3
Config.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export const Config = {
|
||||||
|
APIEndpoint: "http://127.0.0.1:5858"
|
||||||
|
};
|
2
README.MD
Normal file
2
README.MD
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# CollabVM Authentication Admin Panel
|
||||||
|
## WIP
|
BIN
assets/favicon.ico
Normal file
BIN
assets/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
30
package.json
Normal file
30
package.json
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "collabvm-auth-admin-panel",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Admin panel for the CollabVM Authentication Server",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "parcel build --no-source-maps --dist-dir dist --public-url '.' src/html/index.html",
|
||||||
|
"serve": "parcel src/html/index.html",
|
||||||
|
"clean": "run-script-os",
|
||||||
|
"clean:darwin:linux": "rm -rf dist .parcel-cache",
|
||||||
|
"clean:win32": "rd /s /q dist .parcel-cache"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.computernewb.com/collabvm/collabvm-auth-admin-panel"
|
||||||
|
},
|
||||||
|
"author": "Computernewb Development Team",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bootstrap": "^5.2.10",
|
||||||
|
"parcel": "^2.12.0",
|
||||||
|
"run-script-os": "^1.1.6",
|
||||||
|
"typescript": "^5.4.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hcaptcha/types": "^1.0.3",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"bootstrap": "^5.3.2"
|
||||||
|
}
|
||||||
|
}
|
3
src/css/style.css
Normal file
3
src/css/style.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#adminView, #adminLoginForm, #navbarNav {
|
||||||
|
display: none;
|
||||||
|
}
|
109
src/html/index.html
Normal file
109
src/html/index.html
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html data-bs-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>CollabVM Authentication Admin Panel</title>
|
||||||
|
<link rel="stylesheet" href="../css/style.css"/>
|
||||||
|
<link rel="stylesheet" href="../../node_modules/bootstrap/dist/css/bootstrap.min.css"/>
|
||||||
|
<script src="https://kit.fontawesome.com/7add23c1ae.js" crossorigin="anonymous"></script>
|
||||||
|
<link rel="icon" href="../../assets/favicon.ico"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-lg">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">CollabVM Admin Panel</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav" style="display:none!important">
|
||||||
|
<ul class="navbar-nav me-auto">
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
<div class="navbar-text dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="accountDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fa-solid fa-user"></i> <span id="accountDropdownUsername"></span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="accountDropdownMenuLink">
|
||||||
|
<a class="dropdown-item" href="#" id="accountLogoutButton">Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container-sm" id="loginView">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-6">
|
||||||
|
<p id="loadingText">Loading...</p>
|
||||||
|
<form id="adminLoginForm">
|
||||||
|
<label for="loginUsername" class="form-label">Username</label>
|
||||||
|
<input type="text" class="form-control" id="loginUsername" name="username" required/>
|
||||||
|
<label for="loginPassword" class="form-label">Password</label>
|
||||||
|
<input type="password" class="form-control" id="loginPassword" name="password" required/>
|
||||||
|
<div id="loginCaptcha"></div>
|
||||||
|
<button type="submit" class="btn btn-primary">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container-lg" id="adminView">
|
||||||
|
<div id="usersView">
|
||||||
|
<h1>Users</h1>
|
||||||
|
<form id="searchUsersForm">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">Filter Username</span>
|
||||||
|
<input type="text" class="form-control" id="usernameFilter" name="username"/>
|
||||||
|
<span class="input-group-text">Sort By</span>
|
||||||
|
<select class="form-select" id="userSortBy" name="sortBy" required>
|
||||||
|
<option value="id">ID</option>
|
||||||
|
<option value="username">Username</option>
|
||||||
|
<option value="email">Email</option>
|
||||||
|
<option value="date_of_birth">Date of Birth</option>
|
||||||
|
<option value="cvm_rank">Rank</option>
|
||||||
|
<option value="banned">Banned</option>
|
||||||
|
<option value="created">Created At</option>
|
||||||
|
</select>
|
||||||
|
<div class="input-group-text">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="userSortDescending">
|
||||||
|
<label class="form-check-label">Descending?</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Search</button>
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Rank</th>
|
||||||
|
<th>Banned</th>
|
||||||
|
<th>Date of Birth</th>
|
||||||
|
<th>Created At</th>
|
||||||
|
<th>Registration IP</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="usersTableBody"></tbody>
|
||||||
|
</table>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">Page</span>
|
||||||
|
<input type="number" class="form-control" id="usersPage" name="page" value="1" min="1" max="1" required/>
|
||||||
|
<span class="input-group-text">of <span id="usersPageCount">1</span></span>
|
||||||
|
<button type="submit" class="btn btn-primary">Go</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-7"><!-- yeah this is lazy whatever --></div>
|
||||||
|
<div class="col-2">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="number" class="form-control" id="usersPerPage" name="perPage" value="10" min="1" max="100" required/>
|
||||||
|
<span class="input-group-text">Per Page</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
</body>
|
||||||
|
</html>
|
189
src/ts/AuthManager.ts
Normal file
189
src/ts/AuthManager.ts
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
export default class AuthManager {
|
||||||
|
apiEndpoint : string;
|
||||||
|
info : AuthServerInformation | null;
|
||||||
|
account : Account | null;
|
||||||
|
constructor(apiEndpoint : string) {
|
||||||
|
this.apiEndpoint = apiEndpoint;
|
||||||
|
this.info = null;
|
||||||
|
this.account = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAPIInformation() : Promise<AuthServerInformation> {
|
||||||
|
return new Promise(async res => {
|
||||||
|
var data = await fetch(this.apiEndpoint + "/api/v1/info");
|
||||||
|
this.info = await data.json();
|
||||||
|
res(this.info!);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
login(username : string, password : string, captchaToken : string | undefined) : Promise<AccountLoginResult> {
|
||||||
|
return new Promise(async (res,rej) => {
|
||||||
|
if (!this.info) throw new Error("Cannot login before fetching API information.");
|
||||||
|
if (!captchaToken && this.info.hcaptcha.required) throw new Error("This API requires a valid hCaptcha token.");
|
||||||
|
var data = await fetch(this.apiEndpoint + "/api/v1/login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
captchaToken: captchaToken
|
||||||
|
})
|
||||||
|
});
|
||||||
|
var json = await data.json() as AccountLoginResult;
|
||||||
|
if (!json) throw new Error("data.json() gave null or undefined result");
|
||||||
|
if (json.success && !json.verificationRequired) {
|
||||||
|
this.account = {
|
||||||
|
username: json.username!,
|
||||||
|
email: json.email!,
|
||||||
|
sessionToken: json.token!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res(json);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSession(token : string) {
|
||||||
|
return new Promise<SessionResult>(async (res, rej) => {
|
||||||
|
var data = await fetch(this.apiEndpoint + "/api/v1/session", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
token: token,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
var json = await data.json() as SessionResult;
|
||||||
|
if (json.success) {
|
||||||
|
this.account = {
|
||||||
|
sessionToken: token,
|
||||||
|
username: json.username!,
|
||||||
|
email: json.email!,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
res(json);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
return new Promise<LogoutResult>(async res => {
|
||||||
|
if (!this.account) throw new Error("Cannot log out without logging in first");
|
||||||
|
var data = await fetch(this.apiEndpoint + "/api/v1/logout", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
token: this.account.sessionToken
|
||||||
|
})
|
||||||
|
});
|
||||||
|
var json = await data.json() as LogoutResult;
|
||||||
|
this.account = null;
|
||||||
|
res(json);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
listUsers(resultsPerPage : number, page : number, filterUsername : string | undefined, orderBy : string | undefined, orderByDescending : boolean) {
|
||||||
|
return new Promise<ListUsersResult>(async res => {
|
||||||
|
if (!this.account) throw new Error("Cannot list users without logging in first");
|
||||||
|
var data = await fetch(this.apiEndpoint + "/api/v1/admin/users", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
token: this.account.sessionToken,
|
||||||
|
resultsPerPage: resultsPerPage,
|
||||||
|
page: page,
|
||||||
|
filterUsername: filterUsername,
|
||||||
|
orderBy: orderBy,
|
||||||
|
orderByDescending: orderByDescending
|
||||||
|
})
|
||||||
|
});
|
||||||
|
var json = await data.json() as ListUsersResult;
|
||||||
|
res(json);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRank(username : string, newRank : number) {
|
||||||
|
return new Promise<UpdateRankResult>(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", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
token: this.account.sessionToken,
|
||||||
|
username: username,
|
||||||
|
rank: newRank
|
||||||
|
})
|
||||||
|
});
|
||||||
|
var json = await data.json() as UpdateRankResult;
|
||||||
|
res(json);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthServerInformation {
|
||||||
|
registrationOpen : boolean;
|
||||||
|
hcaptcha : {
|
||||||
|
required : boolean;
|
||||||
|
siteKey : string | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccountLoginResult {
|
||||||
|
success : boolean;
|
||||||
|
token : string | undefined;
|
||||||
|
error : string | undefined;
|
||||||
|
verificationRequired : boolean | undefined;
|
||||||
|
email : string | undefined;
|
||||||
|
username : string | undefined;
|
||||||
|
rank : number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SessionResult {
|
||||||
|
success : boolean;
|
||||||
|
error : string | undefined;
|
||||||
|
banned : boolean;
|
||||||
|
username : string | undefined;
|
||||||
|
email : string | undefined;
|
||||||
|
rank : number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LogoutResult {
|
||||||
|
success : boolean;
|
||||||
|
error : string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Account {
|
||||||
|
username : string;
|
||||||
|
email : string;
|
||||||
|
sessionToken : string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
id : number;
|
||||||
|
username : string;
|
||||||
|
email : string;
|
||||||
|
rank : number;
|
||||||
|
banned : boolean;
|
||||||
|
dateOfBirth : string;
|
||||||
|
dateJoined : string;
|
||||||
|
registrationIp : string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListUsersResult {
|
||||||
|
success : boolean;
|
||||||
|
error : string | undefined;
|
||||||
|
totalPageCount : number | undefined;
|
||||||
|
users : User[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateRankResult {
|
||||||
|
success : boolean;
|
||||||
|
error : string | undefined;
|
||||||
|
}
|
170
src/ts/main.ts
Normal file
170
src/ts/main.ts
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
import * as bootstrap from 'bootstrap';
|
||||||
|
// trick parcel into including bootstrap js
|
||||||
|
bootstrap;
|
||||||
|
import { Config } from '../../Config.js';
|
||||||
|
import AuthManager from './AuthManager.js';
|
||||||
|
|
||||||
|
const elements = {
|
||||||
|
accountDropdownUsername: document.getElementById('accountDropdownUsername') as HTMLSpanElement,
|
||||||
|
navbarNav: document.getElementById('navbarNav') as HTMLDivElement,
|
||||||
|
loginView: document.getElementById('loginView') as HTMLDivElement,
|
||||||
|
adminView: document.getElementById('adminView') as HTMLDivElement,
|
||||||
|
|
||||||
|
loadingText: document.getElementById('loadingText') as HTMLParagraphElement,
|
||||||
|
adminLoginForm: document.getElementById('adminLoginForm') as HTMLFormElement,
|
||||||
|
loginUsername: document.getElementById('loginUsername') as HTMLInputElement,
|
||||||
|
loginPassword: document.getElementById('loginPassword') as HTMLInputElement,
|
||||||
|
loginCaptcha: document.getElementById('loginCaptcha') as HTMLDivElement,
|
||||||
|
|
||||||
|
accountLogoutButton: document.getElementById('accountLogoutButton') as HTMLAnchorElement,
|
||||||
|
|
||||||
|
searchUsersForm: document.getElementById('searchUsersForm') as HTMLFormElement,
|
||||||
|
usernameFilter: document.getElementById('usernameFilter') as HTMLInputElement,
|
||||||
|
userSortBy: document.getElementById('userSortBy') as HTMLSelectElement,
|
||||||
|
userSortDescending: document.getElementById('userSortDescending') as HTMLInputElement,
|
||||||
|
usersPage: document.getElementById('usersPage') as HTMLInputElement,
|
||||||
|
usersPerPage: document.getElementById('usersPerPage') as HTMLInputElement,
|
||||||
|
usersPageCount: document.getElementById('usersPageCount') as HTMLSpanElement,
|
||||||
|
usersTableBody: document.getElementById('usersTableBody') as HTMLTableSectionElement,
|
||||||
|
};
|
||||||
|
|
||||||
|
const RankString = {
|
||||||
|
1: "User",
|
||||||
|
2: "Administrator",
|
||||||
|
3: "Moderator",
|
||||||
|
};
|
||||||
|
|
||||||
|
var auth : AuthManager = new AuthManager(Config.APIEndpoint);
|
||||||
|
var hcaptchaid : string;
|
||||||
|
|
||||||
|
elements.adminLoginForm.addEventListener('submit', async e => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (auth.info!.hcaptcha.required) {
|
||||||
|
var hcaptchaToken = undefined;
|
||||||
|
if (auth!.info!.hcaptcha.required) {
|
||||||
|
var response = hcaptcha.getResponse(hcaptchaid);
|
||||||
|
if (response === "") {
|
||||||
|
alert("Missing captcha!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
hcaptchaToken = response;
|
||||||
|
}
|
||||||
|
var result = await auth.login(elements.loginUsername.value, elements.loginPassword.value, hcaptchaToken);
|
||||||
|
elements.loginUsername.value = "";
|
||||||
|
elements.loginPassword.value = "";
|
||||||
|
hcaptcha.reset(hcaptchaid);
|
||||||
|
if (result.success) {
|
||||||
|
if (result.rank !== 2) {
|
||||||
|
alert("You are not an administrator!");
|
||||||
|
await auth.logout();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
localStorage.setItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`, result.token!);
|
||||||
|
loadAdminView();
|
||||||
|
} else {
|
||||||
|
alert("Login failed: " + result.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
elements.accountLogoutButton.addEventListener('click', async () => {
|
||||||
|
await auth.logout();
|
||||||
|
localStorage.removeItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`);
|
||||||
|
loadLoginForm();
|
||||||
|
});
|
||||||
|
|
||||||
|
elements.searchUsersForm.addEventListener('submit', async e => {
|
||||||
|
e.preventDefault();
|
||||||
|
var usernameFilter = elements.usernameFilter.value;
|
||||||
|
var sortBy = elements.userSortBy.value;
|
||||||
|
var sortDescending = elements.userSortDescending.checked;
|
||||||
|
var page = parseInt(elements.usersPage.value);
|
||||||
|
var perPage = parseInt(elements.usersPerPage.value);
|
||||||
|
var data = await auth.listUsers(perPage, page, usernameFilter, sortBy, sortDescending);
|
||||||
|
if (!data.success) {
|
||||||
|
alert("Failed to list users: " + data.error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
elements.usersTableBody.innerHTML = "";
|
||||||
|
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 rankSelect = document.createElement('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 => {
|
||||||
|
var newRank = parseInt(rankSelect.value);
|
||||||
|
// @ts-ignore
|
||||||
|
if (!window.confirm(`Are you sure you want to set ${_user.username}'s rank to ${RankString[newRank]}?`)) {
|
||||||
|
e.preventDefault();
|
||||||
|
rankSelect.value = _user.rank.toString(10);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var result = await auth.updateRank(_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;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await auth.getAPIInformation();
|
||||||
|
if (auth!.info!.hcaptcha.required) {
|
||||||
|
hcaptchaid = hcaptcha.render(elements.loginCaptcha, {
|
||||||
|
sitekey: auth!.info!.hcaptcha.siteKey!,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var token = localStorage.getItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`);
|
||||||
|
if (token) {
|
||||||
|
var session = await auth.loadSession(token);
|
||||||
|
if (session.success) {
|
||||||
|
if (session.rank! !== 2) {
|
||||||
|
await auth.logout();
|
||||||
|
localStorage.removeItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`);
|
||||||
|
loadLoginForm();
|
||||||
|
}
|
||||||
|
loadAdminView();
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem(`collabvm_session_${new URL(Config.APIEndpoint).host}`);
|
||||||
|
loadLoginForm();
|
||||||
|
}
|
||||||
|
} else loadLoginForm();
|
||||||
|
})();
|
||||||
|
|
||||||
|
function loadAdminView() {
|
||||||
|
elements.loginView.style.display = "none";
|
||||||
|
elements.adminView.style.display = "block";
|
||||||
|
elements.navbarNav.style.display = "";
|
||||||
|
elements.accountDropdownUsername.innerText = auth!.account!.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadLoginForm() {
|
||||||
|
elements.loginView.style.display = "block";
|
||||||
|
elements.adminView.style.display = "none";
|
||||||
|
elements.navbarNav.setAttribute("style", "display:none!important");
|
||||||
|
elements.loadingText.style.display = "none";
|
||||||
|
elements.adminLoginForm.style.display = "block";
|
||||||
|
}
|
112
tsconfig.json
Normal file
112
tsconfig.json
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "ES2022", /* Specify what module code is generated. */
|
||||||
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
|
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@hcaptcha",
|
||||||
|
], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||||
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||||
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||||
|
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue