123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Dashboard | DezuKVM</title>
- <meta name="csrf_token" content="">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="dezukvm.csrf.token" content="{{.csrfToken}}">
- <link rel="icon" type="image/png" href="/favicon.png">
- <script src="js/jquery-3.7.1.min.js"></script>
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.4/semantic.min.css" integrity="sha512-ySrYzxj+EI1e9xj/kRYqeDL5l1wW0IWY8pzHNTIZ+vc1D3Z14UDNPbwup4yOUmlRemYjgUXsUZ/xvCQU2ThEAw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
- <script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.4/semantic.min.js" integrity="sha512-Y/wIVu+S+XJsDL7I+nL50kAVFLMqSdvuLqF2vMoRqiMkmvcqFjEpEgeu6Rx8tpZXKp77J8OUpMKy0m3jLYhbbw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-
- <style>
- body {
- margin: 0;
- height: 100vh;
- font-family: 'Segoe UI', Arial, sans-serif;
- background: #f9fafb;
- color: #222;
- }
- .main-layout {
- display: flex;
- height: 100vh;
- }
- .sidebar {
- width: 64px;
- background: #f4f5f7;
- color: #222;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- border-right: 1px solid #e0e1e2;
- }
- .sidebar .menu-top {
- padding: 1em 0 0 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .sidebar .logo {
- width: 44px;
- height: 44px;
- margin-bottom: 32px;
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- overflow: hidden;
- }
- .sidebar .logo img {
- width: 48px;
- height: 48px;
- object-fit: contain;
- }
- .sidebar .menu-options {
- width: 100%;
- }
- .sidebar .menu-options .item {
- padding: 0.8em 0.4em;
- cursor: pointer;
- transition: background 0.2s;
- font-size: 1.1em;
- color: #222;
- text-align: center;
- }
- .sidebar .menu-options .item:hover,
- .sidebar .menu-options .item.active {
- background: #e9ecef; /* Lighter hover/active background */
- }
- .sidebar .menu-bottom {
- background: #2b2b2b; /* Lighter than sidebar */
- padding: 24px 0 24px 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .sidebar .menu-bottom .item {
- color: #eee;
- padding: 0.8em 0.4em;
- width: 100%;
- text-align: center !important;
- cursor: pointer;
- border-radius: 6px;
- margin-bottom: 8px;
- transition: background 0.2s, color 0.2s;
- }
- .sidebar .menu-bottom .item:hover {
- background: #444;
- color: #fff;
- }
- .content {
- flex: 1;
- background: #f9fafb;
- overflow-y: hidden;
- }
- @media (prefers-color-scheme: dark) {
- body, .content {
- background: #181a1b;
- color: #eee;
- }
- .sidebar {
- background: #181818;
- color: #fff;
- }
- .sidebar .menu-bottom {
- background: #f0f1f3;
- }
- .sidebar .menu-options .item:hover,
- .sidebar .menu-options .item.active {
- background: #222;
- }
- }
- #session[contentfor="session"] {
- height: 100%;
- width: 100%;
- padding: 0;
- margin: 0;
- display: flex;
- flex-direction: column;
- }
- #sessionContext {
- border: none;
- width: 100%;
- height: 100%;
- display: block;
- }
- </style>
- </head>
- <body>
- <div class="main-layout">
- <nav class="sidebar">
- <div>
- <div class="menu-top">
- <div class="logo">
- <img src="img/logo.png" alt="Logo">
- </div>
- <div class="menu-options">
- <div class="active item" menu="instances"><i class="ui server icon"></i></div>
- <div class="item" menu="session"><i class="ui desktop icon"></i></div>
- <div class="item" menu="files"><i class="ui folder icon"></i></div>
- <div class="item" menu="power"><i class="ui plug icon"></i></div>
- </div>
- </div>
- </div>
- <div class="menu-bottom">
- <div class="item"><i class="cog icon"></i></div>
- <div class="item"><i class="sign out alternate icon"></i></div>
- </div>
- </nav>
- <main class="content">
- <div contentfor="instances" class="ui container" style="padding: 1rem; height: 100%; overflow-y: auto;">
- <br>
- <h2>Instances</h2>
- <div id="instanceList">
- <div class="ui active inverted dimmer">
- <div class="ui text loader">Loading Instances...</div>
- </div>
- </div>
- </div>
- <div id="session" contentfor="session">
- <iframe id="sessionContext" src="no_session.html"></iframe>
- </div>
- <div id="files" contentfor="files">
- <h2>File Management</h2>
- </div>
- <div id="power" contentfor="power">
- <h2>Power Controls</h2>
- </div>
- </main>
- </div>
- <script>
- let currentTab = 'instances';
- $(document).ready(function() {
- listInstances();
- });
- $('#sessionContext').on('load', function() {
- this.contentWindow.focus();
- });
- $('.sidebar .menu-options .item').on('click', function() {
- $('.sidebar .menu-options .item').removeClass('active');
- $(this).addClass('active');
- const menu = $(this).attr('menu');
- $('.content > [contentfor]').hide();
- $('.content > [contentfor="' + menu + '"]').show();
- currentTab = menu;
- if (menu === 'session') {
- $('#sessionContext').focus();
- }
- });
- $('.content > [contentfor]').hide();
- $('.content > [contentfor="instances"]').show();
- $(window).on('focus', function() {
- if (currentTab === 'session') {
- $('#sessionContext').focus();
- }
- });
- function renderInstance(instance) {
- return `
- <div class="ui segment" style="margin-bottom: 1em; position: relative;">
- <div class="ui header">
- Instance UUID: ${instance.uuid}
- </div>
- <div class="ui list">
- <div class="item"><strong>Video Device:</strong> ${instance.video_capture_dev}</div>
- <div class="item"><strong>Resolution:</strong> ${instance.video_resolution_width}x${instance.video_resolution_height}</div>
- <div class="item"><strong>Framerate:</strong> ${instance.video_framerate} fps</div>
- <div class="item"><strong>Audio Device:</strong> ${instance.audio_capture_dev}</div>
- <div class="item"><strong>Audio Channels:</strong> ${instance.audio_channels}</div>
- <div class="item"><strong>Audio Sample Rate:</strong> ${instance.audio_sample_rate} Hz</div>
- <div class="item"><strong>Aux MCU Device:</strong> ${instance.aux_mcu_device}</div>
- <div class="item"><strong>USB KVM Device:</strong> ${instance.usb_kvm_device}</div>
- <div class="item"><strong>USB Mass Storage Side:</strong> ${instance.usb_mass_storage_side}</div>
- <div class="item"><strong>Stream Info:</strong> ${instance.stream_info}</div>
- </div>
- <button class="ui primary button" style="position: absolute; bottom: 1em; right: 1em;"
- onclick="startSession('${instance.uuid}')">
- Launch Viewport
- </button>
- </div>
- `;
- }
- function connectToSession(sessionId, callback=undefined) {
- $('#sessionContext').attr('src', `/viewport.html#${sessionId}`);
- if (callback) callback();
- }
- function startSession(sessionId) {
- connectToSession(sessionId, function() {
- // Switch to session tab
- $('.sidebar .menu-options .item').removeClass('active');
- $('.sidebar .menu-options .item[menu="session"]').addClass('active');
- $('.content > [contentfor]').hide();
- $('.content > [contentfor="session"]').show();
- });
- }
- function listInstances() {
- $.get('/api/v1/instances', function(data) {
- let instances = [];
- try {
- instances = typeof data === 'string' ? JSON.parse(data) : data;
- } catch (e) {
- instances = [];
- }
- const $list = $('#instanceList');
- $list.empty();
- if (instances.length === 0) {
- $list.append('<div class="ui message">No instances found.</div>');
- return;
- }
- instances.forEach(function(instance) {
- $list.append(renderInstance(instance));
- });
- });
- }
- </script>
- </body>
- </html>
|