Basic VM list support.

This commit is contained in:
Elijah R 2024-02-01 19:58:19 -05:00 committed by Elijah R
parent f244ae412f
commit 225f91f7a4
6 changed files with 226 additions and 3 deletions

13
Config.ts Normal file
View File

@ -0,0 +1,13 @@
export const Config = {
ServerAddresses: [
"wss://computernewb.com/collab-vm/vm0",
"wss://computernewb.com/collab-vm/vm1",
"wss://computernewb.com/collab-vm/vm2",
"wss://computernewb.com/collab-vm/vm3",
"wss://computernewb.com/collab-vm/vm4",
"wss://computernewb.com/collab-vm/vm5",
"wss://computernewb.com/collab-vm/vm6",
"wss://computernewb.com/collab-vm/vm7",
"wss://computernewb.com/collab-vm/vm8",
]
}

View File

@ -117,7 +117,9 @@
<div class="row"></div> <div class="row"></div>
</div> </div>
<div class="container-fluid" id="vmview"> <div class="container-fluid" id="vmview">
<canvas id="display" height="0" width="0" tabindex="-1"></canvas> <div id="vmDisplay">
</div>
<p id="turnstatus" class="text-light"></p> <p id="turnstatus" class="text-light"></p>
<div id="voteResetPanel" class="bg-dark text-light" style="display:none;"> <div id="voteResetPanel" class="bg-dark text-light" style="display:none;">
Do you want to reset the vm?<br/> Do you want to reset the vm?<br/>
@ -176,6 +178,6 @@
</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 src="../../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="../ts/main.ts" type="application/javascript"></script> <script type="module" src="../ts/main.ts" type="application/javascript"></script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,66 @@
import CollabVMClient from "./protocol/CollabVMClient.js";
import VM from "./protocol/VM.js";
import { Config } from "../../Config.js";
// Elements
const elements = {
vmlist: document.getElementById('vmlist') as HTMLDivElement,
}
// Listed VMs
const vms : VM[] = [];
function multicollab(url : string) {
return new Promise<void>(async (res, rej) => {
// Create the client
var client = new CollabVMClient(url);
// Wait for the client to open
await new Promise<void>(res => client.on('open', () => res()));
// Get the list of VMs
var list = await client.list();
// Add to the list
vms.push(...list);
// Add to the DOM
for (var vm of list) {
var div = document.createElement('div');
div.classList.add("col-sm-5", "col-md-3");
var card = document.createElement('div');
card.classList.add("card", "bg-dark", "text-light");
card.setAttribute("data-cvm-node", vm.id);
card.addEventListener('click', () => openVM(vm));
vm.thumbnail.classList.add("card-img-top");
var cardBody = document.createElement('div');
cardBody.classList.add("card-body");
var cardTitle = document.createElement('h5');
cardTitle.innerHTML = vm.displayName;
cardBody.appendChild(cardTitle);
card.appendChild(vm.thumbnail);
card.appendChild(cardBody);
div.appendChild(card);
elements.vmlist.children[0].appendChild(div);
reloadVMList();
}
res();
});
}
function openVM(vm : VM) {
}
function reloadVMList() {
var cards = Array.prototype.slice.call(elements.vmlist.children[0].children);
cards.sort(function(a, b) {
return a.id > b.id ? 1 : -1;
});
elements.vmlist.children[0].innerHTML = "";
cards.forEach((c) => elements.vmlist.children[0].appendChild(c));
}
// Public API
var w = window as any;
w.multicollab = multicollab;
w.openVM = openVM;
// Load all VMs
for (var url of Config.ServerAddresses) {
multicollab(url);
}

View File

@ -1,3 +1,92 @@
import {createNanoEvents } from "nanoevents";
import * as Guacutils from './Guacutils.js';
import VM from "./VM.js";
export default class CollabVMClient { export default class CollabVMClient {
// Fields
private socket : WebSocket;
private canvas : HTMLCanvasElement;
private ctx : CanvasRenderingContext2D;
private url : string;
// events that are used internally and not exposed
private emitter;
// public events
private publicEmitter;
constructor(url : string) {
// Save the URL
this.url = url;
// Create the events
this.emitter = createNanoEvents();
this.publicEmitter = createNanoEvents();
// Create the canvas
this.canvas = document.createElement('canvas');
// Set tab index so it can be focused
this.canvas.tabIndex = -1;
// Get the 2D context
this.ctx = this.canvas.getContext('2d')!;
// Create the WebSocket
this.socket = new WebSocket(url, "guacamole");
// Add the event listeners
this.socket.addEventListener('open', () => this.onOpen());
this.socket.addEventListener('message', (event) => this.onMessage(event));
}
// Fires when the WebSocket connection is opened
private onOpen() {
this.publicEmitter.emit('open');
}
// Fires on WebSocket message
private onMessage(event : MessageEvent) {
var msgArr : string[];
try {
msgArr = Guacutils.decode(event.data);
} catch (e) {
console.error(`Server sent invalid message (${e})`);
return;
}
switch (msgArr[0]) {
case "nop": {
// Send a NOP back
this.send("nop");
break;
}
case "list": {
// pass msgarr to the emitter for processing by list()
console.log("got list")
this.emitter.emit('list', msgArr.slice(1));
}
}
}
// Sends a message to the server
send(...args : string[]) {
this.socket.send(Guacutils.encode(...args));
}
// Get a list of all VMs
list() : Promise<VM[]> {
return new Promise((res, rej) => {
var u = this.emitter.on('list', (list : string[]) => {
u();
var vms : VM[] = [];
for (var i = 0; i < list.length; i += 3) {
var th = new Image();
th.src = "data:image/jpeg;base64," + list[i + 2];
vms.push({
url: this.url,
id: list[i],
displayName: list[i + 1],
thumbnail: th,
});
console.log("pushed", list[i]);
}
res(vms);
});
this.send("list");
});
}
on = (event : string | number, cb: (...args: any) => void) => this.publicEmitter.on(event, cb);
} }

View File

@ -0,0 +1,43 @@
export function decode(string : string) : string[] {
let pos = -1;
let sections = [];
for(;;) {
let len = string.indexOf('.', pos + 1);
if(len === -1)
break;
pos = parseInt(string.slice(pos + 1, len)) + len + 1;
// don't allow funky protocol length
if(pos > string.length)
return [];
sections.push(string.slice(len + 1, pos));
const sep = string.slice(pos, pos + 1);
if(sep === ',')
continue;
else if(sep === ';')
break;
else
// Invalid data.
return [];
}
return sections;
}
export function encode(...string : string[]) : string {
let command = '';
for(var i = 0; i < string.length; i++) {
let current = string[i];
command += current.toString().length + '.' + current;
command += ( i < string.length - 1 ? ',' : ';');
}
return command;
}

10
src/ts/protocol/VM.ts Normal file
View File

@ -0,0 +1,10 @@
export default interface VM {
url : string;
id : string;
displayName : string;
thumbnail : HTMLImageElement;
}