index.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Dashboard | DezuKVM</title>
  6. <meta name="csrf_token" content="">
  7. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  8. <meta name="dezukvm.csrf.token" content="{{.csrfToken}}">
  9. <link rel="icon" type="image/png" href="/favicon.png">
  10. <script src="js/jquery-3.7.1.min.js"></script>
  11. <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" />
  12. <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>
  13. <style>
  14. body {
  15. margin: 0;
  16. height: 100vh;
  17. font-family: 'Segoe UI', Arial, sans-serif;
  18. background: #f9fafb;
  19. color: #222;
  20. }
  21. .main-layout {
  22. display: flex;
  23. height: 100vh;
  24. }
  25. .sidebar {
  26. width: 64px;
  27. background: #f4f5f7;
  28. color: #222;
  29. display: flex;
  30. flex-direction: column;
  31. justify-content: space-between;
  32. border-right: 1px solid #e0e1e2;
  33. }
  34. .sidebar .menu-top {
  35. padding: 1em 0 0 0;
  36. display: flex;
  37. flex-direction: column;
  38. align-items: center;
  39. }
  40. .sidebar .logo {
  41. width: 44px;
  42. height: 44px;
  43. margin-bottom: 32px;
  44. border-radius: 12px;
  45. display: flex;
  46. align-items: center;
  47. justify-content: center;
  48. overflow: hidden;
  49. }
  50. .sidebar .logo img {
  51. width: 48px;
  52. height: 48px;
  53. object-fit: contain;
  54. }
  55. .sidebar .menu-options {
  56. width: 100%;
  57. }
  58. .sidebar .menu-options .item {
  59. padding: 0.8em 0.4em;
  60. cursor: pointer;
  61. transition: background 0.2s;
  62. font-size: 1.1em;
  63. color: #222;
  64. text-align: center;
  65. }
  66. .sidebar .menu-options .item:hover,
  67. .sidebar .menu-options .item.active {
  68. background: #e9ecef; /* Lighter hover/active background */
  69. }
  70. .sidebar .menu-bottom {
  71. background: #2b2b2b; /* Lighter than sidebar */
  72. padding: 24px 0 24px 0;
  73. display: flex;
  74. flex-direction: column;
  75. align-items: center;
  76. }
  77. .sidebar .menu-bottom .item {
  78. color: #eee;
  79. padding: 0.8em 0.4em;
  80. width: 100%;
  81. text-align: center !important;
  82. cursor: pointer;
  83. border-radius: 6px;
  84. margin-bottom: 8px;
  85. transition: background 0.2s, color 0.2s;
  86. }
  87. .sidebar .menu-bottom .item:hover {
  88. background: #444;
  89. color: #fff;
  90. }
  91. .content {
  92. flex: 1;
  93. background: #f9fafb;
  94. overflow-y: hidden;
  95. }
  96. @media (prefers-color-scheme: dark) {
  97. body, .content {
  98. background: #181a1b;
  99. color: #eee;
  100. }
  101. .sidebar {
  102. background: #181818;
  103. color: #fff;
  104. }
  105. .sidebar .menu-bottom {
  106. background: #f0f1f3;
  107. }
  108. .sidebar .menu-options .item:hover,
  109. .sidebar .menu-options .item.active {
  110. background: #222;
  111. }
  112. }
  113. #session[contentfor="session"] {
  114. height: 100%;
  115. width: 100%;
  116. padding: 0;
  117. margin: 0;
  118. display: flex;
  119. flex-direction: column;
  120. }
  121. #sessionContext {
  122. border: none;
  123. width: 100%;
  124. height: 100%;
  125. display: block;
  126. }
  127. </style>
  128. </head>
  129. <body>
  130. <div class="main-layout">
  131. <nav class="sidebar">
  132. <div>
  133. <div class="menu-top">
  134. <div class="logo">
  135. <img src="img/logo.png" alt="Logo">
  136. </div>
  137. <div class="menu-options">
  138. <div class="active item" menu="instances"><i class="ui server icon"></i></div>
  139. <div class="item" menu="session"><i class="ui desktop icon"></i></div>
  140. <div class="item" menu="files"><i class="ui folder icon"></i></div>
  141. <div class="item" menu="power"><i class="ui plug icon"></i></div>
  142. </div>
  143. </div>
  144. </div>
  145. <div class="menu-bottom">
  146. <div class="item"><i class="cog icon"></i></div>
  147. <div class="item"><i class="sign out alternate icon"></i></div>
  148. </div>
  149. </nav>
  150. <main class="content">
  151. <div contentfor="instances" class="ui container" style="padding: 1rem; height: 100%; overflow-y: auto;">
  152. <br>
  153. <h2>Instances</h2>
  154. <div id="instanceList">
  155. <div class="ui active inverted dimmer">
  156. <div class="ui text loader">Loading Instances...</div>
  157. </div>
  158. </div>
  159. </div>
  160. <div id="session" contentfor="session">
  161. <iframe id="sessionContext" src="no_session.html"></iframe>
  162. </div>
  163. <div id="files" contentfor="files">
  164. <h2>File Management</h2>
  165. </div>
  166. <div id="power" contentfor="power">
  167. <h2>Power Controls</h2>
  168. </div>
  169. </main>
  170. </div>
  171. <script>
  172. let currentTab = 'instances';
  173. $(document).ready(function() {
  174. listInstances();
  175. });
  176. $('#sessionContext').on('load', function() {
  177. this.contentWindow.focus();
  178. });
  179. $('.sidebar .menu-options .item').on('click', function() {
  180. $('.sidebar .menu-options .item').removeClass('active');
  181. $(this).addClass('active');
  182. const menu = $(this).attr('menu');
  183. $('.content > [contentfor]').hide();
  184. $('.content > [contentfor="' + menu + '"]').show();
  185. currentTab = menu;
  186. if (menu === 'session') {
  187. $('#sessionContext').focus();
  188. }
  189. });
  190. $('.content > [contentfor]').hide();
  191. $('.content > [contentfor="instances"]').show();
  192. $(window).on('focus', function() {
  193. if (currentTab === 'session') {
  194. $('#sessionContext').focus();
  195. }
  196. });
  197. function renderInstance(instance) {
  198. return `
  199. <div class="ui segment" style="margin-bottom: 1em; position: relative;">
  200. <div class="ui header">
  201. Instance UUID: ${instance.uuid}
  202. </div>
  203. <div class="ui list">
  204. <div class="item"><strong>Video Device:</strong> ${instance.video_capture_dev}</div>
  205. <div class="item"><strong>Resolution:</strong> ${instance.video_resolution_width}x${instance.video_resolution_height}</div>
  206. <div class="item"><strong>Framerate:</strong> ${instance.video_framerate} fps</div>
  207. <div class="item"><strong>Audio Device:</strong> ${instance.audio_capture_dev}</div>
  208. <div class="item"><strong>Audio Channels:</strong> ${instance.audio_channels}</div>
  209. <div class="item"><strong>Audio Sample Rate:</strong> ${instance.audio_sample_rate} Hz</div>
  210. <div class="item"><strong>Aux MCU Device:</strong> ${instance.aux_mcu_device}</div>
  211. <div class="item"><strong>USB KVM Device:</strong> ${instance.usb_kvm_device}</div>
  212. <div class="item"><strong>USB Mass Storage Side:</strong> ${instance.usb_mass_storage_side}</div>
  213. <div class="item"><strong>Stream Info:</strong> ${instance.stream_info}</div>
  214. </div>
  215. <button class="ui primary button" style="position: absolute; bottom: 1em; right: 1em;"
  216. onclick="startSession('${instance.uuid}')">
  217. Launch Viewport
  218. </button>
  219. </div>
  220. `;
  221. }
  222. function connectToSession(sessionId, callback=undefined) {
  223. $('#sessionContext').attr('src', `/viewport.html#${sessionId}`);
  224. if (callback) callback();
  225. }
  226. function startSession(sessionId) {
  227. connectToSession(sessionId, function() {
  228. // Switch to session tab
  229. $('.sidebar .menu-options .item').removeClass('active');
  230. $('.sidebar .menu-options .item[menu="session"]').addClass('active');
  231. $('.content > [contentfor]').hide();
  232. $('.content > [contentfor="session"]').show();
  233. });
  234. }
  235. function listInstances() {
  236. $.get('/api/v1/instances', function(data) {
  237. let instances = [];
  238. try {
  239. instances = typeof data === 'string' ? JSON.parse(data) : data;
  240. } catch (e) {
  241. instances = [];
  242. }
  243. const $list = $('#instanceList');
  244. $list.empty();
  245. if (instances.length === 0) {
  246. $list.append('<div class="ui message">No instances found.</div>');
  247. return;
  248. }
  249. instances.forEach(function(instance) {
  250. $list.append(renderInstance(instance));
  251. });
  252. });
  253. }
  254. </script>
  255. </body>
  256. </html>