337 lines
11 KiB
JavaScript

import RFB from '/static/novnc/core/rfb.js';
const sidebarHeader = document.getElementById('sidebarHeader');
const sidebarNav = document.getElementById('sidebarNav');
const sidebarList = document.getElementById('sidebarList');
const overlay = document.getElementById('overlay');
const userBanner = document.getElementById('bannerUserid');
const mainContent = document.getElementById('mainContent');
const vmListBtn = document.getElementById('vmListBtn');
let consoleContainer = document.createElement('div');
let cachedInstructions = '';
const cookies = getCookies();
let vmName = "";
let sbHeaderStatus = "";
let status = "";
let vmList = [];
userBanner.textContent = cookies['username'];
await validatePath();
window.addEventListener('popstate', () =>{
validatePath();
});
async function validatePath() {
const path = window.location.pathname;
if (path.startsWith('/vm')) {
const split = path.split('/');
vmName = split.pop()
const access = await validateAccess(vmName);
} else {
navigateTo('start');
}
}
function getVMList() {
vmList = [];
fetch('/api/vmlist')
.then(res => res.json())
.then(data => {
if (data.access) {
data.allowed.forEach(vm => {
const hostname = vm.hostname;
vmList.push(hostname);
const li = document.createElement('li');
const a = document.createElement('a');
a.classList.add('vm-link');
li.appendChild(a);
a.innerText = hostname;
a.addEventListener('click', () => navigateTo(hostname));
sidebarList.appendChild(li);
});
} else {
mainContent.innerHTML = "You haven't been assigned any VM"
}
}).catch(err => console.error('Error getting VM list:', err));
};
function loadStart() {
vmName = "";
sidebarHeader.innerHTML = 'Virtual Machines';
sidebarList.innerHTML = '';
mainContent.innerHTML = 'Welcome';
consoleContainer.innerHTML = '';
cachedInstructions = '';
sbHeaderStatus = '';
status = '';
getVMList();
}
function navigateTo(vmName) {
if(vmName === 'start') {
history.pushState({vm: ''}, '', '/');
loadStart();
} else{
history.pushState({vm: vmName}, '', `/vm/${vmName}`);
selectVM(vmName);
}
}
async function validateAccess(vmName){
fetch(`/api/vmaccess?vm=${vmName}`)
.then(response => response.json())
.then(data => {
if (!data.access){
navigateTo('start');
} else {
selectVM(vmName);
}
})
.catch(err => console.error('Error validating user access:', err));
}
function selectVM(vm){
vmName = vm;
mainContent.innerHTML = '';
sidebarHeader.innerHTML = '';
const sbHeaderDiv = document.createElement('div');
sbHeaderDiv.innerHTML = vmName;
sbHeaderStatus = document.createElement('div');
sbHeaderStatus.classList.add('status-indicator');
sbHeaderStatus.innerHTML = 'Status: ';
status = document.createElement('span');
getVMStatus();
sbHeaderStatus.appendChild(status);
sidebarHeader.appendChild(sbHeaderDiv);
sidebarHeader.appendChild(sbHeaderStatus);
sidebarList.innerHTML = '';
const infoli = document.createElement('li');
const consoleli = document.createElement('li');
const snapli = document.createElement('li');
const homeli = document.createElement('li');
const infoa = document.createElement('a');
const consolea = document.createElement('a');
const snapa = document.createElement('a');
const homea = document.createElement('a');
infoli.appendChild(infoa);
consoleli.appendChild(consolea);
snapli.appendChild(snapa);
homeli.appendChild(homea);
infoa.innerText = 'Information';
infoa.addEventListener('click', () => vmStart());
consolea.innerText = 'Console';
consolea.addEventListener('click', () => consolePage());
snapa.innerText = 'Snapshot';
snapa.addEventListener('click', () => getSnapshots());
homea.innerText = 'Back to start';
homea.addEventListener('click', () => navigateTo('start'));
homea.classList.add('home-link');
sidebarList.appendChild(infoli);
sidebarList.appendChild(consoleli);
sidebarList.appendChild(snapli);
sidebarList.appendChild(homeli);
vmStart();
}
function consolePage(){
mainContent.innerHTML = '';
consoleContainer.innerHTML = '';
consoleContainer.className = 'console-container';
consoleContainer.name = 'console'
const startBtn = document.createElement('button');
const stopBtn = document.createElement('button');
startBtn.innerText = 'Power on';
stopBtn.innerText = 'Power off';
startBtn.className = 'vm-button';
stopBtn.className = 'vm-button';
startBtn.addEventListener('click', () => sendPowerAction('start'));
stopBtn.addEventListener('click', () => sendPowerAction('stop'));
mainContent.appendChild(startBtn);
mainContent.appendChild(stopBtn);
mainContent.appendChild(consoleContainer);
connectToVM();
}
async function connectToVM(){
fetch(`/api/vncproxy?vm=${vmName}`)
.then(response => response.json())
.then(data => {
if (data.ready) {
setTimeout(() => {
let wsurl = `wss://${data.hostname}.dsv.su.se/vnc?ticket=${encodeURIComponent(data.ticket)}`;
const rfb = new RFB(consoleContainer, wsurl, {
credentials: {
password: data.ticket
}
});
rfb.viewOnly = false;
rfb.scaleViewport = true;
rfb.background = '#000';
}, 1000);
} else {
throw new Error('Failed with VNC D:');
}
})
.catch(err => console.error('Error connecting to VNC proxy:', err));
}
function sendPowerAction(action){
fetch(`/api/${action}?vm=${vmName}`)
.then(res => res.json())
.then(data => {
alert(`${action} exectuted!`)
})
.catch(err => alert('Error:', + err))
}
function getCookies() {
var out = new Object();
const cookies = document.cookie.split('; ');
cookies.forEach((cookie) => {
const temp = cookie.split('=');
const name = temp[0];
const value = temp.slice(1).join('=');
out[name] = decodeURIComponent(value);
});
return out;
}
async function vmStart() {
consoleContainer.innerHTML = '';
mainContent.innerHTML = '';
if (cachedInstructions === ''){
fetch(`/api/metadata?vm=${vmName}`)
.then(res => res.json())
.then(data => getInstructions(data.instructions))
.catch(err => alert('Error:', err))
} else {
mainContent.innerHTML = cachedInstructions;
}
};
async function getInstructions(instructions) {
if (cachedInstructions === '') cachedInstructions = instructions;
mainContent.innerHTML = instructions;
};
async function getVMStatus(){
fetch(`/api/status?vm=${vmName}`)
.then(res => res.json())
.then(data => {
if(data) {
status.textContent = "On";
sbHeaderStatus.classList.remove('stopped');
sbHeaderStatus.classList.add('running');
} else {
status.textContent = "Off";
sbHeaderStatus.classList.remove('running');
sbHeaderStatus.classList.add('stopped');
}
})
.catch(err => console.error(err))
}
async function getSnapshots(){
mainContent.innerHTML = '';
consoleContainer.innerHTML = '';
fetch(`/api/getsnapshots?vm=${vmName}`)
.then(res => res.json())
.then(data => displaySnapshots(data))
.catch(err => console.log(err))
};
function displaySnapshots(data){
if (Object.keys(data).length === 1){
mainContent.innerHTML = "There is nothing to show here";
} else {
const listData = Object.entries(data);
const table = document.createElement('table');
const thead = table.createTHead();
const tbody = table.createTBody();
const headRow = thead.insertRow(0);
const headNameCell = headRow.insertCell(0);
const headDescriptionCell = headRow.insertCell(1);
const headDateCell = headRow.insertCell(2);
const headRollbackCell = headRow.insertCell(3);
const [headName, headValue] = listData[0];
const headRollbackValue = 'Rollback';
headNameCell.textContent = headName;
headDescriptionCell.textContent = headValue[0];
headDateCell.textContent = headValue[1];
headRollbackCell.textContent = headRollbackValue;
delete data[headName];
let rowInd = 0;
for (const [key, value] of Object.entries(data)){
let row = tbody.insertRow(rowInd);
if (rowInd % 2 === 0 ) row.classList.add('even');
else row.classList.add('odd');
const keyCell = row.insertCell(0);
const descriptionCell = row.insertCell(1);
const dateCell = row.insertCell(2);
const rollbackCell = row.insertCell(3);
keyCell.textContent = key;
descriptionCell.textContent = value[0];
const date = new Date(value[1] * 1000);
const formatted = new Intl.DateTimeFormat("sv-SE", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
}).format(date);
dateCell.textContent = formatted;
const rollbackBtn = document.createElement('button');
rollbackBtn.textContent = 'Restore';
rollbackBtn.dataset.snapname = key;
rollbackBtn.addEventListener('click', function() {
const snapname = this.getAttribute('data-snapname');
rollbackSnapshot(snapname);
})
rollbackCell.appendChild(rollbackBtn);
rowInd++;
}
mainContent.appendChild(table);
}
};
async function rollbackSnapshot(snapname) {
const confirmRollback = confirm("Are you sure you want to rollback the VM to this snapshot?\nDoing this will remove all data on it and can't be undone.");
if(!confirmRollback) return;
try {
fetch(`/api/rollback?vm=${vmName}&snap=${snapname}`)
.then(res => res.json())
.then(data => {
if(data.success){
alert(`Succesfully rollbacked ${vmName}`);
} else {
alert(`Failed with rollbacked for ${vmName}`);
}
})
}catch (err) {
}
}
setInterval(async () => {
if (status !== ""){
getVMStatus();
}
}, 10000);