Basic VM list support.
This commit is contained in:
parent
a6a28ef518
commit
e8d1eb4b87
13
Config.ts
Normal file
13
Config.ts
Normal 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",
|
||||
]
|
||||
}
|
||||
|
|
@ -117,7 +117,9 @@
|
|||
<div class="row"></div>
|
||||
</div>
|
||||
<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>
|
||||
<div id="voteResetPanel" class="bg-dark text-light" style="display:none;">
|
||||
Do you want to reset the vm?<br/>
|
||||
|
|
@ -176,6 +178,6 @@
|
|||
</div>
|
||||
<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="../ts/main.ts" type="application/javascript"></script>
|
||||
<script type="module" src="../ts/main.ts" type="application/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -1,3 +1,92 @@
|
|||
import {createNanoEvents } from "nanoevents";
|
||||
import * as Guacutils from './Guacutils.js';
|
||||
import VM from "./VM.js";
|
||||
|
||||
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);
|
||||
}
|
||||
43
src/ts/protocol/Guacutils.ts
Normal file
43
src/ts/protocol/Guacutils.ts
Normal 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
10
src/ts/protocol/VM.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export default interface VM {
|
||||
url : string;
|
||||
|
||||
id : string;
|
||||
|
||||
displayName : string;
|
||||
|
||||
thumbnail : HTMLImageElement;
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user