mio dashboard docs.kosmonum.space/mio/
at master 714 lines 23 kB view raw
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>MIO Instances</title> 7 8 <style> 9 * { 10 margin: 0; 11 padding: 0; 12 box-sizing: border-box; 13 } 14 15 body { 16 font-family: system-ui,sans-serif; 17 background: linear-gradient(135deg, #4c566a 0%, #2e3440 100%); 18 min-height: 100vh; 19 padding: 20px; 20 } 21 22 /* Header Styles */ 23 .header { 24 background: #3b4252; 25 padding: 16px 24px; 26 border-radius: 8px; 27 display: flex; 28 align-items: center; 29 justify-content: space-between; 30 margin-bottom: 24px; 31 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); 32 border-left: 8px solid #88c0d0; 33 } 34 35 .header-left { 36 display: flex; 37 align-items: center; 38 gap: 12px; 39 } 40 41 .header-title { 42 font-size: 18px; 43 font-weight: 600; 44 color: #eceff4; 45 text-transform: uppercase; 46 } 47 48 .butterfly-icon { 49 font-size: 36px; 50 } 51 52 .header-right { 53 display: flex; 54 align-items: center; 55 gap: 12px; 56 margin-left: 12px; 57 } 58 59 .btn { 60 padding: 8px 12px; 61 border: none; 62 background: #4c566a; 63 color: #eceff4; 64 border-radius: 6px; 65 cursor: pointer; 66 font-size: 16px; 67 transition: all 0.2s ease; 68 display: flex; 69 align-items: center; 70 justify-content: center; 71 gap: 6px; 72 } 73 74 .btn:hover { 75 background: #5e81ac; 76 transform: translateY(-1px); 77 } 78 79 .btn-primary { 80 background: #4c566a; 81 font-weight: 500; 82 } 83 84 .btn-icon { 85 width: 36px; 86 height: 36px; 87 padding: 0; 88 display: flex; 89 align-items: center; 90 justify-content: center; 91 border-radius: 6px; 92 } 93 94 .avatar-btn { 95 border-radius: 50%; 96 background: linear-gradient(135deg, #81a1c1 0%, #5e81ac 100%); 97 color: #eceff4; 98 font-weight: 600; 99 position: relative; 100 cursor: pointer; 101 } 102 103 /* Dropdown Menus */ 104 .dropdown { 105 position: relative; 106 display: inline-block; 107 } 108 109 .dropdown-menu { 110 position: absolute; 111 top: 100%; 112 right: 0; 113 background: #3b4252; 114 border-radius: 6px; 115 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); 116 min-width: 160px; 117 z-index: 1000; 118 display: none; 119 margin-top: 4px; 120 overflow: hidden; 121 border: 1px solid #4c566a; 122 } 123 124 .dropdown-menu.active { 125 display: block; 126 } 127 128 .dropdown-item { 129 padding: 10px 16px; 130 display: flex; 131 align-items: center; 132 gap: 8px; 133 color: #eceff4; 134 cursor: pointer; 135 transition: background 0.2s ease; 136 border: none; 137 background: none; 138 width: 100%; 139 text-align: left; 140 font-size: 14px; 141 } 142 143 .dropdown-item:hover { 144 background: #4c566a; 145 } 146 147 .dropdown-item.danger { 148 color: #bf616a; 149 } 150 151 .dropdown-item.danger:hover { 152 background: #4c566a; 153 } 154 155 /* Instances Container */ 156 .instances-container { 157 max-width: 1200px; 158 margin: 0 auto; 159 } 160 161 /* Instance Card */ 162 .instance { 163 background: #3b4252; 164 padding: 16px 12px 16px 20px; 165 border-radius: 8px; 166 margin-bottom: 12px; 167 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); 168 transition: all 0.2s ease; 169 display: grid; 170 grid-template-columns: auto 1fr auto auto; 171 grid-template-rows: auto auto; 172 gap: 12px 12px; 173 align-items: start; 174 border-left: 8px solid #b48ead; 175 } 176 177 .instance:hover { 178 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); 179 background: #434c5e; 180 } 181 182 .instance.healthy { 183 border-left-color: #a3be8c; 184 } 185 186 .instance.warning { 187 border-left-color: #ebcb8b; 188 } 189 190 .instance.critical { 191 border-left-color: #bf616a; 192 } 193 194 @keyframes pulse { 195 0%, 100% { opacity: 1; } 196 50% { opacity: 0.5; } 197 } 198 199 /* First line: name + tags + menu */ 200 .instance-name { 201 font-weight: 500; 202 color: #eceff4; 203 display: flex; 204 align-items: center; 205 grid-column: 1; 206 grid-row: 1; 207 font-size: 14px; 208 letter-spacing: 0.5px; 209 height: 24px; 210 } 211 212 /* Tags container */ 213 .instance-tags { 214 display: flex; 215 gap: 8px; 216 align-items: center; 217 grid-column: 2; 218 grid-row: 1; 219 justify-self: end; 220 height: 24px; 221 } 222 223 .tag { 224 display: inline-flex; 225 align-items: center; 226 padding: 4px 8px; 227 background: #4c566a; 228 color: #88c0d0; 229 border-radius: 4px; 230 font-size: 11px; 231 font-weight: 500; 232 cursor: help; 233 transition: all 0.2s ease; 234 position: relative; 235 text-transform: uppercase; 236 border: 1px solid #5e81ac; 237 } 238 239 .tag:hover { 240 background: #5e81ac; 241 color: #eceff4; 242 } 243 244 /* Tag tooltip */ 245 .tag::before { 246 content: attr(data-label); 247 position: absolute; 248 bottom: -24px; 249 left: 50%; 250 transform: translateX(-50%); 251 background: #2e3440; 252 color: #88c0d0; 253 padding: 4px 8px; 254 border-radius: 4px; 255 font-size: 10px; 256 font-weight: 600; 257 white-space: nowrap; 258 opacity: 0; 259 pointer-events: none; 260 transition: opacity 0.2s ease; 261 z-index: 100; 262 border: 1px solid #4c566a; 263 } 264 265 .tag:hover::before { 266 opacity: 1; 267 } 268 269 /* Metrics on second line */ 270 .metrics { 271 display: flex; 272 gap: 24px; 273 grid-column: 1 / 3; 274 grid-row: 2; 275 } 276 277 .metric { 278 display: flex; 279 flex-direction: column; 280 gap: 4px; 281 flex: 1; 282 position: relative; 283 } 284 285 .metric-bar { 286 height: 8px; 287 background: #2e3440; 288 border-radius: 4px; 289 overflow: hidden; 290 cursor: pointer; 291 position: relative; 292 } 293 294 .metric-fill { 295 height: 100%; 296 border-radius: 4px; 297 transition: width 0.3s ease; 298 } 299 300 .metric-utilization .metric-fill { 301 background: linear-gradient(90deg, #81a1c1 0%, #5e81ac 100%); 302 } 303 304 .metric-saturation .metric-fill { 305 background: linear-gradient(90deg, #d08770 0%, #bf616a 100%); 306 } 307 308 .metric-error .metric-fill { 309 background: linear-gradient(90deg, #bf616a 0%, #a3414a 100%); 310 } 311 312 .metric-latency .metric-fill { 313 background: linear-gradient(90deg, #b48ead 0%, #a3429e 100%); 314 } 315 316 .metric-uptime .metric-fill { 317 background: linear-gradient(90deg, #a3be8c 0%, #8fb78a 100%); 318 } 319 320 /* Metric value tooltip */ 321 .metric-tooltip { 322 position: absolute; 323 bottom: -24px; 324 left: 50%; 325 transform: translateX(-50%); 326 background: #2e3440; 327 color: #88c0d0; 328 padding: 4px 8px; 329 border-radius: 4px; 330 font-size: 11px; 331 font-weight: 600; 332 white-space: nowrap; 333 opacity: 0; 334 pointer-events: none; 335 transition: opacity 0.2s ease; 336 z-index: 100; 337 border: 1px solid #4c566a; 338 } 339 340 .metric-bar:hover + .metric-tooltip, 341 .metric-tooltip:hover { 342 opacity: 1; 343 } 344 345 /* Instance Menu */ 346 .instance-menu { 347 flex-shrink: 0; 348 position: relative; 349 grid-column: 3; 350 grid-row: 1; 351 justify-self: end; 352 display: flex; 353 align-items: center; 354 height: 24px; 355 } 356 357 .menu-btn { 358 background: none; 359 border: 1px solid #4c566a; 360 font-size: 18px; 361 color: #81a1c1; 362 cursor: pointer; 363 padding: 4px 8px; 364 border-radius: 4px; 365 transition: all 0.2s ease; 366 display: flex; 367 align-items: center; 368 justify-content: center; 369 height: 24px; 370 } 371 372 .menu-btn:hover { 373 background: #4c566a; 374 color: #88c0d0; 375 } 376 377 /* Responsive Design */ 378 @media (max-width: 768px) { 379 .instance { 380 grid-template-columns: auto 1fr auto; 381 } 382 383 .instance-name { 384 font-size: 13px; 385 } 386 387 .instance-tags { 388 } 389 390 .metrics { 391 gap: 12px; 392 } 393 394 .metric-tooltip { 395 bottom: -28px; 396 } 397 } 398 399 @media (max-width: 480px) { 400 .header { 401 padding-left: 16px; 402 padding-right: 16px; 403 } 404 405 .header-right { 406 } 407 408 .header-title { 409 font-size: 16px; 410 } 411 412 .instance { 413 grid-template-columns: auto 1fr auto; 414 gap: 8px; 415 padding-left: 16px; 416 padding-right: 16px; 417 } 418 419 .instance-tags { 420 gap: 4px; 421 } 422 423 .metrics { 424 gap: 8px; 425 } 426 427 .tag { 428 font-size: 8px; 429 padding: 2px 4px; 430 } 431 } 432 </style> 433</head> 434<body> 435 <!-- Header --> 436 <div class="header"> 437 <div class="header-left"> 438 <span class="butterfly-icon">🦋</span> 439 <h1 class="header-title">MIO Instances</h1> 440 </div> 441 <div class="header-right"> 442 <button class="btn btn-primary btn-icon" onclick="showAddInstanceModal()" title="Add Instance"> 443444 </button> 445 <div class="dropdown"> 446 <button class="avatar-btn btn" onclick="toggleProfileMenu()">C</button> 447 <div class="dropdown-menu" id="profileMenu"> 448 <button class="dropdown-item">👤 Identity</button> 449 <button class="dropdown-item">🔑 Secrets</button> 450 <button class="dropdown-item danger">🚪 Logout</button> 451 </div> 452 </div> 453 </div> 454 </div> 455 456 <!-- Instances List --> 457 <div class="instances-container" id="instancesList"> 458 <!-- Instance 1 --> 459 <div class="instance critical"> 460 <div class="instance-name">netti</div> 461 <div class="instance-tags"> 462 <span class="tag" data-label="App">netbox</span> 463 <span class="tag" data-label="Location">us-east-1</span> 464 </div> 465 <div class="instance-menu dropdown"> 466 <button class="menu-btn" onclick="toggleInstanceMenu(this)"></button> 467 <div class="dropdown-menu"> 468 <button class="dropdown-item">💻 Terminal</button> 469 <button class="dropdown-item">💾 Backup</button> 470 <button class="dropdown-item">⬆️ Upgrade</button> 471 <button class="dropdown-item">🔄 Reboot</button> 472 <button class="dropdown-item danger">🗑️ Delete</button> 473 </div> 474 </div> 475 <div class="metrics"> 476 <div class="metric metric-utilization"> 477 <div class="metric-bar"> 478 <div class="metric-fill" style="width: 15%"></div> 479 </div> 480 <div class="metric-tooltip">Utilization: 15%</div> 481 </div> 482 <div class="metric metric-saturation"> 483 <div class="metric-bar"> 484 <div class="metric-fill" style="width: 32%"></div> 485 </div> 486 <div class="metric-tooltip">Saturation: 32%</div> 487 </div> 488 <div class="metric metric-error"> 489 <div class="metric-bar"> 490 <div class="metric-fill" style="width: 62%"></div> 491 </div> 492 <div class="metric-tooltip">Error Rate: 62%</div> 493 </div> 494 <div class="metric metric-latency"> 495 <div class="metric-bar"> 496 <div class="metric-fill" style="width: 45%"></div> 497 </div> 498 <div class="metric-tooltip">Latency: 120ms</div> 499 </div> 500 <div class="metric metric-uptime"> 501 <div class="metric-bar"> 502 <div class="metric-fill" style="width: 20.9%"></div> 503 </div> 504 <div class="metric-tooltip">Uptime: 20.9%</div> 505 </div> 506 </div> 507 </div> 508 509 <!-- Instance 2 --> 510 <div class="instance warning"> 511 <div class="instance-name">papi</div> 512 <div class="instance-tags"> 513 <span class="tag" data-label="App">paperless</span> 514 <span class="tag" data-label="Location">eu-west-1</span> 515 </div> 516 <div class="instance-menu dropdown"> 517 <button class="menu-btn" onclick="toggleInstanceMenu(this)"></button> 518 <div class="dropdown-menu"> 519 <button class="dropdown-item">💻 Terminal</button> 520 <button class="dropdown-item">💾 Backup</button> 521 <button class="dropdown-item">⬆️ Upgrade</button> 522 <button class="dropdown-item">🔄 Reboot</button> 523 <button class="dropdown-item danger">🗑️ Delete</button> 524 </div> 525 </div> 526 <div class="metrics"> 527 <div class="metric metric-utilization"> 528 <div class="metric-bar"> 529 <div class="metric-fill" style="width: 78%"></div> 530 </div> 531 <div class="metric-tooltip">Utilization: 78%</div> 532 </div> 533 <div class="metric metric-saturation"> 534 <div class="metric-bar"> 535 <div class="metric-fill" style="width: 62%"></div> 536 </div> 537 <div class="metric-tooltip">Saturation: 62%</div> 538 </div> 539 <div class="metric metric-error"> 540 <div class="metric-bar"> 541 <div class="metric-fill" style="width: 5%"></div> 542 </div> 543 <div class="metric-tooltip">Error Rate: 0.5%</div> 544 </div> 545 <div class="metric metric-latency"> 546 <div class="metric-bar"> 547 <div class="metric-fill" style="width: 72%"></div> 548 </div> 549 <div class="metric-tooltip">Latency: 245ms</div> 550 </div> 551 <div class="metric metric-uptime"> 552 <div class="metric-bar"> 553 <div class="metric-fill" style="width: 98.5%"></div> 554 </div> 555 <div class="metric-tooltip">Uptime: 98.5%</div> 556 </div> 557 </div> 558 </div> 559 560 <!-- Instance 3 --> 561 <div class="instance"> 562 <div class="instance-name">ci</div> 563 <div class="instance-tags"> 564 <span class="tag" data-label="App">buildbot</span> 565 <span class="tag" data-label="Location">ap-south-1</span> 566 </div> 567 <div class="instance-menu dropdown"> 568 <button class="menu-btn" onclick="toggleInstanceMenu(this)"></button> 569 <div class="dropdown-menu"> 570 <button class="dropdown-item">💻 Terminal</button> 571 <button class="dropdown-item">💾 Backup</button> 572 <button class="dropdown-item">⬆️ Upgrade</button> 573 <button class="dropdown-item">🔄 Reboot</button> 574 <button class="dropdown-item danger">🗑️ Delete</button> 575 </div> 576 </div> 577 <div class="metrics"> 578 <div class="metric metric-utilization"> 579 <div class="metric-bar"> 580 <div class="metric-fill" style="width: 92%"></div> 581 </div> 582 <div class="metric-tooltip">Utilization: 92%</div> 583 </div> 584 <div class="metric metric-saturation"> 585 <div class="metric-bar"> 586 <div class="metric-fill" style="width: 88%"></div> 587 </div> 588 <div class="metric-tooltip">Saturation: 88%</div> 589 </div> 590 <div class="metric metric-error"> 591 <div class="metric-bar"> 592 <div class="metric-fill" style="width: 18%"></div> 593 </div> 594 <div class="metric-tooltip">Error Rate: 1.8%</div> 595 </div> 596 <div class="metric metric-latency"> 597 <div class="metric-bar"> 598 <div class="metric-fill" style="width: 95%"></div> 599 </div> 600 <div class="metric-tooltip">Latency: 890ms</div> 601 </div> 602 <div class="metric metric-uptime"> 603 <div class="metric-bar"> 604 <div class="metric-fill" style="width: 85.2%"></div> 605 </div> 606 <div class="metric-tooltip">Uptime: 85.2%</div> 607 </div> 608 </div> 609 </div> 610 611 <!-- Instance 4 --> 612 <div class="instance healthy"> 613 <div class="instance-name">chat</div> 614 <div class="instance-tags"> 615 <span class="tag" data-label="App">irc</span> 616 <span class="tag" data-label="App">xmpp</span> 617 <span class="tag" data-label="Location">us-west-2</span> 618 </div> 619 <div class="instance-menu dropdown"> 620 <button class="menu-btn" onclick="toggleInstanceMenu(this)"></button> 621 <div class="dropdown-menu"> 622 <button class="dropdown-item">💻 Terminal</button> 623 <button class="dropdown-item">💾 Backup</button> 624 <button class="dropdown-item">⬆️ Upgrade</button> 625 <button class="dropdown-item">🔄 Reboot</button> 626 <button class="dropdown-item danger">🗑️ Delete</button> 627 </div> 628 </div> 629 <div class="metrics"> 630 <div class="metric metric-utilization"> 631 <div class="metric-bar"> 632 <div class="metric-fill" style="width: 54%"></div> 633 </div> 634 <div class="metric-tooltip">Utilization: 54%</div> 635 </div> 636 <div class="metric metric-saturation"> 637 <div class="metric-bar"> 638 <div class="metric-fill" style="width: 28%"></div> 639 </div> 640 <div class="metric-tooltip">Saturation: 28%</div> 641 </div> 642 <div class="metric metric-error"> 643 <div class="metric-bar"> 644 <div class="metric-fill" style="width: 1%"></div> 645 </div> 646 <div class="metric-tooltip">Error Rate: 0.1%</div> 647 </div> 648 <div class="metric metric-latency"> 649 <div class="metric-bar"> 650 <div class="metric-fill" style="width: 35%"></div> 651 </div> 652 <div class="metric-tooltip">Latency: 85ms</div> 653 </div> 654 <div class="metric metric-uptime"> 655 <div class="metric-bar"> 656 <div class="metric-fill" style="width: 99.98%"></div> 657 </div> 658 <div class="metric-tooltip">Uptime: 99.98%</div> 659 </div> 660 </div> 661 </div> 662 </div> 663 664 <script> 665 // Close all dropdowns when clicking outside 666 document.addEventListener('click', function(event) { 667 if (!event.target.closest('.dropdown')) { 668 document.querySelectorAll('.dropdown-menu').forEach(menu => { 669 menu.classList.remove('active'); 670 }); 671 } 672 }); 673 674 // Toggle profile menu 675 function toggleProfileMenu() { 676 const menu = document.getElementById('profileMenu'); 677 menu.classList.toggle('active'); 678 } 679 680 // Toggle instance menu 681 function toggleInstanceMenu(button) { 682 const menu = button.nextElementSibling; 683 684 // Close all other menus 685 document.querySelectorAll('.instance-menu .dropdown-menu').forEach(m => { 686 if (m !== menu) m.classList.remove('active'); 687 }); 688 689 menu.classList.toggle('active'); 690 } 691 692 // Add instance modal 693 function showAddInstanceModal() { 694 alert('Add new instance modal would open here'); 695 } 696 697 // Handle instance menu actions 698 document.querySelectorAll('.instance-menu .dropdown-item').forEach(item => { 699 item.addEventListener('click', function() { 700 const action = this.textContent.trim(); 701 console.log('Instance action:', action); 702 }); 703 }); 704 705 // Handle profile menu actions 706 document.querySelectorAll('#profileMenu .dropdown-item').forEach(item => { 707 item.addEventListener('click', function() { 708 const action = this.textContent.trim(); 709 console.log('Profile action:', action); 710 }); 711 }); 712 </script> 713</body> 714</html>