index.html 21 KB

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