index.html 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  1. <!DOCTYPE html>
  2. <html lang="es">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>VirtManager Dashboard</title>
  7. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
  8. <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
  9. <script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
  10. <style>
  11. * {
  12. margin: 0;
  13. padding: 0;
  14. box-sizing: border-box;
  15. }
  16. body {
  17. height: 100vh;
  18. overflow: hidden;
  19. background-color: #f8f9fa;
  20. }
  21. .dashboard-container {
  22. display: flex;
  23. height: 100vh;
  24. }
  25. /* Sidebar */
  26. .sidebar {
  27. width: 300px;
  28. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  29. color: white;
  30. overflow-y: auto;
  31. box-shadow: 2px 0 5px rgba(0,0,0,0.1);
  32. display: flex;
  33. flex-direction: column;
  34. transition: transform 0.3s ease;
  35. }
  36. .sidebar.hidden {
  37. transform: translateX(-100%);
  38. position: absolute;
  39. left: 0;
  40. height: 100vh;
  41. z-index: 1000;
  42. }
  43. .sidebar-header {
  44. padding: 20px;
  45. border-bottom: 1px solid rgba(255,255,255,0.2);
  46. flex-shrink: 0;
  47. }
  48. .sidebar-header h4 {
  49. margin: 0;
  50. font-weight: 600;
  51. font-size: 1.5rem;
  52. }
  53. .sidebar-header p {
  54. margin: 5px 0 0 0;
  55. font-size: 0.9rem;
  56. opacity: 0.9;
  57. }
  58. .sidebar-vms {
  59. flex: 1;
  60. overflow-y: auto;
  61. padding: 10px;
  62. }
  63. .vm-item {
  64. padding: 12px 15px;
  65. margin-bottom: 8px;
  66. background: rgba(255,255,255,0.15);
  67. border: 2px solid transparent;
  68. border-radius: 8px;
  69. cursor: pointer;
  70. transition: all 0.3s ease;
  71. display: flex;
  72. justify-content: space-between;
  73. align-items: center;
  74. flex-wrap: wrap;
  75. }
  76. .vm-item:hover {
  77. background: rgba(255,255,255,0.25);
  78. transform: translateX(5px);
  79. }
  80. .vm-item.active {
  81. background: rgba(255,255,255,0.3);
  82. border-color: rgba(255,255,255,0.5);
  83. font-weight: 600;
  84. }
  85. .vm-item-name {
  86. flex: 1;
  87. min-width: 120px;
  88. }
  89. .vm-item-badge {
  90. font-size: 0.75rem;
  91. padding: 3px 8px;
  92. border-radius: 4px;
  93. margin-left: auto;
  94. }
  95. .sidebar-actions {
  96. padding: 15px;
  97. border-top: 1px solid rgba(255,255,255,0.2);
  98. flex-shrink: 0;
  99. display: flex;
  100. gap: 8px;
  101. flex-wrap: wrap;
  102. }
  103. .sidebar-actions button {
  104. flex: 1;
  105. min-width: 45px;
  106. padding: 8px 12px;
  107. font-size: 0.9rem;
  108. }
  109. /* Main Content */
  110. .main-content {
  111. flex: 1;
  112. display: flex;
  113. flex-direction: column;
  114. overflow: hidden;
  115. }
  116. .top-bar {
  117. background: white;
  118. border-bottom: 1px solid #e9ecef;
  119. padding: 15px 25px;
  120. display: flex;
  121. justify-content: space-between;
  122. align-items: center;
  123. flex-shrink: 0;
  124. box-shadow: 0 2px 4px rgba(0,0,0,0.05);
  125. flex-wrap: wrap;
  126. gap: 10px;
  127. }
  128. .top-bar h2 {
  129. margin: 0;
  130. color: #333;
  131. font-size: 1.5rem;
  132. }
  133. .top-bar-toggle {
  134. display: none;
  135. background: #667eea;
  136. color: white;
  137. border: none;
  138. padding: 8px 12px;
  139. border-radius: 5px;
  140. cursor: pointer;
  141. font-size: 1.2rem;
  142. }
  143. .content-area {
  144. flex: 1;
  145. overflow-y: auto;
  146. padding: 25px;
  147. overflow-x: hidden;
  148. }
  149. .empty-state {
  150. display: flex;
  151. flex-direction: column;
  152. justify-content: center;
  153. align-items: center;
  154. height: 100%;
  155. color: #999;
  156. text-align: center;
  157. padding: 20px;
  158. }
  159. .empty-state i {
  160. font-size: 4rem;
  161. margin-bottom: 20px;
  162. opacity: 0.5;
  163. }
  164. .vm-details {
  165. display: grid;
  166. grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  167. gap: 20px;
  168. }
  169. .card {
  170. border: none;
  171. border-radius: 10px;
  172. box-shadow: 0 2px 8px rgba(0,0,0,0.08);
  173. transition: all 0.3s ease;
  174. }
  175. .card:hover {
  176. box-shadow: 0 4px 12px rgba(0,0,0,0.12);
  177. }
  178. .card-header {
  179. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  180. color: white;
  181. border-radius: 10px 10px 0 0;
  182. padding: 15px;
  183. }
  184. .card-title {
  185. margin: 0;
  186. font-size: 1rem;
  187. }
  188. .card-body {
  189. padding: 15px;
  190. }
  191. .badge-running {
  192. background-color: #28a745;
  193. }
  194. .badge-off {
  195. background-color: #6c757d;
  196. }
  197. .badge-paused {
  198. background-color: #ffc107;
  199. color: #333;
  200. }
  201. .badge-error {
  202. background-color: #dc3545;
  203. }
  204. .info-item {
  205. padding: 12px 0;
  206. border-bottom: 1px solid #e9ecef;
  207. }
  208. .info-item:last-child {
  209. border-bottom: none;
  210. }
  211. .info-label {
  212. font-weight: 600;
  213. color: #667eea;
  214. font-size: 0.9rem;
  215. }
  216. .info-value {
  217. color: #333;
  218. margin-top: 3px;
  219. word-break: break-all;
  220. }
  221. /* Tabs */
  222. .nav-tabs {
  223. border-bottom: 2px solid #e9ecef;
  224. margin-bottom: 20px;
  225. flex-wrap: wrap;
  226. }
  227. .nav-tabs .nav-link {
  228. color: #667eea;
  229. border: none;
  230. border-bottom: 3px solid transparent;
  231. font-weight: 500;
  232. padding: 0.5rem 1rem;
  233. font-size: 0.95rem;
  234. }
  235. .nav-tabs .nav-link:hover {
  236. border-bottom-color: #667eea;
  237. color: #764ba2;
  238. }
  239. .nav-tabs .nav-link.active {
  240. color: #764ba2;
  241. border-bottom-color: #764ba2;
  242. background: none;
  243. }
  244. /* Actions */
  245. .actions-group {
  246. margin-top: 20px;
  247. padding-top: 20px;
  248. border-top: 2px solid #e9ecef;
  249. }
  250. .actions-group h5 {
  251. color: #667eea;
  252. margin-bottom: 15px;
  253. font-size: 1rem;
  254. }
  255. .btn-action {
  256. margin-right: 10px;
  257. margin-bottom: 10px;
  258. display: inline-block;
  259. }
  260. /* Modal adjustments */
  261. .modal-dialog {
  262. margin: 10px;
  263. }
  264. .modal-content {
  265. max-height: 90vh;
  266. overflow-y: auto;
  267. }
  268. /* Tablet */
  269. @media (max-width: 1024px) {
  270. .sidebar {
  271. width: 280px;
  272. }
  273. .top-bar {
  274. padding: 12px 15px;
  275. }
  276. .top-bar h2 {
  277. font-size: 1.3rem;
  278. }
  279. .content-area {
  280. padding: 15px;
  281. }
  282. .vm-details {
  283. grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  284. gap: 15px;
  285. }
  286. }
  287. /* Mobile */
  288. @media (max-width: 768px) {
  289. .dashboard-container {
  290. flex-direction: column;
  291. }
  292. .sidebar {
  293. width: 100%;
  294. height: auto;
  295. max-height: 0;
  296. overflow: hidden;
  297. position: fixed;
  298. top: 0;
  299. left: 0;
  300. z-index: 999;
  301. transition: max-height 0.3s ease;
  302. }
  303. .sidebar.show {
  304. max-height: calc(100vh - 50px);
  305. }
  306. .main-content {
  307. width: 100%;
  308. height: 100%;
  309. }
  310. .top-bar {
  311. padding: 10px 15px;
  312. order: -1;
  313. flex-wrap: nowrap;
  314. }
  315. .top-bar h2 {
  316. font-size: 1.2rem;
  317. min-width: auto;
  318. flex: 1;
  319. }
  320. .top-bar-toggle {
  321. display: block;
  322. margin-left: auto;
  323. }
  324. #topBarActions {
  325. width: 100%;
  326. margin-top: 10px;
  327. display: flex;
  328. gap: 8px;
  329. flex-wrap: wrap;
  330. }
  331. #topBarActions button {
  332. flex: 1;
  333. min-width: 70px;
  334. padding: 6px 8px;
  335. font-size: 0.85rem;
  336. }
  337. .content-area {
  338. padding: 15px 12px;
  339. max-width: 100%;
  340. }
  341. .empty-state {
  342. padding: 20px 10px;
  343. }
  344. .empty-state i {
  345. font-size: 3rem;
  346. margin-bottom: 15px;
  347. }
  348. .empty-state h5 {
  349. font-size: 1.1rem;
  350. }
  351. .empty-state p {
  352. font-size: 0.9rem;
  353. }
  354. .vm-details {
  355. grid-template-columns: 1fr;
  356. gap: 15px;
  357. }
  358. .card {
  359. border-radius: 8px;
  360. }
  361. .card-header {
  362. padding: 12px;
  363. }
  364. .card-body {
  365. padding: 12px;
  366. }
  367. .card-title {
  368. font-size: 0.95rem;
  369. }
  370. .info-label {
  371. font-size: 0.85rem;
  372. }
  373. .info-value {
  374. font-size: 0.9rem;
  375. }
  376. .nav-tabs .nav-link {
  377. padding: 0.4rem 0.8rem;
  378. font-size: 0.85rem;
  379. }
  380. .btn-action {
  381. width: 100%;
  382. margin-right: 0;
  383. margin-bottom: 8px;
  384. display: block;
  385. }
  386. .sidebar-actions button {
  387. padding: 6px 10px;
  388. font-size: 0.85rem;
  389. }
  390. .modal-dialog {
  391. margin: 5px;
  392. }
  393. .modal {
  394. --bs-modal-margin: 5px;
  395. }
  396. }
  397. /* Small phones */
  398. @media (max-width: 480px) {
  399. .top-bar h2 {
  400. font-size: 1rem;
  401. }
  402. .top-bar {
  403. padding: 8px 10px;
  404. }
  405. .content-area {
  406. padding: 10px 8px;
  407. }
  408. .vm-details {
  409. gap: 10px;
  410. }
  411. .card-header {
  412. padding: 10px;
  413. }
  414. .card-body {
  415. padding: 10px;
  416. }
  417. .info-item {
  418. padding: 8px 0;
  419. }
  420. .modal-dialog {
  421. margin: 0;
  422. width: 95vw;
  423. }
  424. #topBarActions {
  425. flex-direction: column;
  426. }
  427. #topBarActions button {
  428. width: 100%;
  429. }
  430. .btn-action {
  431. font-size: 0.8rem;
  432. padding: 6px;
  433. }
  434. .sidebar-header h4 {
  435. font-size: 1.2rem;
  436. }
  437. .sidebar-header p {
  438. font-size: 0.8rem;
  439. }
  440. .vm-item {
  441. padding: 10px 12px;
  442. font-size: 0.9rem;
  443. }
  444. .nav-tabs {
  445. margin-bottom: 15px;
  446. }
  447. .nav-tabs .nav-link {
  448. padding: 0.35rem 0.6rem;
  449. font-size: 0.8rem;
  450. }
  451. }
  452. </style>
  453. </head>
  454. <body>
  455. <div class="dashboard-container">
  456. <!-- Sidebar -->
  457. <div class="sidebar" id="sidebar">
  458. <div class="sidebar-header">
  459. <h4><i class="bi bi-display"></i> VirtManager</h4>
  460. <p>Virtual Machine Dashboard</p>
  461. </div>
  462. <div class="sidebar-vms" id="vmList">
  463. <div class="text-center mt-3" style="color: rgba(255,255,255,0.7);">
  464. <p>Cargando máquinas...</p>
  465. </div>
  466. </div>
  467. <div class="sidebar-actions">
  468. <button class="btn btn-light btn-sm" onclick="loadVMs()" title="Actualizar">
  469. <i class="bi bi-arrow-clockwise"></i>
  470. </button>
  471. <button class="btn btn-light btn-sm" onclick="openCreateVMModal()" title="Nueva VM">
  472. <i class="bi bi-plus-circle"></i> Nueva
  473. </button>
  474. <button class="btn btn-light btn-sm" onclick="openISOManagementModal()" title="Gestionar ISOs">
  475. <i class="bi bi-disc"></i> ISOs
  476. </button>
  477. </div>
  478. </div>
  479. <!-- Main Content -->
  480. <div class="main-content">
  481. <div class="top-bar">
  482. <h2 id="currentVMName">VirtManager</h2>
  483. <button class="top-bar-toggle" onclick="toggleSidebar()" title="Menú">
  484. <i class="bi bi-list"></i>
  485. </button>
  486. <div id="topBarActions"></div>
  487. </div>
  488. <div class="content-area" id="contentArea">
  489. <div class="empty-state">
  490. <i class="bi bi-inbox"></i>
  491. <h5>Selecciona una máquina virtual</h5>
  492. <p>Haz clic en una VM del panel lateral para ver sus detalles</p>
  493. </div>
  494. </div>
  495. </div>
  496. </div>
  497. <!-- Create VM Modal -->
  498. <div class="modal fade" id="createVMModal" tabindex="-1">
  499. <div class="modal-dialog">
  500. <div class="modal-content">
  501. <div class="modal-header">
  502. <h5 class="modal-title">Crear Nueva VM</h5>
  503. <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
  504. </div>
  505. <form id="createVMForm">
  506. <div class="modal-body">
  507. <div class="mb-3">
  508. <label for="name" class="form-label">Nombre</label>
  509. <input type="text" class="form-control" id="name" required>
  510. </div>
  511. <div class="mb-3">
  512. <label for="memory" class="form-label">Memoria (MiB)</label>
  513. <input type="number" class="form-control" id="memory" placeholder="2048" required>
  514. </div>
  515. <div class="mb-3">
  516. <label for="cpus" class="form-label">CPUs</label>
  517. <input type="number" class="form-control" id="cpus" value="1" min="1" required>
  518. </div>
  519. <div class="mb-3">
  520. <label for="disk_size" class="form-label">Tamaño de Disco (GB)</label>
  521. <input type="number" class="form-control" id="disk_size" placeholder="20" required>
  522. </div>
  523. <div class="mb-3">
  524. <label for="iso" class="form-label">Imagen ISO</label>
  525. <select class="form-control" id="iso" required>
  526. <option value="">Selecciona una ISO</option>
  527. </select>
  528. </div>
  529. </div>
  530. <div class="modal-footer">
  531. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
  532. <button type="submit" class="btn btn-primary">Crear VM</button>
  533. </div>
  534. </form>
  535. </div>
  536. </div>
  537. </div>
  538. <!-- ISO Management Modal -->
  539. <div class="modal fade" id="isoManagementModal" tabindex="-1">
  540. <div class="modal-dialog modal-lg">
  541. <div class="modal-content">
  542. <div class="modal-header">
  543. <h5 class="modal-title"><i class="bi bi-disc"></i> Gestionar Imágenes ISO</h5>
  544. <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
  545. </div>
  546. <div class="modal-body">
  547. <ul class="nav nav-tabs mb-3" role="tablist">
  548. <li class="nav-item">
  549. <a class="nav-link active" id="upload-tab" data-bs-toggle="tab" href="#upload-panel" role="tab">
  550. <i class="bi bi-upload"></i> Subir ISO
  551. </a>
  552. </li>
  553. <li class="nav-item">
  554. <a class="nav-link" id="manage-tab" data-bs-toggle="tab" href="#manage-panel" role="tab">
  555. <i class="bi bi-list-check"></i> Mis ISOs
  556. </a>
  557. </li>
  558. </ul>
  559. <div class="tab-content">
  560. <!-- Upload Tab -->
  561. <div class="tab-pane fade show active" id="upload-panel" role="tabpanel">
  562. <form id="uploadIsoForm" enctype="multipart/form-data">
  563. <div class="mb-3">
  564. <label for="isoFile" class="form-label">Selecciona archivo ISO</label>
  565. <input type="file" class="form-control" id="isoFile" accept=".iso" required>
  566. <small class="form-text text-muted">Máximo recomendado: 10GB</small>
  567. </div>
  568. <div id="progressContainer" style="display: none;">
  569. <label class="form-label">Progreso</label>
  570. <div class="progress">
  571. <div class="progress-bar" id="uploadProgress" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
  572. </div>
  573. </div>
  574. <button type="submit" class="btn btn-success" id="uploadBtn">
  575. <i class="bi bi-cloud-upload"></i> Subir ISO
  576. </button>
  577. </form>
  578. </div>
  579. <!-- Manage Tab -->
  580. <div class="tab-pane fade" id="manage-panel" role="tabpanel">
  581. <table class="table table-hover" id="isoTable">
  582. <thead>
  583. <tr>
  584. <th><i class="bi bi-disc"></i> Nombre</th>
  585. <th>Acciones</th>
  586. </tr>
  587. </thead>
  588. <tbody id="isoTableBody">
  589. <tr>
  590. <td colspan="2" class="text-center text-muted">Cargando ISOs...</td>
  591. </tr>
  592. </tbody>
  593. </table>
  594. </div>
  595. </div>
  596. </div>
  597. <div class="modal-footer">
  598. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
  599. </div>
  600. </div>
  601. </div>
  602. </div>
  603. <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
  604. <script src="/static/dashboard.js?v=7"></script>
  605. </body>
  606. </html>