Implement admin login and features
This commit is contained in:
parent
901109a389
commit
9f39678fd0
|
|
@ -23,6 +23,7 @@
|
||||||
"@parcel/watcher": "~2.1.0"
|
"@parcel/watcher": "~2.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/bootstrap": "^5.2.10",
|
||||||
"parcel": "^2.11.0",
|
"parcel": "^2.11.0",
|
||||||
"parcel-reporter-static-files-copy": "^1.5.3",
|
"parcel-reporter-static-files-copy": "^1.5.3",
|
||||||
"run-script-os": "^1.1.6",
|
"run-script-os": "^1.1.6",
|
||||||
|
|
|
||||||
|
|
@ -87,11 +87,11 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.user-admin > td, .chat-username-admin {
|
tr.user-admin > td, .chat-username-admin, .username-admin {
|
||||||
color: #FF0000 !important;
|
color: #FF0000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.user-moderator > td, .chat-username-moderator {
|
tr.user-moderator > td, .chat-username-moderator, .username-moderator {
|
||||||
color: #00FF00 !important;
|
color: #00FF00 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,3 +279,7 @@ color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* End OSK */
|
/* End OSK */
|
||||||
|
|
||||||
|
#badPasswordAlert {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
@ -74,6 +74,31 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal fade" id="loginModal" tabindex="-1" aria-labelledby="loginModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-md">
|
||||||
|
<div class="modal-content bg-dark text-light">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Login</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="alert alert-danger alert-dismissible" id="badPasswordAlert" role="alert">
|
||||||
|
Incorrect password.
|
||||||
|
<button type="button" class="btn-close" aria-label="Close" id="incorrectPasswordDismissBtn"></button>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="hidden" name="username" id="adminInputVMID"/>
|
||||||
|
<span class="input-group-text bg-dark text-light">Password</span>
|
||||||
|
<input id="adminPassword" type="password" class="form-control bg-dark text-light" placeholder="Password" name="password"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" id="loginButton" class="btn btn-primary">Login</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="modal fade" id="hcaptchaModal" tabindex="-1" aria-hidden="true">
|
<div class="modal fade" id="hcaptchaModal" tabindex="-1" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content bg-dark text-light">
|
<div class="modal-content bg-dark text-light">
|
||||||
|
|
@ -140,6 +165,7 @@
|
||||||
<button class="btn btn-secondary" id="clearQueueBtn"><i class="fa-solid fa-eraser"></i> Clear Turn Queue</button>
|
<button class="btn btn-secondary" id="clearQueueBtn"><i class="fa-solid fa-eraser"></i> Clear Turn Queue</button>
|
||||||
<button class="btn btn-secondary" id="bypassTurnBtn"><i class="fa-solid fa-forward"></i> Bypass Turn</button>
|
<button class="btn btn-secondary" id="bypassTurnBtn"><i class="fa-solid fa-forward"></i> Bypass Turn</button>
|
||||||
<button class="btn btn-secondary" id="endTurnBtn"><i class="fa-solid fa-ban"></i> End Turn</button>
|
<button class="btn btn-secondary" id="endTurnBtn"><i class="fa-solid fa-ban"></i> End Turn</button>
|
||||||
|
<button class="btn btn-secondary" id="indefTurnBtn"><i class="fa-solid fa-infinity"></i> Indefinite Turn</button>
|
||||||
<button class="btn btn-secondary" id="qemuMonitorBtn" data-bs-toggle="modal" data-bs-target="#qemuMonitorModal"><i class="fa-solid fa-terminal"></i> QEMU Monitor</button>
|
<button class="btn btn-secondary" id="qemuMonitorBtn" data-bs-toggle="modal" data-bs-target="#qemuMonitorModal"><i class="fa-solid fa-terminal"></i> QEMU Monitor</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -188,7 +214,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
|
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
|
||||||
<script src="../../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script type="module" src="../ts/main.ts" type="application/javascript"></script>
|
<script type="module" src="../ts/main.ts" type="application/javascript"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
193
src/ts/main.ts
193
src/ts/main.ts
|
|
@ -1,13 +1,15 @@
|
||||||
import CollabVMClient from "./protocol/CollabVMClient.js";
|
import CollabVMClient from "./protocol/CollabVMClient.js";
|
||||||
import VM from "./protocol/VM.js";
|
import VM from "./protocol/VM.js";
|
||||||
import { Config } from "../../Config.js";
|
import { Config } from "../../Config.js";
|
||||||
import { Rank } from "./protocol/Permissions.js";
|
import { Permissions, Rank } from "./protocol/Permissions.js";
|
||||||
import { User } from "./protocol/User.js";
|
import { User } from "./protocol/User.js";
|
||||||
import TurnStatus from "./protocol/TurnStatus.js";
|
import TurnStatus from "./protocol/TurnStatus.js";
|
||||||
import Keyboard from "simple-keyboard";
|
import Keyboard from "simple-keyboard";
|
||||||
import { OSK_buttonToKeysym } from "./keyboard";
|
import { OSK_buttonToKeysym } from "./keyboard";
|
||||||
import "simple-keyboard/build/css/index.css";
|
import "simple-keyboard/build/css/index.css";
|
||||||
import VoteStatus from "./protocol/VoteStatus.js";
|
import VoteStatus from "./protocol/VoteStatus.js";
|
||||||
|
import * as bootstrap from "bootstrap";
|
||||||
|
import MuteState from "./protocol/MuteState.js";
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
const w = window as any;
|
const w = window as any;
|
||||||
|
|
@ -37,6 +39,28 @@ const elements = {
|
||||||
voteYesLabel: document.getElementById("voteYesLabel") as HTMLSpanElement,
|
voteYesLabel: document.getElementById("voteYesLabel") as HTMLSpanElement,
|
||||||
voteNoLabel: document.getElementById("voteNoLabel") as HTMLSpanElement,
|
voteNoLabel: document.getElementById("voteNoLabel") as HTMLSpanElement,
|
||||||
votetime: document.getElementById("votetime") as HTMLSpanElement,
|
votetime: document.getElementById("votetime") as HTMLSpanElement,
|
||||||
|
loginModal: document.getElementById("loginModal") as HTMLDivElement,
|
||||||
|
adminPassword: document.getElementById("adminPassword") as HTMLInputElement,
|
||||||
|
loginButton: document.getElementById("loginButton") as HTMLButtonElement,
|
||||||
|
adminInputVMID: document.getElementById("adminInputVMID") as HTMLInputElement,
|
||||||
|
badPasswordAlert: document.getElementById("badPasswordAlert") as HTMLDivElement,
|
||||||
|
incorrectPasswordDismissBtn: document.getElementById("incorrectPasswordDismissBtn") as HTMLButtonElement,
|
||||||
|
// Admin
|
||||||
|
staffbtns: document.getElementById("staffbtns") as HTMLDivElement,
|
||||||
|
restoreBtn: document.getElementById("restoreBtn") as HTMLButtonElement,
|
||||||
|
rebootBtn: document.getElementById("rebootBtn") as HTMLButtonElement,
|
||||||
|
clearQueueBtn: document.getElementById("clearQueueBtn") as HTMLButtonElement,
|
||||||
|
bypassTurnBtn: document.getElementById("bypassTurnBtn") as HTMLButtonElement,
|
||||||
|
endTurnBtn: document.getElementById("endTurnBtn") as HTMLButtonElement,
|
||||||
|
qemuMonitorBtn: document.getElementById("qemuMonitorBtn") as HTMLButtonElement,
|
||||||
|
xssCheckboxContainer: document.getElementById("xssCheckboxContainer") as HTMLDivElement,
|
||||||
|
forceVotePanel: document.getElementById("forceVotePanel") as HTMLDivElement,
|
||||||
|
forceVoteYesBtn: document.getElementById("forceVoteYesBtn") as HTMLButtonElement,
|
||||||
|
forceVoteNoBtn: document.getElementById("forceVoteNoBtn") as HTMLButtonElement,
|
||||||
|
indefTurnBtn: document.getElementById("indefTurnBtn") as HTMLButtonElement,
|
||||||
|
qemuMonitorInput: document.getElementById("qemuMonitorInput") as HTMLInputElement,
|
||||||
|
qemuMonitorSendBtn: document.getElementById("qemuMonitorSendBtn") as HTMLButtonElement,
|
||||||
|
qemuMonitorOutput: document.getElementById("qemuMonitorOutput") as HTMLTextAreaElement,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Start OSK */
|
/* Start OSK */
|
||||||
|
|
@ -237,6 +261,8 @@ var turnInterval : number | undefined = undefined;
|
||||||
var voteInterval : number | undefined = undefined;
|
var voteInterval : number | undefined = undefined;
|
||||||
var turnTimer = 0;
|
var turnTimer = 0;
|
||||||
var voteTimer = 0;
|
var voteTimer = 0;
|
||||||
|
var rank : Rank = Rank.Unregistered;
|
||||||
|
var perms : Permissions = new Permissions(0);
|
||||||
|
|
||||||
// Active VM
|
// Active VM
|
||||||
var VM : CollabVMClient | null = null;
|
var VM : CollabVMClient | null = null;
|
||||||
|
|
@ -308,6 +334,7 @@ function openVM(vm : VM) {
|
||||||
listeners.push(VM!.on('vote', (status : VoteStatus) => voteUpdate(status)));
|
listeners.push(VM!.on('vote', (status : VoteStatus) => voteUpdate(status)));
|
||||||
listeners.push(VM!.on('voteend', () => voteEnd()));
|
listeners.push(VM!.on('voteend', () => voteEnd()));
|
||||||
listeners.push(VM!.on('votecd', cd => window.alert(`Please wait ${cd} seconds before starting another vote.`)));
|
listeners.push(VM!.on('votecd', cd => window.alert(`Please wait ${cd} seconds before starting another vote.`)));
|
||||||
|
listeners.push(VM!.on('login', (rank : Rank, perms : Permissions) => onLogin(rank, perms)));
|
||||||
listeners.push(VM!.on('close', () => {
|
listeners.push(VM!.on('close', () => {
|
||||||
if (!expectedClose) alert("You have been disconnected from the server");
|
if (!expectedClose) alert("You have been disconnected from the server");
|
||||||
for (var l of listeners) l();
|
for (var l of listeners) l();
|
||||||
|
|
@ -316,8 +343,10 @@ function openVM(vm : VM) {
|
||||||
// Wait for the client to open
|
// Wait for the client to open
|
||||||
await new Promise<void>(res => VM!.on('open', () => res()));
|
await new Promise<void>(res => VM!.on('open', () => res()));
|
||||||
// Connect to node
|
// Connect to node
|
||||||
chatMessage("", vm.id);
|
chatMessage("", `<b>${vm.id}</b><hr>`);
|
||||||
var connected = await VM.connect(vm.id);
|
var connected = await VM.connect(vm.id);
|
||||||
|
elements.adminInputVMID.value = vm.id;
|
||||||
|
w.VMName = vm.id;
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
VM.close();
|
VM.close();
|
||||||
VM = null;
|
VM = null;
|
||||||
|
|
@ -349,6 +378,9 @@ function closeVM() {
|
||||||
// Clear users
|
// Clear users
|
||||||
users.splice(0, users.length);
|
users.splice(0, users.length);
|
||||||
elements.userlist.innerHTML = "";
|
elements.userlist.innerHTML = "";
|
||||||
|
rank = Rank.Unregistered;
|
||||||
|
perms = new Permissions(0);
|
||||||
|
w.VMName = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadList() {
|
function loadList() {
|
||||||
|
|
@ -449,9 +481,11 @@ function addUser(user : User) {
|
||||||
if (user.username === w.username)
|
if (user.username === w.username)
|
||||||
tr.classList.add("user-current");
|
tr.classList.add("user-current");
|
||||||
tr.appendChild(td);
|
tr.appendChild(td);
|
||||||
|
var u = {user: user, element: tr};
|
||||||
|
if (rank !== Rank.Unregistered) userModOptions(u);
|
||||||
elements.userlist.appendChild(tr);
|
elements.userlist.appendChild(tr);
|
||||||
if (olduser !== undefined) olduser.element = tr;
|
if (olduser !== undefined) olduser.element = tr;
|
||||||
else users.push({user: user, element: tr});
|
else users.push(u);
|
||||||
elements.onlineusercount.innerHTML = VM!.getUsers().length.toString();
|
elements.onlineusercount.innerHTML = VM!.getUsers().length.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -582,6 +616,125 @@ elements.screenshotButton.addEventListener('click', () => {
|
||||||
elements.voteResetButton.addEventListener('click', () => VM?.vote(true));
|
elements.voteResetButton.addEventListener('click', () => VM?.vote(true));
|
||||||
elements.voteYesBtn.addEventListener('click', () => VM?.vote(true));
|
elements.voteYesBtn.addEventListener('click', () => VM?.vote(true));
|
||||||
elements.voteNoBtn.addEventListener('click', () => VM?.vote(false));
|
elements.voteNoBtn.addEventListener('click', () => VM?.vote(false));
|
||||||
|
// Login
|
||||||
|
var usernameClick = false;
|
||||||
|
const loginModal = new bootstrap.Modal(elements.loginModal);
|
||||||
|
elements.loginModal.addEventListener('shown.bs.modal', () => elements.adminPassword.focus());
|
||||||
|
elements.username.addEventListener('click', () => {
|
||||||
|
if (!usernameClick) {
|
||||||
|
usernameClick = true;
|
||||||
|
setInterval(() => usernameClick = false, 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loginModal.show();
|
||||||
|
});
|
||||||
|
elements.loginButton.addEventListener('click', () => doLogin());
|
||||||
|
elements.adminPassword.addEventListener('keypress', (e) => e.key === "Enter" && doLogin());
|
||||||
|
elements.incorrectPasswordDismissBtn.addEventListener('click', () => elements.badPasswordAlert.style.display = "none");
|
||||||
|
function doLogin() {
|
||||||
|
var adminPass = elements.adminPassword.value;
|
||||||
|
if (adminPass === "") return;
|
||||||
|
VM?.login(adminPass);
|
||||||
|
elements.adminPassword.value = "";
|
||||||
|
var u = VM?.on('login', () => {
|
||||||
|
u!();
|
||||||
|
loginModal.hide();
|
||||||
|
elements.badPasswordAlert.style.display = "none";
|
||||||
|
});
|
||||||
|
var _u = VM?.on('badpw', () => {
|
||||||
|
_u!();
|
||||||
|
elements.badPasswordAlert.style.display = "block";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLogin(_rank : Rank, _perms : Permissions) {
|
||||||
|
rank = _rank;
|
||||||
|
perms = _perms;
|
||||||
|
elements.staffbtns.style.display = "block";
|
||||||
|
if (_perms.restore) elements.restoreBtn.style.display = "inline-block";
|
||||||
|
if (_perms.reboot) elements.rebootBtn.style.display = "inline-block";
|
||||||
|
if (_perms.bypassturn) {
|
||||||
|
elements.bypassTurnBtn.style.display = "inline-block";
|
||||||
|
elements.endTurnBtn.style.display = "inline-block";
|
||||||
|
elements.clearQueueBtn.style.display = "inline-block";
|
||||||
|
}
|
||||||
|
if (_rank === Rank.Admin) {
|
||||||
|
elements.qemuMonitorBtn.style.display = "inline-block";
|
||||||
|
elements.indefTurnBtn.style.display = "inline-block";
|
||||||
|
}
|
||||||
|
if (_perms.xss) elements.xssCheckboxContainer.style.display = "inline-block";
|
||||||
|
if (_perms.forcevote) elements.forceVotePanel.style.display = "block";
|
||||||
|
for (const user of users) userModOptions(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
function userModOptions(user : {
|
||||||
|
user : User,
|
||||||
|
element : HTMLTableRowElement
|
||||||
|
}) {
|
||||||
|
var tr = user.element;
|
||||||
|
var td = tr.children[0] as HTMLTableCellElement;
|
||||||
|
tr.classList.add("dropdown");
|
||||||
|
td.classList.add("dropdown-toggle");
|
||||||
|
td.setAttribute("data-bs-toggle", "dropdown");
|
||||||
|
td.setAttribute("role", "button");
|
||||||
|
td.setAttribute("aria-expanded", "false");
|
||||||
|
var ul = document.createElement('ul');
|
||||||
|
ul.classList.add("dropdown-menu", "dropdown-menu-dark", "table-dark", "text-light");
|
||||||
|
if (perms.bypassturn) addUserDropdownItem(ul, "End Turn", () => VM!.endTurn(user.user.username));
|
||||||
|
if (perms.ban) addUserDropdownItem(ul, "Ban", () => VM!.ban(user.user.username));
|
||||||
|
if (perms.kick) addUserDropdownItem(ul, "Kick", () => VM!.kick(user.user.username));
|
||||||
|
if (perms.rename) addUserDropdownItem(ul, "Rename", () => {
|
||||||
|
var newname = prompt(`Enter new username for ${user.user.username}`);
|
||||||
|
if (!newname) return;
|
||||||
|
VM!.renameUser(user.user.username, newname);
|
||||||
|
});
|
||||||
|
if (perms.mute) {
|
||||||
|
addUserDropdownItem(ul, "Temporary Mute", () => VM!.mute(user.user.username, MuteState.Temp));
|
||||||
|
addUserDropdownItem(ul, "Indefinite Mute", () => VM!.mute(user.user.username, MuteState.Perma));
|
||||||
|
addUserDropdownItem(ul, "Unmute", () => VM!.mute(user.user.username, MuteState.Unmuted));
|
||||||
|
}
|
||||||
|
if (perms.grabip) addUserDropdownItem(ul, "Get IP", async () => {
|
||||||
|
var ip = await VM!.getip(user.user.username);
|
||||||
|
alert(ip);
|
||||||
|
});
|
||||||
|
tr.appendChild(ul);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUserDropdownItem(ul : HTMLUListElement, text : string, func : () => void) {
|
||||||
|
var li = document.createElement('li');
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = "#";
|
||||||
|
a.classList.add("dropdown-item");
|
||||||
|
a.innerHTML = text;
|
||||||
|
a.addEventListener('click', () => func());
|
||||||
|
li.appendChild(a);
|
||||||
|
ul.appendChild(li);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin buttons
|
||||||
|
elements.restoreBtn.addEventListener('click', () => window.confirm("Are you sure you want to restore the VM?") && VM?.restore());
|
||||||
|
elements.rebootBtn.addEventListener('click', () => VM?.reboot());
|
||||||
|
elements.clearQueueBtn.addEventListener('click', () => VM?.clearQueue());
|
||||||
|
elements.bypassTurnBtn.addEventListener('click', () => VM?.bypassTurn());
|
||||||
|
elements.endTurnBtn.addEventListener('click', () => {
|
||||||
|
var user = VM?.getUsers().find(u => u.turn === 0);
|
||||||
|
if (user) VM?.endTurn(user.username);
|
||||||
|
});
|
||||||
|
elements.forceVoteNoBtn.addEventListener('click', () => VM?.forceVote(false));
|
||||||
|
elements.forceVoteYesBtn.addEventListener('click', () => VM?.forceVote(true));
|
||||||
|
elements.indefTurnBtn.addEventListener('click', () => VM?.indefiniteTurn());
|
||||||
|
|
||||||
|
async function sendQEMUCommand() {
|
||||||
|
if (!elements.qemuMonitorInput.value) return;
|
||||||
|
var cmd = elements.qemuMonitorInput.value;
|
||||||
|
elements.qemuMonitorOutput.innerHTML += `> ${cmd}\n`;
|
||||||
|
elements.qemuMonitorInput.value = "";
|
||||||
|
var response = await VM?.qemuMonitor(cmd);
|
||||||
|
elements.qemuMonitorOutput.innerHTML += `${response}\n`;
|
||||||
|
elements.qemuMonitorOutput.scrollTop = elements.qemuMonitorOutput.scrollHeight;
|
||||||
|
}
|
||||||
|
elements.qemuMonitorSendBtn.addEventListener('click', () => sendQEMUCommand());
|
||||||
|
elements.qemuMonitorInput.addEventListener('keypress', (e) => e.key === "Enter" && sendQEMUCommand());
|
||||||
|
|
||||||
elements.osk.addEventListener('click', () => elements.oskContainer.classList.toggle('d-none'));
|
elements.osk.addEventListener('click', () => elements.oskContainer.classList.toggle('d-none'));
|
||||||
|
|
||||||
|
|
@ -590,12 +743,42 @@ w.collabvm = {
|
||||||
openVM: openVM,
|
openVM: openVM,
|
||||||
closeVM: closeVM,
|
closeVM: closeVM,
|
||||||
loadList: loadList,
|
loadList: loadList,
|
||||||
multicollab: multicollab
|
multicollab: multicollab,
|
||||||
|
getVM: () => VM,
|
||||||
}
|
}
|
||||||
// Multicollab will stay in the global scope for backwards compatibility
|
// Multicollab will stay in the global scope for backwards compatibility
|
||||||
w.multicollab = multicollab;
|
w.multicollab = multicollab;
|
||||||
// Same goes for GetAdmin
|
// Same goes for GetAdmin
|
||||||
// w.GetAdmin = () => VM.admin;
|
w.GetAdmin = () => {
|
||||||
|
if (VM === null) return;
|
||||||
|
return {
|
||||||
|
adminInstruction: (...args : string[]) => {
|
||||||
|
args.unshift("admin");
|
||||||
|
VM?.send(...args);
|
||||||
|
},
|
||||||
|
restore: () => VM!.restore(),
|
||||||
|
reboot: () => VM!.reboot(),
|
||||||
|
clearQueue: () => VM!.clearQueue(),
|
||||||
|
bypassTurn: () => VM!.bypassTurn(),
|
||||||
|
endTurn: (username : string) => VM!.endTurn(username),
|
||||||
|
ban: (username : string) => VM!.ban(username),
|
||||||
|
kick: (username : string) => VM!.kick(username),
|
||||||
|
renameUser: (oldname : string, newname : string) => VM!.renameUser(oldname, newname),
|
||||||
|
mute: (username : string, state : number) => VM!.mute(username, state),
|
||||||
|
getip: (username : string) => VM!.getip(username),
|
||||||
|
qemuMonitor: (cmd : string) => {VM?.qemuMonitor(cmd); return;},
|
||||||
|
globalXss: (msg : string) => VM!.xss(msg),
|
||||||
|
forceVote: (result : boolean) => VM!.forceVote(result),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// more backwards compatibility
|
||||||
|
w.cvmEvents = {
|
||||||
|
on: (event : string | number, cb: (...args: any) => void) => {
|
||||||
|
if (VM === null) return;
|
||||||
|
VM.on('message', (...args : any) => cb(...args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.VMName = null;
|
||||||
|
|
||||||
// Load all VMs
|
// Load all VMs
|
||||||
loadList();
|
loadList();
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ import {createNanoEvents } from "nanoevents";
|
||||||
import * as Guacutils from './Guacutils.js';
|
import * as Guacutils from './Guacutils.js';
|
||||||
import VM from "./VM.js";
|
import VM from "./VM.js";
|
||||||
import { User } from "./User.js";
|
import { User } from "./User.js";
|
||||||
import { Rank } from "./Permissions.js";
|
import { Permissions, Rank } from "./Permissions.js";
|
||||||
import TurnStatus from "./TurnStatus.js";
|
import TurnStatus from "./TurnStatus.js";
|
||||||
import Mouse from "./mouse.js";
|
import Mouse from "./mouse.js";
|
||||||
import GetKeysym from '../keyboard.js';
|
import GetKeysym from '../keyboard.js';
|
||||||
import VoteStatus from "./VoteStatus.js";
|
import VoteStatus from "./VoteStatus.js";
|
||||||
|
import MuteState from "./MuteState.js";
|
||||||
|
|
||||||
export default class CollabVMClient {
|
export default class CollabVMClient {
|
||||||
// Fields
|
// Fields
|
||||||
|
|
@ -19,7 +20,9 @@ export default class CollabVMClient {
|
||||||
private username : string | null = null;
|
private username : string | null = null;
|
||||||
private mouse : Mouse = new Mouse();
|
private mouse : Mouse = new Mouse();
|
||||||
private rank : Rank = Rank.Unregistered;
|
private rank : Rank = Rank.Unregistered;
|
||||||
|
private perms : Permissions = new Permissions(0);
|
||||||
private voteStatus : VoteStatus | null = null;
|
private voteStatus : VoteStatus | null = null;
|
||||||
|
private node : string | null = null;
|
||||||
// events that are used internally and not exposed
|
// events that are used internally and not exposed
|
||||||
private emitter;
|
private emitter;
|
||||||
// public events
|
// public events
|
||||||
|
|
@ -105,6 +108,7 @@ export default class CollabVMClient {
|
||||||
console.error(`Server sent invalid message (${e})`);
|
console.error(`Server sent invalid message (${e})`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.publicEmitter.emit('message', ...msgArr);
|
||||||
switch (msgArr[0]) {
|
switch (msgArr[0]) {
|
||||||
case "nop": {
|
case "nop": {
|
||||||
// Send a NOP back
|
// Send a NOP back
|
||||||
|
|
@ -144,9 +148,14 @@ export default class CollabVMClient {
|
||||||
}
|
}
|
||||||
case "adduser": {
|
case "adduser": {
|
||||||
for (var i = 2; i < msgArr.length; i += 2) {
|
for (var i = 2; i < msgArr.length; i += 2) {
|
||||||
var user = new User(msgArr[i], parseInt(msgArr[i + 1]) as Rank);
|
var _user = this.users.find(u => u.username === msgArr[i]);
|
||||||
this.users.push(user);
|
if (_user !== undefined) {
|
||||||
this.publicEmitter.emit('adduser', user);
|
_user.rank = parseInt(msgArr[i + 1]) as Rank;
|
||||||
|
} else {
|
||||||
|
_user = new User(msgArr[i], parseInt(msgArr[i + 1]) as Rank);
|
||||||
|
this.users.push(_user);
|
||||||
|
}
|
||||||
|
this.publicEmitter.emit('adduser', _user);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -251,6 +260,37 @@ export default class CollabVMClient {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "admin": {
|
||||||
|
switch (msgArr[1]) {
|
||||||
|
case "0": {
|
||||||
|
// Login
|
||||||
|
switch (msgArr[2]) {
|
||||||
|
case "0":
|
||||||
|
this.publicEmitter.emit('badpw');
|
||||||
|
return;
|
||||||
|
case "1":
|
||||||
|
this.perms = new Permissions(65535);
|
||||||
|
this.rank = Rank.Admin;
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
this.perms = new Permissions(parseInt(msgArr[3]));
|
||||||
|
this.rank = Rank.Moderator;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.publicEmitter.emit('login', this.rank, this.perms);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "19": {
|
||||||
|
// IP
|
||||||
|
this.emitter.emit('ip', msgArr[2], msgArr[3]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "2": {
|
||||||
|
// QEMU
|
||||||
|
this.emitter.emit('qemu', msgArr[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,6 +331,7 @@ export default class CollabVMClient {
|
||||||
if (username === null) this.send("rename");
|
if (username === null) this.send("rename");
|
||||||
else this.send("rename", username);
|
else this.send("rename", username);
|
||||||
this.send("connect", id);
|
this.send("connect", id);
|
||||||
|
this.node = id;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,5 +383,110 @@ export default class CollabVMClient {
|
||||||
this.send("vote", vote ? "1" : "0");
|
this.send("vote", vote ? "1" : "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to login using the specified password
|
||||||
|
login(password : string) {
|
||||||
|
this.send("admin", "2", password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Admin commands */
|
||||||
|
|
||||||
|
// Restore
|
||||||
|
restore() {
|
||||||
|
if (!this.node) return;
|
||||||
|
this.send("admin", "8", this.node!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reboot
|
||||||
|
reboot() {
|
||||||
|
if (!this.node) return;
|
||||||
|
this.send("admin", "10", this.node!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear turn queue
|
||||||
|
clearQueue() {
|
||||||
|
if (!this.node) return;
|
||||||
|
this.send("admin", "17", this.node!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass turn
|
||||||
|
bypassTurn() {
|
||||||
|
this.send("admin", "20");
|
||||||
|
}
|
||||||
|
|
||||||
|
// End turn
|
||||||
|
endTurn(user : string) {
|
||||||
|
this.send("admin", "16", user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ban
|
||||||
|
ban(user : string) {
|
||||||
|
this.send("admin", "12", user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kick
|
||||||
|
kick(user : string) {
|
||||||
|
this.send("admin", "15", user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename user
|
||||||
|
renameUser(oldname : string, newname : string) {
|
||||||
|
this.send("admin", "18", oldname, newname);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mute user
|
||||||
|
mute(user : string, state : MuteState) {
|
||||||
|
this.send("admin", "14", user, state.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab IP
|
||||||
|
getip(user : string) {
|
||||||
|
if (this.users.find(u => u.username === user) === undefined) return false;
|
||||||
|
return new Promise<string>(res => {
|
||||||
|
var u = this.emitter.on('ip', (username : string, ip : string) => {
|
||||||
|
if (username !== user) return;
|
||||||
|
u();
|
||||||
|
res(ip);
|
||||||
|
})
|
||||||
|
this.send("admin", "19", user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// QEMU Monitor
|
||||||
|
qemuMonitor(cmd : string) {
|
||||||
|
return new Promise<string>(res => {
|
||||||
|
var u = this.emitter.on('qemu', output => {
|
||||||
|
u();
|
||||||
|
res(output);
|
||||||
|
})
|
||||||
|
this.send("admin", "5", this.node!, cmd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// XSS
|
||||||
|
xss(msg : string) {
|
||||||
|
this.send("admin", "21", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force vote
|
||||||
|
forceVote(result : boolean) {
|
||||||
|
this.send("admin", "13", result ? "1" : "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle turns
|
||||||
|
turns(enabled : boolean) {
|
||||||
|
this.send("admin", "22", enabled ? "1" : "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indefinite turn
|
||||||
|
indefiniteTurn() {
|
||||||
|
this.send("admin", "23");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide screen
|
||||||
|
hideScreen(hidden : boolean) {
|
||||||
|
this.send("admin", "24", hidden ? "1" : "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
on = (event : string | number, cb: (...args: any) => void) => this.publicEmitter.on(event, cb);
|
on = (event : string | number, cb: (...args: any) => void) => this.publicEmitter.on(event, cb);
|
||||||
}
|
}
|
||||||
7
src/ts/protocol/MuteState.ts
Normal file
7
src/ts/protocol/MuteState.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
enum MuteState {
|
||||||
|
Temp = 0,
|
||||||
|
Perma = 1,
|
||||||
|
Unmuted = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MuteState;
|
||||||
Loading…
Reference in New Issue
Block a user