app.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. const API_BASE = window.location.origin;
  2. function getStateClass(state) {
  3. switch (state) {
  4. case 'running': return 'bg-success';
  5. case 'shut off': return 'bg-secondary';
  6. case 'paused': return 'bg-warning text-dark';
  7. case 'crashed': return 'bg-danger';
  8. default: return 'bg-light text-dark';
  9. }
  10. }
  11. async function loadISOs() {
  12. try {
  13. const response = await fetch(`${API_BASE}/isos`);
  14. const data = await response.json();
  15. const tbody = document.getElementById('isoTableBody');
  16. tbody.innerHTML = '';
  17. data.isos.forEach(iso => {
  18. const row = document.createElement('tr');
  19. row.innerHTML = `
  20. <td>${iso.name}</td>
  21. <td>
  22. <button class="btn btn-sm btn-danger" onclick="deleteISO('${iso.name}')">Delete</button>
  23. </td>
  24. `;
  25. tbody.appendChild(row);
  26. });
  27. } catch (error) {
  28. console.error('Error loading ISOs:', error);
  29. alert('Error loading ISOs');
  30. }
  31. }
  32. async function deleteISO(name) {
  33. if (confirm(`Delete ISO ${name}?`)) {
  34. try {
  35. const response = await fetch(`${API_BASE}/isos/${name}`, { method: 'DELETE' });
  36. const result = await response.json();
  37. if (response.ok) {
  38. alert(result.message);
  39. loadISOs();
  40. loadISOsForSelect(); // Refresh the select
  41. } else {
  42. alert(result.error);
  43. }
  44. } catch (error) {
  45. console.error('Error deleting ISO:', error);
  46. alert('Error deleting ISO');
  47. }
  48. }
  49. }
  50. async function loadISOsForSelect() {
  51. try {
  52. const response = await fetch(`${API_BASE}/isos`);
  53. const data = await response.json();
  54. const select = document.getElementById('iso');
  55. select.innerHTML = '<option value="">Select ISO</option>';
  56. data.isos.forEach(iso => {
  57. select.innerHTML += `<option value="${iso.path}">${iso.name}</option>`;
  58. });
  59. } catch (error) {
  60. console.error('Error loading ISOs:', error);
  61. }
  62. }
  63. /* Load VMs and populate the table */
  64. async function loadVMs() {
  65. try {
  66. const response = await fetch(`${API_BASE}/vms`);
  67. const data = await response.json();
  68. const tbody = document.getElementById('vmTableBody');
  69. tbody.innerHTML = '';
  70. const select = document.getElementById('bootVM');
  71. select.innerHTML = '<option value="">Select VM</option>';
  72. data.vms.forEach(vm => {
  73. const row = document.createElement('tr');
  74. row.innerHTML = `
  75. <td>${vm.id}</td>
  76. <td>${vm.name}</td>
  77. <td><span class="badge ${getStateClass(vm.state)}">${vm.state}</span></td>
  78. <td>
  79. <button class="btn btn-sm btn-success" onclick="startVM('${vm.name}')">Lanzar</button>
  80. <button class="btn btn-sm btn-warning" onclick="stopVM('${vm.name}')">Stop</button>
  81. <button class="btn btn-sm btn-danger" onclick="deleteVM('${vm.name}')">Delete</button>
  82. ${vm.vnc_port ? `<button class="btn btn-sm btn-info" onclick="openVNC('${vm.name}')">Console</button>` : ''}
  83. </td>
  84. `;
  85. tbody.appendChild(row);
  86. select.innerHTML += `<option value="${vm.name}">${vm.name}</option>`;
  87. });
  88. loadISOsForSelect(); // Load ISOs after loading VMs
  89. } catch (error) {
  90. console.error('Error loading VMs:', error);
  91. alert('Error loading VMs');
  92. }
  93. }
  94. async function startVM(name) {
  95. try {
  96. await fetch(`${API_BASE}/vms/${name}/start`);
  97. loadVMs();
  98. } catch (error) {
  99. console.error('Error starting VM:', error);
  100. alert('Error starting VM');
  101. }
  102. }
  103. async function stopVM(name) {
  104. try {
  105. await fetch(`${API_BASE}/vms/${name}/stop`);
  106. loadVMs();
  107. } catch (error) {
  108. console.error('Error stopping VM:', error);
  109. alert('Error stopping VM');
  110. }
  111. }
  112. async function deleteVM(name) {
  113. if (confirm(`Delete VM ${name}?`)) {
  114. try {
  115. await fetch(`${API_BASE}/vms/${name}`, { method: 'DELETE' });
  116. loadVMs();
  117. } catch (error) {
  118. console.error('Error deleting VM:', error);
  119. alert('Error deleting VM');
  120. }
  121. }
  122. }
  123. function openVNC(vm_name) {
  124. fetch(`${API_BASE}/console/${vm_name}`)
  125. .then(response => response.json())
  126. .then(data => {
  127. if (data.url) {
  128. window.open(data.url);
  129. } else {
  130. alert(data.error || 'Error opening console');
  131. }
  132. })
  133. .catch(error => {
  134. console.error('Error:', error);
  135. alert('Error opening console');
  136. });
  137. }
  138. document.getElementById('createVMForm').addEventListener('submit', async (e) => {
  139. e.preventDefault();
  140. const name = document.getElementById('name').value;
  141. const memory = document.getElementById('memory').value;
  142. const disk_size = document.getElementById('disk_size').value;
  143. const cpus = document.getElementById('cpus').value;
  144. const iso = document.getElementById('iso').value;
  145. try {
  146. const response = await fetch(`${API_BASE}/vms?name=${name}&memory=${memory}&disk_size=${disk_size}&cpus=${cpus}&iso=${iso}`, {
  147. method: 'POST'
  148. });
  149. let result;
  150. try {
  151. result = await response.json();
  152. } catch {
  153. result = { error: 'Unknown error' };
  154. }
  155. if (response.ok) {
  156. alert(result.message);
  157. loadVMs();
  158. document.getElementById('createVMForm').reset();
  159. } else {
  160. alert(result.error || 'Error creating VM');
  161. }
  162. } catch (error) {
  163. console.error('Error creating VM:', error);
  164. alert('Error creating VM');
  165. }
  166. });
  167. document.getElementById('setBootOrderForm').addEventListener('submit', async (e) => {
  168. e.preventDefault();
  169. const vm_name = document.getElementById('bootVM').value;
  170. const boot_order_str = document.getElementById('bootOrder').value;
  171. const order = boot_order_str.split(',').map(s => s.trim());
  172. try {
  173. const response = await fetch(`${API_BASE}/vms/${vm_name}/boot-order`, {
  174. method: 'PUT',
  175. headers: {
  176. 'Content-Type': 'application/json'
  177. },
  178. body: JSON.stringify({ order: order })
  179. });
  180. const result = await response.json();
  181. if (response.ok) {
  182. alert(result.message);
  183. loadVMs();
  184. document.getElementById('setBootOrderForm').reset();
  185. } else {
  186. alert(result.error);
  187. }
  188. } catch (error) {
  189. console.error('Error setting boot order:', error);
  190. alert('Error setting boot order');
  191. }
  192. });
  193. document.getElementById('uploadIsoForm').addEventListener('submit', (e) => {
  194. e.preventDefault();
  195. const fileInput = document.getElementById('isoFile');
  196. const file = fileInput.files[0];
  197. if (!file) {
  198. alert('Please select a file');
  199. return;
  200. }
  201. const formData = new FormData();
  202. formData.append('file', file);
  203. const progressContainer = document.getElementById('progressContainer');
  204. const progressBar = document.getElementById('uploadProgress');
  205. const uploadBtn = document.getElementById('uploadBtn');
  206. // Show progress bar
  207. progressContainer.style.display = 'block';
  208. uploadBtn.disabled = true;
  209. progressBar.style.width = '0%';
  210. progressBar.textContent = '0%';
  211. const xhr = new XMLHttpRequest();
  212. xhr.upload.addEventListener('progress', (e) => {
  213. if (e.lengthComputable) {
  214. const percentComplete = Math.round((e.loaded / e.total) * 100);
  215. progressBar.style.width = percentComplete + '%';
  216. progressBar.textContent = percentComplete + '%';
  217. }
  218. });
  219. xhr.addEventListener('load', () => {
  220. if (xhr.status === 200) {
  221. const result = JSON.parse(xhr.responseText);
  222. alert(result.message);
  223. loadISOsForSelect();
  224. document.getElementById('uploadIsoForm').reset();
  225. progressContainer.style.display = 'none';
  226. uploadBtn.disabled = false;
  227. } else {
  228. try {
  229. const result = JSON.parse(xhr.responseText);
  230. alert(result.error || 'Error uploading ISO');
  231. } catch {
  232. alert('Error uploading ISO');
  233. }
  234. progressContainer.style.display = 'none';
  235. uploadBtn.disabled = false;
  236. }
  237. });
  238. xhr.addEventListener('error', () => {
  239. alert('Error uploading ISO');
  240. progressContainer.style.display = 'none';
  241. uploadBtn.disabled = false;
  242. });
  243. xhr.open('POST', `${API_BASE}/upload-iso`);
  244. xhr.send(formData);
  245. });
  246. // Load VMs on page load
  247. loadVMs();
  248. loadISOs();