import RFB from '/static/novnc/core/rfb.js'; const sidebarHeader = document.getElementById('sidebarHeader'); const powerdiv = document.getElementById('powermenu'); const sidebarList = document.getElementById('sidebarList'); const overlay = document.getElementById('overlay'); const userBanner = document.getElementById('bannerUserid'); const mainContent = document.getElementById('mainContent'); let consoleContainer = document.createElement('div'); let cachedInstructions = ''; const cookies = getCookies(); let vmName = ""; let homeButton = true; let sbHeaderStatus = ""; let status = ""; let vmList = []; userBanner.textContent = cookies['username']; await validatePath(); window.addEventListener('popstate', async function() { await validatePath(true); }); async function validatePath(popState = false) { const path = window.location.pathname; if (path.startsWith('/vm')) { const split = path.split('/'); vmName = split.pop() await validateAccess(vmName, popState); } else { navigateTo('start', popState); } } function getVMList() { vmList = []; fetch('/api/vmlist') .then(res => res.json()) .then(data => { if (data.access) { if (data.hosts.length === 1) { vmName = data.hosts[0].hostname; homeButton = false; navigateTo(vmName); } else { data.hosts.forEach(vm => { const hostname = vm.hostname; vmList.push(hostname); const li = document.createElement('li'); const a = document.createElement('a'); 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 = ""; document.title = 'Student Proxy Access' powerdiv.innerHTML = ''; sidebarHeader.innerHTML = 'Virtual Machines'; sidebarList.innerHTML = ''; mainContent.innerHTML = 'Welcome'; consoleContainer.innerHTML = ''; cachedInstructions = ''; sbHeaderStatus = ''; status = ''; getVMList(); } function navigateTo(vmName, popState = false) { if(!popState){ if(vmName === 'start') { history.pushState({vm: ''}, '', '/'); loadStart(); } else{ history.pushState({vm: vmName}, '', `/vm/${vmName}`); selectVM(vmName); } } else { if(vmName === 'start') { loadStart(); } else{ selectVM(vmName); } } } async function validateAccess(vmName, popState = false){ fetch(`/api/vmaccess?vm=${vmName}`) .then(response => response.json()) .then(data => { if (!data.access){ navigateTo('start', popState); } else { homeButton = data.multiple; selectVM(vmName); } }) .catch(err => console.error('Error validating user access:', err)); } function selectVM(vm){ vmName = vm; document.title = `Student Proxy Access - ${vmName}` 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'); sbHeaderStatus.appendChild(status); sidebarHeader.appendChild(sbHeaderDiv); sidebarHeader.appendChild(sbHeaderStatus); powerdiv.classList.add('powermenu'); const powerText = document.createElement('div'); powerdiv.appendChild(powerText); powerText.innerHTML = 'Power Actions'; const submenudiv = document.createElement('div'); submenudiv.id = 'submenu' submenudiv.classList.add('submenu'); powerdiv.appendChild(submenudiv); const powerul = document.createElement('ul'); const powerstart = document.createElement('li'); const powerstartbutton = document.createElement('button'); powerstartbutton.id = "powerstart"; powerstart.appendChild(powerstartbutton); powerstartbutton.innerText = 'Start'; powerstartbutton.addEventListener('click', () => sendPowerAction('start')); const powershutdown = document.createElement('li'); const powershutdownbutton = document.createElement('button'); powershutdownbutton.id = "powershutdown"; powershutdown.appendChild(powershutdownbutton); powershutdownbutton.innerText = 'Shutdown'; powershutdownbutton.addEventListener('click', () => sendPowerAction('shutdown')); const powerreset = document.createElement('li'); const powerresetbutton = document.createElement('button'); powerresetbutton.id = "powerstop"; powerreset.appendChild(powerresetbutton); powerresetbutton.innerText = 'Reset'; powerresetbutton.addEventListener('click', () => sendPowerAction('reset')); submenudiv.appendChild(powerul); powerul.appendChild(powerstart); powerul.appendChild(powershutdown); powerul.appendChild(powerreset); getVMStatus(); sidebarList.innerHTML = ''; const infoli = document.createElement('li'); const infoa = document.createElement('a'); infoli.appendChild(infoa); infoa.innerText = 'Information'; infoa.addEventListener('click', () => vmStart()); const consoleli = document.createElement('li'); const consolea = document.createElement('a'); consoleli.appendChild(consolea); consolea.innerText = 'Console'; consolea.addEventListener('click', () => consolePage()); const snapli = document.createElement('li'); const snapa = document.createElement('a'); snapli.appendChild(snapa); snapa.innerText = 'Snapshot'; snapa.addEventListener('click', () => getSnapshots()); const homeli = document.createElement('li'); const homea = document.createElement('a'); homeli.appendChild(homea); homea.innerText = 'Back to start'; homea.addEventListener('click', () => navigateTo('start')); homea.classList.add('home-link'); sidebarList.appendChild(infoli); sidebarList.appendChild(consoleli); sidebarList.appendChild(snapli); if(homeButton){ sidebarList.appendChild(homeli); } vmStart(); } function consolePage(){ mainContent.innerHTML = ''; consoleContainer.innerHTML = ''; consoleContainer.className = 'console-container'; consoleContainer.name = 'console' const consoleInfo = document.createElement('p'); consoleInfo.innerText = 'This page can be used to access the virtual machine directly from this webpage. Due to lack of ability to use copy and paste with the console it is usually preferable to use SSH or RDP to access the machines.' mainContent.appendChild(consoleInfo); 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?vm=${vmName}`; 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){ if (action === 'reset') { const confirmReset = confirm('Warning: This might result in data loss!'); if (!confirmReset) return; } 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 === ''){ await fetch(`/api/metadata?vm=${vmName}`) .then(res => res.json()) .then(data => { cachedInstructions = data.instructions; }) .catch(err => alert('Error:', err)) } mainContent.innerHTML = cachedInstructions; }; 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'); updatePowerMenu(true) } else { status.textContent = "Off"; sbHeaderStatus.classList.remove('running'); sbHeaderStatus.classList.add('stopped'); updatePowerMenu(false) } }) .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){ const snapInfo = document.createElement('p'); snapInfo.innerText = 'On this page you have a list of all snapshots for this VM.'; const snapInfo2 = document.createElement('p'); snapInfo2.innerText = "Observe that a machine should only be rolledback if the machine breaks in a way you can't fix it since all the data that's been created since the start of course will be delete and unrecoverable."; mainContent.appendChild(snapInfo); mainContent.appendChild(snapInfo2); if (Object.keys(data).length > 1){ 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) { } } async function updatePowerMenu(state){ const powerstart = document.querySelector('#powerstart'); const powershutdown = document.querySelector('#powershutdown'); if(state){ powerstart.disabled = true; powershutdown.disabled = false; } else { powerstart.disabled = false; powershutdown.disabled = true; } } setInterval(async () => { if (status !== ""){ getVMStatus(); } }, 10000);