master
handlebars 23,671 lines 1.56 MB
Raw
Large file — syntax highlighting disabled.
1 <!DOCTYPE html>
2 <html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
3
4 <head>
5 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 <meta name="viewport" content="user-scalable=yes,initial-scale=1.0,minimum-scale=1.0" />
8 <meta name="apple-mobile-web-app-capable" content="yes" />
9 <meta name="format-detection" content="telephone=no" />
10 <meta name="robots" content="noindex,nofollow">
11 <link rel="manifest" href="{{{domainurl}}}manifest.json">
12 <link type="image/x-icon" href="{{{domainurl}}}favicon.ico" rel="shortcut icon" />
13 <link keeplink=1 type="text/css" href="styles/style-bootstrap.css" media="screen" rel="stylesheet" title="CSS" />
14 <link type="text/css" href="styles/ol.css" media="screen" rel="stylesheet" title="CSS" />
15 <link type="text/css" href="styles/ol3-contextmenu.min.css" media="screen" rel="stylesheet" title="CSS" />
16 <link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
17 <link type="text/css" href="styles/flatpickr.min.css" media="screen" rel="stylesheet" title="CSS">
18 <link id="theme-stylesheet" href="styles/bootstrap{{{min}}}.css" rel="stylesheet" title="CSS">
19 <link href="styles/select2.min.css" rel="stylesheet" title="CSS">
20 <link href="styles/select2-bootstrap-5-theme.min.css" rel="stylesheet" title="CSS">
21 {{{customCSSTags}}}
22 <link rel="apple-touch-icon" href="/favicon-303x303.png" />
23 <script type="text/javascript" src="scripts/common-0.0.1{{{min}}}.js"></script>
24 <script type="text/javascript" src="scripts/meshcentral{{{min}}}.js"></script>
25 <script type="text/javascript" src="scripts/amt-0.2.0{{{min}}}.js"></script>
26 <script type="text/javascript" src="scripts/amt-wsman-0.2.0{{{min}}}.js"></script>
27 <script type="text/javascript" src="scripts/amt-desktop-0.0.2{{{min}}}.js"></script>
28 <script type="text/javascript" src="scripts/amt-terminal-0.0.2{{{min}}}.js"></script>
29 <script type="text/javascript" src="scripts/zlib{{{min}}}.js"></script>
30 <script type="text/javascript" src="scripts/zlib-inflate{{{min}}}.js"></script>
31 <script type="text/javascript" src="scripts/zlib-adler32{{{min}}}.js"></script>
32 <script type="text/javascript" src="scripts/zlib-crc32{{{min}}}.js"></script>
33 <script type="text/javascript" src="scripts/amt-redir-ws-0.1.0{{{min}}}.js"></script>
34 <script type="text/javascript" src="scripts/amt-wsman-ws-0.2.0{{{min}}}.js"></script>
35 <script type="text/javascript" src="scripts/agent-redir-ws-0.1.1{{{min}}}.js"></script>
36 <script type="text/javascript" src="scripts/agent-redir-rtc-0.1.0{{{min}}}.js"></script>
37 <script type="text/javascript" src="scripts/agent-desktop-0.0.2{{{min}}}.js"></script>
38 <script type="text/javascript" src="scripts/agent-rdp-0.0.1{{{min}}}.js"></script>
39 <script type="text/javascript" src="scripts/qrcode.min.js"></script>
40 <script type="text/javascript" src="scripts/xterm{{{min}}}.js"></script>
41 <script type="text/javascript" src="scripts/xterm-addon-fit{{{min}}}.js"></script>
42 <script type="text/javascript" src="scripts/xterm-addon-image{{{min}}}.js"></script>
43 <script type="text/javascript" src="scripts/flatpickr.js"></script>
44 <script type="text/javascript" src="mstsc/mstsc.js"></script>
45 <script type="text/javascript" src="mstsc/keyboard.js"></script>
46 <script type="text/javascript" src="mstsc/rle.js"></script>
47 <script type="text/javascript" src="mstsc/client.js"></script>
48 <script type="text/javascript" src="mstsc/canvas.js"></script>
49 <script type="text/javascript" src="scripts/jquery{{{min}}}.js"></script>
50 <script type="text/javascript" src="scripts/bootstrap{{{min}}}.js"></script>
51 <script type="text/javascript" src="scripts/fontawesome/all{{{min}}}.js"></script>
52 <script type="text/javascript" src="scripts/select2.full.min.js"></script>
53 <script type="text/javascript" src="scripts/themes/theme-switcher.js"></script>
54 <script keeplink=1 type="text/javascript" src="scripts/u2f-api{{{min}}}.js"></script>
55 <script keeplink=1 type="text/javascript" src="scripts/charts{{{min}}}.js"></script>
56 <script keeplink=1 type="text/javascript" src="scripts/moment{{min}}.js"></script>
57 <script keeplink=1 type="text/javascript" src="scripts/chartjs-adapter-moment{{{min}}}.js"></script>
58 <script keeplink=1 type="text/javascript" src="scripts/filesaver.min.js"></script>
59 <script keeplink=1 type="text/javascript" src="scripts/ol{{{min}}}.js"></script>
60 <script keeplink=1 type="text/javascript" src="scripts/ol3-contextmenu{{{min}}}.js"></script>
61 <script keeplink=1 type="text/javascript" src="scripts/purify{{{min}}}.js"></script>
62 <script keeplink=1 type="text/javascript" src="scripts/marked{{{min}}}.js"></script>
63 <script type="text/javascript" src="js/ui-components.js"></script>
64 {{{customJSTags}}}
65 <title>{{{title}}}</title>
66 </head>
67
68 <body id="body" oncontextmenu="handleContextMenu(event)" style="display:none;min-width:495px"
69 onload="if (typeof(startup) !== 'undefined') startup();">
70 <script>
71 if (('ontouchstart' in window || navigator.maxTouchPoints > 0) && window.matchMedia('(pointer: coarse)').matches) {
72 document.body.classList.add('is-mobile');
73 document.documentElement.classList.add('is-mobile');
74 }
75 // Auto-fullscreen on landscape: when in the desktop view and connected,
76 // rotating to landscape triggers deskToggleFull() so the existing fulldesk
77 // CSS maximises the canvas without any new layout code.
78 // A flag tracks whether WE triggered fullscreen so we can auto-exit on
79 // rotating back to portrait (but not if the user exited fullscreen manually).
80 var _mobileAutoFullscreen = false;
81 var _mobileSavedAspectRatio = 0;
82 var _mobileZoomMode = 'fit'; // 'native' = 1:1 scrollable | 'fit' = fill viewport
83 var _nativePinchStartDist = 0, _nativePinchStartW = 0, _nativePinchStartH = 0;
84 var _nativePinchMidX = 0, _nativePinchMidY = 0;
85 var _prevHadFulldesk = false;
86
87 // Switch between native 1:1 (scrollable, full precision) and fit-to-viewport.
88 // In native mode we suspend desktop.m.onScreenSizeChange (= deskAdjust) so
89 // it can't override the explicit pixel dimensions we set on the canvas.
90 function _mobileSetZoom(mode) {
91 _mobileZoomMode = mode;
92 var desk = document.getElementById('Desk');
93 var dp = document.getElementById('DeskParent');
94 if (!desk || !dp) return;
95 var dm = (typeof desktop !== 'undefined') && desktop && desktop.m;
96 if (mode === 'native') {
97 // CSS custom properties lock canvas at native resolution.
98 // Panning is handled by dtouchmove JS (scrollLeft/Top on deskarea3x).
99 // Touch listeners stay active so trackpad mode keeps working.
100 var w = (dm && dm.ScreenWidth) || desk.width || 640;
101 var h = (dm && dm.ScreenHeight) || desk.height || 480;
102 document.documentElement.style.setProperty('--native-desk-w', w + 'px');
103 document.documentElement.style.setProperty('--native-desk-h', h + 'px');
104 document.body.classList.add('native-zoom');
105 if (dm && !dm._savedOnScreenSizeChange) {
106 dm._savedOnScreenSizeChange = dm.onScreenSizeChange;
107 dm.onScreenSizeChange = function(obj, sw, sh) {
108 document.documentElement.style.setProperty('--native-desk-w', sw + 'px');
109 document.documentElement.style.setProperty('--native-desk-h', sh + 'px');
110 };
111 }
112 } else {
113 document.body.classList.remove('native-zoom');
114 if (dm && dm._savedOnScreenSizeChange) {
115 dm.onScreenSizeChange = dm._savedOnScreenSizeChange;
116 delete dm._savedOnScreenSizeChange;
117 }
118 desk.style.width = '100%';
119 desk.style.height = '100%';
120 if (typeof deskAdjust === 'function') deskAdjust();
121 }
122 }
123
124 // Watch body class for fulldesk being added/removed and apply the right zoom
125 if (document.body.classList.contains('is-mobile')) {
126 new MutationObserver(function(muts) {
127 muts.forEach(function(m) {
128 if (m.attributeName !== 'class') return;
129 var inFull = document.body.classList.contains('fulldesk');
130 if (inFull && !_prevHadFulldesk) {
131 // Just entered fullscreen: portrait->native, landscape->fit
132 setTimeout(function() {
133 var landscape = window.matchMedia('(orientation: landscape)').matches;
134 _mobileSetZoom(landscape ? 'fit' : 'native');
135 }, 200);
136 } else if (!inFull && _prevHadFulldesk) {
137 _mobileSetZoom('fit'); // always reset to fit on exit
138 }
139 _prevHadFulldesk = inFull;
140 });
141 }).observe(document.body, { attributes: true });
142 }
143 function _doOrientationUpdate() {
144 if (!document.body.classList.contains('is-mobile')) return;
145 var landscape = window.matchMedia('(orientation: landscape)').matches;
146 document.body.classList.toggle('is-landscape', landscape);
147 // Check DOM state — JS variables like xxcurrentView / fullscreen are
148 // in a later function scope and not accessible from this early script.
149 var p11 = document.getElementById('p11');
150 var inDesktop = p11 && p11.style.display !== 'none';
151 var alreadyFullscreen = document.body.classList.contains('fulldesk');
152 if (landscape && inDesktop && !alreadyFullscreen) {
153 // Auto-enter fullscreen in landscape using the global function
154 if (typeof deskToggleFull === 'function') {
155 _mobileAutoFullscreen = true;
156 _mobileSavedAspectRatio = (typeof deskAspectRatio !== 'undefined') ? deskAspectRatio : 0;
157 deskToggleFull();
158 }
159 // CSS rule body.is-mobile.is-landscape.fulldesk #Desk { width/height: 100% !important }
160 // handles the canvas fill automatically — no JS override needed here.
161 } else if (!landscape && alreadyFullscreen && _mobileAutoFullscreen) {
162 _mobileAutoFullscreen = false;
163 if (typeof deskToggleFull === 'function') deskToggleFull();
164 // Let deskAdjust() recalculate naturally for portrait (no forced CSS override)
165 setTimeout(function() { if (typeof deskAdjust === 'function') deskAdjust(); }, 400);
166 } else {
167 // Not entering/exiting fullscreen but still call deskAdjust so the
168 // canvas recalculates for the new viewport dimensions
169 if (typeof deskAdjust === 'function') {
170 requestAnimationFrame(function() { deskAdjust(); });
171 }
172 }
173 }
174 function _updateOrientation() {
175 setTimeout(_doOrientationUpdate, 300);
176 }
177 window.addEventListener('orientationchange', _updateOrientation);
178 window.addEventListener('resize', _updateOrientation);
179
180 // Use Popper strategy:'fixed' so dropdown menus escape overflow:hidden parents and canvas layers.
181 window.addEventListener('load', function() {
182 if (typeof bootstrap === 'undefined') return;
183 document.querySelectorAll('[data-bs-toggle="dropdown"]').forEach(function(el) {
184 var ex = bootstrap.Dropdown.getInstance(el);
185 if (ex) ex.dispose();
186 new bootstrap.Dropdown(el, {
187 popperConfig: function(cfg) { cfg.strategy = 'fixed'; return cfg; }
188 });
189 });
190 });
191 </script>
192 <!-- right click menu -->
193 <div id="contextMenu" class="contextMenu noselect" style="display:none">
194 <div id="cxinfo" class="cmtext" onclick="cmaction(1,event)"><b>Information</b></div>
195 <div id="cxdesktop" class="cmtext" onclick="cmaction(3,event)">Desktop</div>
196 <div id="cxterminal" class="cmtext" onclick="cmaction(2,event)">Terminal</div>
197 <div id="cxfiles" class="cmtext" onclick="cmaction(4,event)">Files</div>
198 <div id="cxevents" class="cmtext" onclick="cmaction(5,event)">Events</div>
199 <div id="cxdetails" class="cmtext" onclick="cmaction(6,event)">Details</div>
200 <div id="cxconsole" class="cmtext" onclick="cmaction(7,event)">Console</div>
201 <div id="cxwebrdp" class="cmtext" onclick="cmaction(11,event)">Web-RDP</div>
202 <div id="cxwebvnc" class="cmtext" onclick="cmaction(12,event)">Web-VNC</div>
203 <div id="cxwebssh" class="cmtext" onclick="cmaction(13,event)">Web-SSH</div>
204 <div id="cxrdp" class="cmtext" onclick="cmaction(14,event)">RDP</div>
205 <div id="cxplugins" class="cmtext" onclick="cmaction(8,event)" style="display:none">Plugins</div>
206 <hr id="cxmgroupsplit" />
207 <div id="cxstar" class="cmtext" onclick="cmaction(10,event)" style="display:none">Toggle Star</div>
208 <div id="cxmdesktop" class="cmtext" onclick="cmaction(9,event)" style="display:none">Multi-Desktop</div>
209 </div>
210 <div id="meshContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
211 <div id="cxselectall" class="cmtext" onclick="cmmeshaction(1,event)">Select All</div>
212 <div id="cxselectnone" class="cmtext" onclick="cmmeshaction(2,event)">Select None</div>
213 <!--
214 <hr id="cxmgroupsplit2" style="display:none" />
215 <div id="cxmmdesktop" class="cmtext" style="display:none" onclick="cmmeshaction(3,event)">Multi-Desktop</div>
216 -->
217 </div>
218 <div id="altPortContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
219 <div class="cmtext" onclick="cmaltportaction(1,event)">Alternate Port</div>
220 </div>
221 <div id="sshPortContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
222 <div class="cmtext" onclick="cmsshportaction(1,event)">Alternate Port</div>
223 </div>
224 <div id="rfbPortContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
225 <div class="cmtext" onclick="cmrfbportaction(1,event)">Alternate Port</div>
226 </div>
227 <div id="httpPortContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
228 <div class="cmtext" onclick="cmhttpportaction(1,event)">Alternate Port</div>
229 </div>
230 <div id="httpsPortContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
231 <div class="cmtext" onclick="cmhttpsportaction(1,event)">Alternate Port</div>
232 </div>
233 <div id="filesContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
234 <div class="cmtext" onclick="cmfilesaction(1,event)">Rename</div>
235 <div class="cmtext" onclick="cmfilesaction(2,event)">Edit</div>
236 <div class="cmtext" onclick="cmfilesaction(3,event)">Delete</div>
237 </div>
238 <div id="expandAllContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
239 <div class="cmtext" onclick="cmexpandaction(1,event)">Expand All</div>
240 <div class="cmtext" onclick="cmexpandaction(2,event)">Collapse All</div>
241 </div>
242 <div id="deskPlayerContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
243 <div class="cmtext" onclick="cmdeskplayeraction(1,event)">Open Player...</div>
244 </div>
245 <div id="deskKeyShortcutContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
246 <div class="cmtext" onclick="cmdeskshortcutaction(1,event)">Customize...</div>
247 </div>
248 <div id="deskPreConfigShortcutContextMenu" class="contextMenu noselect"
249 style="display:none;min-width:0px;max-height:calc(80% - 50px);overflow-y:auto">
250 <span id="deskPreConfigShortcutContextMenu1"></span>
251 <span id="deskPreConfigShortcutContextMenu2"></span>
252 <div class="cmtext" onclick="cmdeskpreconfigtypeaction(-1,event)">Customize...</div>
253 </div>
254 <div id="deskPreConfigScriptContextMenu" class="contextMenu noselect"
255 style="display:none;min-width:0px;max-height:calc(80% - 50px);overflow-y:auto">
256 <span id="deskPreConfigScriptContextMenu1"></span>
257 <span id="deskPreConfigScriptContextMenu2"></span>
258 </div>
259 <!--
260 <div id="pluginTabContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
261 <div id="cxclose" class="cmtext" onclick="pluginTabClose(event)">Close Tab</div>
262 </div>
263 -->
264 <!-- main page -->
265 <div id=container>
266 <div id="notifiyBox" class="notifiyBox" style="display:none"></div>
267 <div id="masthead" class="noselect masthead-container">
268 <div class="masthead-left">
269 <div class="masthead-title" onclick="go(1,event)">{{{titlehtml}}}</div>
270 <div class="masthead-titles-container">
271 <div class="title" onclick="go(1,event)">{{{title1}}}</div>
272 <div class="title2" onclick="go(1,event)">{{{title2}}}</div>
273 </div>
274 </div>
275
276 <div class="masthead-right">
277 <div id="notificationCount"
278 class="notification-icon position-relative"
279 title="Click to view current notifications"
280 onclick="clickNotificationIcon()"
281 style="display: none;">
282 <i class="fa-solid fa-bell fa-xl text-white"></i>
283 <span id="notificationBadge"
284 class="position-absolute top-1 start-100 translate-middle badge rounded-pill bg-danger">0</span>
285 </div>
286
287 <div class="user-menu-container">
288 <div id="userDropdown">
289 <div id="userDropdownButton">
290 <img id="userDropdownImage" src="images/user-256.png" alt="User" />
291 <span id="userDropdownName" class="LogoffLinkColor"></span>
292 <i class="fa fa-chevron-down LogoffLinkColor" style="font-size: 12px;"></i>
293 </div>
294 <div id="userDropdownMenu">
295 <div id="userDropdownMenuContainer">
296 <div class="userDropdownMenuItem userDropdownMobileOnly mobile-menu-item" data-action="devices" onclick="handleUserMenuItem('devices')">
297 <i class="fa fa-desktop userDropdownMenuIcon"></i>
298 <span>My Devices</span>
299 </div>
300
301 <div class="userDropdownMenuItem userDropdownMobileOnly mobile-menu-item" data-action="events" onclick="handleUserMenuItem('events')">
302 <i class="fa fa-calendar userDropdownMenuIcon"></i>
303 <span>My Events</span>
304 </div>
305
306 <div id="mobileUsersMenuItem" class="userDropdownMenuItem userDropdownMobileOnly users-menu-item mobile-menu-item d-none" data-action="users" onclick="handleUserMenuItem('users')">
307 <i class="fa fa-users userDropdownMenuIcon"></i>
308 <span>My Users</span>
309 </div>
310
311 <div id="mobileFilesMenuItem" class="userDropdownMenuItem userDropdownMobileOnly files-menu-item mobile-menu-item d-none" data-action="files" onclick="handleUserMenuItem('files')">
312 <i class="fa fa-folder userDropdownMenuIcon"></i>
313 <span>My Files</span>
314 </div>
315
316 <div id="mobileServerMenuItem" class="userDropdownMenuItem userDropdownMobileOnly server-menu-item mobile-menu-item d-none" data-action="server" onclick="handleUserMenuItem('server')">
317 <i class="fa fa-server userDropdownMenuIcon"></i>
318 <span>My Server</span>
319 </div>
320
321 <div id="userDropdownMenuDivider" class="userDropdownMobileOnly mobile-menu-item"></div>
322
323 <div class="userDropdownMenuItem userDropdownUISettings" onclick="toggleUISubmenu(); event.stopPropagation();">
324 <i class="fa fa-sliders userDropdownMenuIcon"></i>
325 <span>UI Settings</span>
326 <i class="fa fa-chevron-right userDropdownMenuIcon submenu-arrow"></i>
327 </div>
328
329 <div id="uiSubmenu" class="userDropdownSubmenu">
330 <div id="toggleModernUIMenuItem" class="userDropdownMenuItem" data-action="toggle-modern-ui" onclick="handleUserMenuItem('toggle-modern-ui')">
331 <i class="fa fa-toggle-on userDropdownMenuIcon"></i>
332 <span>Toggle Modern UI</span>
333 </div>
334 <div class="userDropdownMenuItem" data-action="left-bar" onclick="handleUserMenuItem('left-bar')">
335 <i class="fa fa-window-maximize fa-rotate-270 userDropdownMenuIcon"></i>
336 <span>Left Bar Interface</span>
337 </div>
338 <div class="userDropdownMenuItem" data-action="top-bar" onclick="handleUserMenuItem('top-bar')">
339 <i class="fa fa-window-maximize userDropdownMenuIcon"></i>
340 <span>Top Bar Interface</span>
341 </div>
342 <div class="userDropdownMenuItem" data-action="fixed-width" onclick="handleUserMenuItem('fixed-width')">
343 <i class="fa fa-columns userDropdownMenuIcon"></i>
344 <span>Fixed Width Interface</span>
345 </div>
346 <div id="toggleFooterMenuItem" class="userDropdownMenuItem" data-action="toggle-footer" onclick="handleUserMenuItem('toggle-footer')">
347 <i class="fa fa-window-minimize userDropdownMenuIcon"></i>
348 <span>Toggle Footer Bar</span>
349 </div>
350 <div id="userDropdownMenuDivider" class="userDropdownMobileOnly"></div>
351 </div>
352
353 <div id="toggleNightMenuItem" class="userDropdownMenuItem" data-action="toggle-night" onclick="toggleNightMode(); QV('userDropdownMenu', false); QV('uiSubmenu', false); resetChevronArrow();">
354 <i id="nightModeIcon" class="fa userDropdownMenuIcon night-mode-icon"></i>
355 <span id="nightModeText">Toggle night mode</span>
356 </div>
357
358
359 <div class="userDropdownMenuItem" data-action="notes" onclick="handleUserMenuItem('notes')">
360 <i class="fa fa-sticky-note userDropdownMenuIcon"></i>
361 <span>Personal Notes</span>
362 </div>
363
364 <div class="userDropdownMenuItem" data-action="account" onclick="handleUserMenuItem('account')">
365 <i class="fa fa-user userDropdownMenuIcon"></i>
366 <span>My Account</span>
367 </div>
368
369 <div id="userDropdownMenuDivider"></div>
370
371 <div class="userDropdownMenuItem" data-action="logout" onclick="handleUserMenuItem('logout')">
372 <i class="fa fa-sign-out userDropdownMenuIcon"></i>
373 <span>Logout</span>
374 </div>
375 </div>
376 </div>
377 </div>
378 <span id="idleTimeoutNotify" class="idle-timeout-notify"></span>
379 </div>
380 </div>
381 </div>
382 <div class="sidebar flex-column" id="page_leftbar">
383 <div style="height:24px"></div>
384 <nav class="nav flex-column">
385 <a class="nav-link active text-center text-white lbbuttonsel" id="LeftMenuMyDevices" href="#" data-target="general" onclick=go(1,event) onkeypress="if (event.key=='Enter') { go(1); }"><i class="fa-solid fa-computer me-2"></i></a>
386 <a class="nav-link text-center text-white" id="LeftMenuMyAccount" href="#" data-target="account" onclick=go(2,event) onkeypress="if (event.key=='Enter') { go(2); }"><i class="fa-solid fa-user-gear me-2"></i></a>
387 <a class="nav-link text-center text-white" id="LeftMenuMyEvents" href="#" data-target="events" onclick=go(3,event) onkeypress="if (event.key=='Enter') { go(3); }"><i class="fa-solid fa-calendar-alt me-2"></i></a>
388 <a class="nav-link text-center text-white" id="LeftMenuMyFiles" href="#" data-target="files" onclick=go(5,event) onkeypress="if (event.key=='Enter') { go(5); }"><i class="fa-solid fa-folder-open me-2"></i></a>
389 <a class="nav-link text-center text-white" id="LeftMenuMyUsers" href="#" data-target="users" onclick=go(4,event) onkeypress="if (event.key=='Enter') { go(4); }"><i class="fa-solid fa-users me-2"></i></a>
390 <a class="nav-link text-center text-white" id="LeftMenuMyServer" href="#" data-target="server" onclick=go(6,event) onkeypress="if (event.key=='Enter') { go(6); }"><i class="fa-solid fa-server me-2"></i></a>
391 </nav>
392 </div>
393 <div id=topbar class=noselect>
394 <div>
395 <div style="position:relative">
396 <span id=logoutControlSpan2></span>
397 <table id=MainMenuSpan cellpadding=0 cellspacing=0 class=style1>
398 <tr>
399 <td tabindex=0 id=MainMenuMyDevices class="topbar_td style3x" onmouseup=go(1,event)
400 onkeypress="if (event.key == 'Enter') go(1)">My Devices</td>
401 <td tabindex=0 id=MainMenuMyAccount class="topbar_td style3x" onmouseup=go(2,event)
402 onkeypress="if (event.key == 'Enter') go(2)">My Account</td>
403 <td tabindex=0 id=MainMenuMyEvents class="topbar_td style3x" onmouseup=go(3,event)
404 onkeypress="if (event.key == 'Enter') go(3)">My Events</td>
405 <td tabindex=0 id=MainMenuMyFiles class="topbar_td style3x" onmouseup=go(5,event)
406 onkeypress="if (event.key == 'Enter') go(5)">My Files</td>
407 <td tabindex=0 id=MainMenuMyUsers class="topbar_td style3x" onmouseup=go(4,event)
408 onkeypress="if (event.key == 'Enter') go(4)">My Users</td>
409 <td tabindex=0 id=MainMenuMyServer class="topbar_td style3x" onmouseup=go(6,event)
410 onkeypress="if (event.key == 'Enter') go(6)">My Server</td>
411 <td class="topbar_td_end style3">&nbsp;</td>
412 </tr>
413 </table>
414 <div id=MainSubMenuSpan style="display:none">
415 <table id=MainSubMenu cellpadding=0 cellspacing=0 class=style1>
416 <tr>
417 <td tabindex=0 id=MainDev class="topbar_td style3x" onmouseup=go(10,event)
418 onkeypress="if (event.key == 'Enter') go(10)">General</td>
419 <td tabindex=0 id=MainDevDesktop class="topbar_td style3x" onmouseup=go(11,event)
420 onkeypress="if (event.key == 'Enter') go(11)">Desktop</td>
421 <td tabindex=0 id=MainDevTerminal class="topbar_td style3x" onmouseup=go(12,event)
422 onkeypress="if (event.key == 'Enter') go(12)">Terminal</td>
423 <td tabindex=0 id=MainDevFiles class="topbar_td style3x" onmouseup=go(13,event)
424 onkeypress="if (event.key == 'Enter') go(13)">Files</td>
425 <td tabindex=0 id=MainDevEvents class="topbar_td style3x" onmouseup=go(16,event)
426 onkeypress="if (event.key == 'Enter') go(16)">Events</td>
427 <td tabindex=0 id=MainDevInfo class="topbar_td style3x" onmouseup=go(17,event)
428 onkeypress="if (event.key == 'Enter') go(17)">Details</td>
429 <td tabindex=0 id=MainDevApps class="topbar_td style3x" onmouseup=go(18,event)
430 onkeypress="if (event.key == 'Enter') go(18)">Software</td>
431 <td tabindex=0 id=MainDevAmt class="topbar_td style3x" onmouseup=go(14,event)
432 onkeypress="if (event.key == 'Enter') go(14)">Intel&reg;AMT</td>
433 <td tabindex=0 id=MainDevConsole class="topbar_td style3x" onmouseup=go(15,event)
434 onkeypress="if (event.key == 'Enter') go(15)">Console</td>
435 <td tabindex=0 id=MainDevPlugins class="topbar_td style3x" onmouseup=go(19,event)
436 onkeypress="if (event.key == 'Enter') go(19)">Plugins</td>
437 <td class="topbar_td_end style3">&nbsp;</td>
438 </tr>
439 </table>
440 </div>
441 <div id=MeshSubMenuSpan style="display:none">
442 <table id=MeshSubMenu cellpadding=0 cellspacing=0 class=style1>
443 <tr>
444 <td tabindex=0 id=MeshGeneral class="topbar_td style3x" onmouseup=go(20,event)
445 onkeypress="if (event.key == 'Enter') go(20)">General</td>
446 <td tabindex=0 id=MeshSummary class="topbar_td style3x" onmouseup=go(21,event)
447 onkeypress="if (event.key == 'Enter') go(21)">Summary</td>
448 <td class="topbar_td_end style3">&nbsp;</td>
449 </tr>
450 </table>
451 </div>
452 <div id=EventsSubMenuSpan style="display:none">
453 <table id=EventsSubMenu cellpadding=0 cellspacing=0 class=style1>
454 <tr>
455 <td tabindex=0 id=EventsLive class="topbar_td style3x" onmouseup=go(3,event)
456 onkeypress="if (event.key == 'Enter') go(3)">Events</td>
457 <td tabindex=0 id=EventsReport class="topbar_td style3x" onmouseup=go(60,event)
458 onkeypress="if (event.key == 'Enter') go(60)">Reports</td>
459 <td class="topbar_td_end style3">&nbsp;</td>
460 </tr>
461 </table>
462 </div>
463 <div id=UserSubMenuSpan style="display:none">
464 <table id=UserSubMenu cellpadding=0 cellspacing=0 class=style1>
465 <tr>
466 <td tabindex=0 id=UserGeneral class="topbar_td style3x" onmouseup=go(30,event)
467 onkeypress="if (event.key == 'Enter') go(30)">General</td>
468 <td tabindex=0 id=UserEvents class="topbar_td style3x" onmouseup=go(31,event)
469 onkeypress="if (event.key == 'Enter') go(31)">Events</td>
470 <td class="topbar_td_end style3">&nbsp;</td>
471 </tr>
472 </table>
473 </div>
474 <div id=UsersSubMenuSpan style="display:none">
475 <table id=UsersSubMenu cellpadding=0 cellspacing=0 class=style1>
476 <tr>
477 <td tabindex=0 id=UsersGeneral class="topbar_td style3x" onmouseup=go(4,event)
478 onkeypress="if (event.key == 'Enter') go(4)">Users</td>
479 <td tabindex=0 id=UsersGroups class="topbar_td style3x" onmouseup=go(50,event)
480 onkeypress="if (event.key == 'Enter') go(50)">Groups</td>
481 <td tabindex=0 id=UsersRecordings class="topbar_td style3x" onmouseup=go(52,event)
482 onkeypress="if (event.key == 'Enter') go(52)">Recordings</td>
483 <td class="topbar_td_end style3">&nbsp;</td>
484 </tr>
485 </table>
486 </div>
487 <div id=ServerSubMenuSpan style="display:none">
488 <table id=ServerSubMenu cellpadding=0 cellspacing=0 class=style1>
489 <tr>
490 <td tabindex=0 id=ServerGeneral class="topbar_td style3x" onmouseup=go(6,event)
491 onkeypress="if (event.key == 'Enter') go(6)">General</td>
492 <td tabindex=0 id=ServerStats class="topbar_td style3x" onmouseup=go(40,event)
493 onkeypress="if (event.key == 'Enter') go(40)">Stats</td>
494 <td tabindex=0 id=ServerConsole class="topbar_td style3x" onmouseup=go(115,event)
495 onkeypress="if (event.key == 'Enter') go(115)">Console</td>
496 <td tabindex=0 id=ServerTrace class="topbar_td style3x" onmouseup=go(41,event)
497 onkeypress="if (event.key == 'Enter') go(41)">Trace</td>
498 <td tabindex=0 id=ServerPlugins class="topbar_td style3x" onmouseup=go(42,event)
499 onkeypress="if (event.key == 'Enter') go(42)">Plugins</td>
500 <td class="topbar_td_end style3">&nbsp;</td>
501 </tr>
502 </table>
503 </div>
504 </div>
505 </div>
506 </div>
507 <div id="column_l" class='pt-3'>
508 <div id=p0 style="display:none">
509 <div id=p0message><span id=p0span>Server disconnected</span>, <href onclick=reload()
510 style=cursor:pointer><u>click to reconnect</u></href>.</div>
511 </div>
512 <div id=p1 style="display:none">
513 <div id="p1title" class="d-flex justify-content-between align-items-center">
514 <div class="fs-4 fw-bold">My Devices</div>
515 <div style="display:none" id="devListToolbarViewIcons">
516 <div id=devViewPageState style="float:left;line-height:32px;height:32px;font-size:16px;display:none"></div>
517 <div tabindex=0 id=devViewPageButton1 class=viewSelector onclick=onDeviceViewPageChange(1)
518 onkeypress="if (event.key == 'Enter') { onDeviceViewPageChange(1); }"
519 title="Go to first page" style="display:none">
520 <div class="viewSelector9"></div>
521 </div>
522 <div tabindex=0 id=devViewPageButton2 class=viewSelector onclick=onDeviceViewPageChange(2)
523 onkeypress="if (event.key == 'Enter') { onDeviceViewPageChange(2); }"
524 title="Go to previous page" style="display:none">
525 <div class="viewSelector10"></div>
526 </div>
527 <div tabindex=0 id=devViewPageButton3 class=viewSelector onclick=onDeviceViewPageChange(3)
528 onkeypress="if (event.key == 'Enter') { onDeviceViewPageChange(3); }"
529 title="Go to next page" style="display:none">
530 <div class="viewSelector11"></div>
531 </div>
532 <div tabindex=0 id=devViewPageButton4 class=viewSelector onclick=onDeviceViewPageChange(4)
533 onkeypress="if (event.key == 'Enter') { onDeviceViewPageChange(4); }"
534 title="Go to last page" style="display:none;margin-right:12px">
535 <div class="viewSelector8"></div>
536 </div>
537 <span role=button tabindex=0 id=devViewButton1 class="fa-layers fa-fw fa-xl" onclick=onDeviceViewChange(1) onkeypress="if (event.key == 'Enter') { onDeviceViewChange(1); }" title="Columns">
538 <i class="fa-solid fa-bars" data-fa-transform="shrink-8 right-4 up-2"></i>
539 <i class="fa-solid fa-bars" data-fa-transform="shrink-8 left-4 up-2"></i>
540 <i class="fa-solid fa-bars" data-fa-transform="shrink-8 right-4 down-3"></i>
541 <i class="fa-solid fa-bars" data-fa-transform="shrink-8 left-4 down-3"></i>
542 </span>
543 <span role=button tabindex=0 id=devViewButton2 class="fa-layers fa-fw fa-xl" onclick=onDeviceViewChange(2) onkeypress="if (event.key == 'Enter') { onDeviceViewChange(2); }" title="List">
544 <i class="fa-solid fa-bars"></i>
545 </span>
546 <span role=button tabindex=0 id=devViewButton3 class="fa-layers fa-fw fa-xl" onclick=onDeviceViewChange(3) onkeypress="if (event.key == 'Enter') { onDeviceViewChange(3); }" title="Desktops">
547 <i class="fa-solid fa-square" data-fa-transform="shrink-9 up-4 left-4"></i>
548 <i class="fa-solid fa-square" data-fa-transform="shrink-9 up-4 right-4"></i>
549 <i class="fa-solid fa-square" data-fa-transform="shrink-9 down-4 left-4"></i>
550 <i class="fa-solid fa-square" data-fa-transform="shrink-9 down-4 right-4"></i>
551 </span>
552 <span role=button tabindex=0 id=devViewButton5 class="fa-layers fa-fw fa-xl" onclick=onDeviceViewChange(5) onkeypress="if (event.key == 'Enter') { onDeviceViewChange(5); }" title="Desktops">
553 <i class="fa-solid fa-square" data-fa-transform="shrink-10 up-4 left-6"></i>
554 <i class="fa-solid fa-square" data-fa-transform="shrink-10 up-4 right-1"></i>
555 <i class="fa-solid fa-square" data-fa-transform="shrink-10 up-4 right-5"></i>
556 <i class="fa-solid fa-square" data-fa-transform="shrink-10 down-4 left-1"></i>
557 <i class="fa-solid fa-square" data-fa-transform="shrink-10 down-4 left-5"></i>
558 <i class="fa-solid fa-square" data-fa-transform="shrink-10 down-4 right-6"></i>
559 </span>
560 <span role=button tabindex=0 id=devViewButton4 class="fa-layers fa-fw fa-xl" onclick=onDeviceViewChange(4) onkeypress="if (event.key == 'Enter') { onDeviceViewChange(4); }" title="Map" style="display:none">
561 <i class="fa-solid fa-map-location-dot"></i>
562 </span>
563 </div>
564 </div>
565 <div id="devListToolbarSpan" class="noselect d-flex align-items-center p-1">
566 <div id=devListToolbar class="d-flex align-items-center visually-hidden"></td>
567 <button class="btn btn-secondary btn-sm fa-layers fa-fw me-1" style="width: 30px; height: 31px; display:none" title="Collapse All" id="CollapseAllButton" onclick="cmexpandaction(2)">
568 <i class="fa-solid fa-chevron-up" data-fa-transform="down-6"></i>
569 <i class="fa-solid fa-chevron-down" data-fa-transform="up-6"></i>
570 </button>
571 <button class="btn btn-secondary btn-sm fa-layers fa-fw me-1" style="width: 30px; height: 31px; display:none" title="Expand All" id="ExpandAllButton" onclick="cmexpandaction(1)">
572 <i class="fa-solid fa-chevron-up" data-fa-transform="up-6"></i>
573 <i class="fa-solid fa-chevron-down" data-fa-transform="down-6"></i>
574 </button>
575 <input type=button class="btn btn-secondary me-1 btn-sm" id=SelectAllButton onclick="selectallButtonFunction();" value="Select All" />
576 <input type=button class="btn btn-primary me-1 btn-sm" id=GroupActionButton disabled="disabled" value="Group Action" onclick=groupActionFunction() />
577 <input type=button class="btn btn-secondary me-1 btn-sm" id=ScrollToTopButton onclick="onDevicesScroll(true);" value="Scroll To Top" />
578 <input type="text" id=SearchInput class="form-control-sm me-1 btn-sm" placeholder="Filter" onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) onfocus=onSearchFocus(1) onblur=onSearchFocus(0) title="Filter devices by name or prefix: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefix with ! to negate, e.g. !tag:abc">
579 <span id=SearchInputClearButton style="display:none;position:relative">
580 <span class="fa-layers fa-fw" role="button" onclick="clearDeviceSearch()" style="position:absolute;left:-25px;top:-15px">
581 <i class="fa-solid fa-circle" style="color:var(--bs-secondary-bg)"></i>
582 <i class="fa-solid fa-xmark" style="color:var(--bs-body-color)"></i>
583 </span>
584 </span>
585 <select class="form-select-sm me-1" id=DevFilterSelect onchange=onOnlineCheckBox(event) title="Device Filter">
586 <option value=0 selected>All</option>
587 <option value=1>Online</option>
588 <option value=5>Offline</option>
589 <option value=2>Sessions</option>
590 <option value=3>Starred</option>
591 <option value=4>Intel&reg; AMT</option>
592 <option value=6>Help</option>
593 <option value=7>Tagged</option>
594 <option value=8>Untagged</option>
595 </select>
596 <div class="form-check" title="Show devices operating system name"><input class="form-check-input me-1" type=checkbox id=RealNameCheckBox onclick=onRealNameCheckBox() /><label class="form-check-label" for="RealNameCheckBox">OS Name</label></div>
597 <label style="display:none"><input type=checkbox id=OnlineCheckBox class="form-check-input me-2" onclick=onOnlineCheckBox(event) /><span title="Only show devices that are online">Online</span></label>
598 <span id="devsCustomUIBar"></span>
599 </div>
600 <div id=kvmListToolbar class="d-flex align-items-center style14 visually-hidden">
601 <button class="btn btn-secondary btn-sm fa-layers fa-fw me-1" style="width: 30px; height: 31px; display:none" title="Collapse All" id="CollapseAllButton2" onclick="cmexpandaction(2)">
602 <i class="fa-solid fa-chevron-up" data-fa-transform="down-6"></i>
603 <i class="fa-solid fa-chevron-down" data-fa-transform="up-6"></i>
604 </button>
605 <button class="btn btn-secondary btn-sm fa-layers fa-fw me-1" style="width: 30px; height: 31px; display:none" title="Expand All" id="ExpandAllButton2" onclick="cmexpandaction(1)">
606 <i class="fa-solid fa-chevron-up" data-fa-transform="up-6"></i>
607 <i class="fa-solid fa-chevron-down" data-fa-transform="down-6"></i>
608 </button>
609 <span id="KvmDesktopButtons">
610 <input type="button" class="btn btn-success me-1 btn-sm" onclick="connectAllKvmFunction()" value="Connect All" />
611 <input type="button" class="btn btn-danger me-1 btn-sm" onclick="disconnectAllKvmFunction()" value="Disconnect All" />
612 <div class="form-check form-check-inline me-1 align-middle" title="Automatic connect">
613 <input class="form-check-input me-1" type=checkbox id="autoConnectDesktopCheckbox" onclick="autoConnectDesktops(event)" />
614 <label class="form-check-label" for="autoConnectDesktopCheckbox">Auto</label>
615 </div>
616 </span>
617 <input type="button" class="btn btn-secondary me-1 btn-sm" onclick="onDevicesScroll(true);" value="Scroll To Top" />
618 <input type="button" class="btn btn-primary me-1 btn-sm" onclick="showMultiDesktopSettings()" value="Settings" />
619 <input type="text" id=KvmSearchInput class="form-control-sm me-1" placeholder="Filter" onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) onfocus=onSearchFocus(1) onblur=onSearchFocus(0) title="Filter devices by name or prefix: user: (u:) ip: group: (g:) tag: (t:) atag: (a:) os: amt: desc: wsc:ok/noav/noupdate/nofirewall/any connectivity: (c:) - Prefix with ! to negate, e.g. !tag:abc">
620 <span id=KvmSearchInputClearButton style="display:none;position:relative">
621 <span class="fa-layers fa-fw" role="button" onclick="clearDeviceSearch()" style="position:absolute;left:-25px;top:-15px">
622 <i class="fa-solid fa-circle" style="color:var(--bs-secondary-bg)"></i>
623 <i class="fa-solid fa-xmark" style="color:var(--bs-body-color)"></i>
624 </span>
625 </span>
626 </div>
627 <div id=devMapToolbar class="d-flex align-items-center style14 visually-hidden">
628 <input class="form-control-sm me-1" type=search id=mapSearchLocation placeholder="Search Location" onfocus=onMapSearchFocus(1) onblur=onMapSearchFocus(0) />
629 <input class="btn btn-primary me-1 btn-sm" type=button value=Search title="Search for location" onclick=getSearchLocation() />
630 <input class="btn btn-secondary me-1 btn-sm" type=button id=refreshmap title="Reset map view" value=Reset onclick=refreshMap(false,true) />
631 </div>
632 <div class="d-flex align-items-center ms-auto">
633 <div style="display:none" id=devListToolbarView>
634 View
635 <select id=viewselect onchange=onDeviceViewChange()>
636 <option value=1>Columns</option>
637 <option value=2>List</option>
638 <option value=3>Desktops Fixed Width</option>
639 <option id=viewselectmapoption value=4 style="display:none">Map</option>
640 <option value=5>Desktops</option>
641 </select>
642 </div>
643 <div style="display:none" id=devListToolbarSize>
644 Size
645 <select id=sizeselect class="form-select-sm me-2" onchange=onDeviceViewChange()>
646 <option value=0>Small</option>
647 <option value=1>Medium</option>
648 <option value=2>Large</option>
649 </select>
650 &nbsp;
651 </div>
652 <div style="display:none" id=devListToolbarSort>
653 Sort
654 <select id=sortselect class="form-select-sm me-2" onchange=mainUpdate(6)>
655 <option>Group</option>
656 <option>Power</option>
657 <option>Device</option>
658 <option>Tags</option>
659 <option>Group-Tags</option>
660 <option>Last Seen</option>
661 <option>Last Boot Up Time</option>
662 </select>
663 &nbsp;
664 </div>
665 <i class="fa-solid fa-gear" role="button" id=devListToolbarSettings onclick="onDeviceViewSettings()"></i>
666 </div>
667 </div>
668 <div id=NoMeshesPanel style="display:none">
669 <table>
670 <tr>
671 <td valign="top" style="width:50px">
672 <img src="images/info.png" width="47" height="48" />
673 </td>
674 <td>
675 <div id="getStarted1">To get started, <a href=# onclick="return account_createMesh()"><strong>click here to create a device group</strong></a>.</div>
676 <div id="getStarted2">No device groups.</div>
677 </td>
678 </tr>
679 </table>
680 </div>
681 <div id="xdevices" class="noselect" style="display:none" onscroll="onDevicesScroll(false)"></div>
682 <div id="xdevicesmap" style="display:none">
683 <div id=xmapSearchResultsDlg style="display:none">
684 <div id=xmapSearchResultsBck>
685 <div id=xmapSearchClose onclick=mapCloseSearchWindow()><b>X</b></div>
686 <div style=padding:5px>Location Results</div>
687 <div style=width:100%;margin:6px></div>
688 </div>
689 <div id=xmapSearchResults style=margin:6px></div>
690 </div>
691 </div>
692 <div id="xmap-info-window"></div>
693 </div>
694 <div id=p2 style="display:none">
695 <div id="p2title">
696 <h1>My Account</h1>
697 </div>
698 <div id="p2info" style="overflow-y:auto">
699 <div id="p2AccountImageFrame">
700 <img id="p2AccountImage" alt="" width="128" height="128"
701 onclick="account_manageImage(0)" src="images/user-256.png" />
702 <div><a href=# onclick="account_manageImage(0)">Change image</a></div>
703 </div>
704 <div id="p2AccountSecurity" style="display:none">
705 <p><strong>Account security</strong></p>
706 <div style="margin-left:25px">
707 <div id="managePhoneNumber1">
708 <div class="p2AccountActions"><span
709 id="authPhoneNumberCheck"><strong>&#x2713;</strong></span></div><span><a href=#
710 onclick="return account_managePhone()">Manage phone number</a><br /></span>
711 </div>
712 <div id="manageEmail2FA">
713 <div class="p2AccountActions"><span
714 id="authEmailSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=#
715 onclick="return account_manageAuthEmail()">Manage email authentication</a><br /></span>
716 </div>
717 <div id="manageAuthApp">
718 <div class="p2AccountActions"><span
719 id="authAppSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=#
720 onclick="return account_manageAuthApp()">Manage authenticator app</a><br /></span>
721 </div>
722 <div id="manageDuoApp">
723 <div class="p2AccountActions"><span
724 id="authDuoSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=#
725 onclick="return account_manageAuthDuo()">Manage Duo authentication</a><br /></span>
726 </div>
727 <div id="manageHardwareOtp">
728 <div class="p2AccountActions"><span
729 id="authKeySetupCheck"><strong>&#x2713;</strong></span></div><span><a href=#
730 onclick="return account_manageHardwareOtp(0)">Manage security keys</a><br /></span>
731 </div>
732 <div id="managePushAuthDev">
733 <div class="p2AccountActions"><span
734 id="authPushAuthDevCheck"><strong>&#x2713;</strong></span></div><span><a href=#
735 onclick="return account_managePushAuthDev()">Manage push authentication</a><br /></span>
736 </div>
737 <div id="manageMessaging1">
738 <div class="p2AccountActions"><span
739 id="authMessagingCheck"><strong>&#x2713;</strong></span></div><span><a href=#
740 onclick="return account_manageMessaging()">Manage messaging</a><br /></span>
741 </div>
742 <div id="manageOtp">
743 <div class="p2AccountActions"><span
744 id="authCodesSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=#
745 onclick="return account_manageOtp(0)">Manage backup codes</a><br /></span>
746 </div>
747 <div class="p2AccountActions"></div><span><a href=#
748 onclick="return account_viewPreviousLogins()">View previous logins</a><br /></span>
749 </div>
750 </div>
751 <div id="p2AccountActions">
752 <p><strong>Account actions</strong></p>
753 <p class="mL">
754 <span id="managePhoneNumber2" style="display:none"><a href=# onclick="return account_managePhone()">Manage phone number</a><br /></span>
755 <span id="manageMessaging2" style="display:none"><a href=# onclick="return account_manageMessaging()">Manage messaging</a><br /></span>
756 <span id="verifyEmailId" style="display:none"><a href=# onclick="return account_showVerifyEmail()">Verify email</a><br /></span>
757 <span id="accountEnableNotificationsSpan" style="display:none"><a href=# onclick="return account_enableNotifications()">Enable web notifications</a><br /></span>
758 <a href=# onclick="return account_showLocalizationSettings()">Localization Settings</a><br />
759 <a href=# onclick="return account_showAccountNotifySettings()">Notification Settings</a><br />
760 <span id="p2AccountPassActions">
761 <span id="accountChangeEmailAddressSpan" style="display:none"><a href=# onclick="return account_showChangeEmail()">Change email address</a><br /></span>
762 <a href=# onclick="return account_showChangePassword()">Change password</a><span id="p2nextPasswordUpdateTime"></span><br />
763 <a href=# onclick="return account_showDeleteAccount()">Delete account</a><br />
764 </span>
765 <span id="accountCreateLoginTokenSpan" style="display:none"><a href=# onclick="return account_createLoginToken()">Create login token</a><br /></span>
766 <a href=# onclick="return account_showThemesSwitcher()">Switch theme</a><br />
767 <span id="accountCustomIconsSpan" style="display:none"><a href=# onclick="return showIconCustomization()">Icons Customization</a><br /></span>
768 </p>
769 </div>
770 <div id=p2logintokens></div>
771 <strong>Device Groups</strong>
772 <span id="p2createMeshLink1"> - <button class="btn btn-primary btn-sm" onclick="return account_createMesh()"><i class="fa-fw fa-solid fa-circle-plus"></i> New</button></span>
773 <br /><br />
774 <div id=p2meshes></div>
775 <div id=p2noMeshFound style="display:none">No device groups.<span id="p2createMeshLink2"> <a href=# onclick="return account_createMesh()"><strong>Get started here!</strong></a></span>
776 </div>
777 <br style=clear:both />
778 </div>
779 </div>
780 <div id=p3 style="display:none">
781 <div id="p3title" class="d-flex align-items-center">
782 <div class="fs-4 fw-bold">My Events</div>
783 </div>
784 <table class="pTable">
785 <tr>
786 <td class="auto-style1 d-flex justify-content-end p-1">
787 <div class="d-flex align-items-center">
788 <label for="p3filterevents" class="me-1">Filter</label>
789 <select id=p3filterevents class="form-select-sm me-2" onchange=refreshEvents()>
790 <option notransval=1 value="">All Logs</option>
791 <option notransval=1 value=agentlog>Agent Logs</option>
792 <option notransval=1 value=relaylog>Relay Logs</option>
793 <option notransval=1 value=manual>Manual Logs</option>
794 <option notransval=1 value=runcommands>Run Command Logs</option>
795 <option notransval=1 value=batchupload>Batch Upload Logs</option>
796 <option notransval=1 value=changenode>Change Node Logs</option>
797 <option notransval=1 value=removenode>Remove Node Logs</option>
798 </select>
799 <label for="p3limitdropdown" class="me-1">Show</label>
800 <select id=p3limitdropdown class="form-select-sm me-2" onchange=refreshEvents()>
801 <option notransval=1 value=60>Last 60</option>
802 <option notransval=1 value=120>Last 120</option>
803 <option notransval=1 value=250>Last 250</option>
804 <option notransval=1 value=500>Last 500</option>
805 <option notransval=1 value=1000>Last 1000</option>
806 <option notransval=1 value="">No limit</option>
807 </select>
808 <a href=# onclick=p3showDownloadEventsDialog(2)><i class="fa-solid fa-download" title="Download Events"></i></a>
809 </div>
810 </td>
811 </tr>
812 </table>
813 <div id=p3events></div>
814 </div>
815 <div id=p4 style="display:none">
816 <div id="p4title" class="d-flex align-items-center">
817 <div class="fs-4 fw-bold">My Users</div>
818 </div>
819 <table class="pTable">
820 <tr>
821 <td id="p4toolbarActions" class="style14 d-flex align-items-center flex-wrap p-1">
822 <div class="d-flex align-items-center">
823 <input type=button id=UsersSelectAllButton class="btn btn-secondary btn-sm me-1" onclick="p3usersSelectallButtonFunction()" value="Select All" />
824 <input type=button id=UsersGroupActionButton class="btn btn-primary btn-sm me-1" disabled="disabled" value="Group Action" onclick=p3usersGroupActionFunction() />
825 <input id=UserNewAccountButton class="btn btn-success btn-sm me-1" type=button onclick=showCreateNewAccountDialog() value="New Account..." />
826 <div class="btn-group me-1 mobileActionDropdown">
827 <button type="button" class="btn btn-secondary btn-sm" data-bs-toggle="dropdown" aria-expanded="false">More&nbsp;<i class="fa-solid fa-chevron-down fa-xs"></i></button>
828 <div class="dropdown-menu">
829 <button type="button" class="dropdown-item" onclick="showUserBroadcastDialog()">Broadcast</button>
830 <button type="button" class="dropdown-item" onclick="p4downloadUserInfo()">Download user information</button>
831 <button type="button" id=p4MobileUserBatchCreate class="dropdown-item" style="display:none" onclick="p4batchAccountCreate()">Batch create accounts</button>
832 <button type="button" class="dropdown-item" onclick="onUsersViewSettings()">Settings</button>
833 </div>
834 </div>
835 <div class="d-inline-flex align-items-center">
836 <input id=UserSearchInput type=search class="form-control-sm me-1" placeholder=Filter onchange=onUserSearchInputChanged() onkeyup=onUserSearchInputChanged() autocomplete=off onfocus=onUserSearchFocus(1) onblur=onUserSearchFocus(0) />
837 </div>
838 </div>
839 <div class="d-flex align-items-center ms-auto desktopAction">
840 <input type=button class="btn btn-primary btn-sm me-2"onclick=showUserBroadcastDialog() style=margin-right:6px value="Broadcast" />
841 <a href=# onclick=p4downloadUserInfo()><i role=button class="fa-solid fa-download me-2" title="Download user information"></i></a>
842 <a href=# onclick=p4batchAccountCreate()><i role=button id=p4UserBatchCreate style="display:none" title="Batch create many user accounts" class="fa-solid fa-upload me-1"></i></a>
843 <i style="cursor:pointer;margin-top:3px;margin-left:6px" id=usersListToolbarSettings class="fa-solid fa-cog" title="Settings" onclick="onUsersViewSettings()"></i>
844 </div>
845 </td>
846 </tr>
847 </table>
848 <div id="p3users"></div>
849 </div>
850 <div id=p5 style="display:none">
851 <div id="p5title" class="d-flex align-items-center">
852 <div class="fs-4 fw-bold">My Files</div>
853 </div>
854 <table id="p5toolbar" class="table" cellpadding="0" cellspacing="0">
855 <tr>
856 <td id="p5filehead" valign=bottom class="d-flex align-items-center">
857 <div id="p5rightOfButtons" class="d-flex align-items-center"></div>
858 <div class="m-1 d-flex align-items-center">
859 <input type=button id=p5FolderUp class="btn btn-secondary btn-sm me-1" disabled="disabled" onclick="return p5folderup();" value="Up" />
860 <input type=button id=p5SelectAllButton class="btn btn-secondary btn-sm me-1" disabled="disabled" onclick="p5selectallfile();" value="Select All" />
861 <input type=button id=p5RenameFileButton class="btn btn-primary btn-sm me-1" disabled="disabled" value="Rename" onclick="p5renamefile();" />
862 <input type=button id=p5DeleteFileButton class="btn btn-primary btn-sm me-1" disabled="disabled" value="Delete" onclick="p5deletefile();" />
863 <input type=button id=p5ViewFileButton class="btn btn-primary btn-sm me-1" disabled="disabled" value="Edit" onclick="p5viewfile()" />
864 <input type=button id=p5NewFolderButton class="btn btn-primary btn-sm me-1 desktopAction" disabled="disabled" value="New Folder" onclick="p5createfolder();" />
865 <input type=button id=p5UploadButton class="btn btn-primary btn-sm me-1 desktopAction" disabled="disabled" value="Upload" onclick="p5uploadFile()" />
866 <input type=button id=p5DownloadButton class="btn btn-primary btn-sm me-1 desktopAction" disabled="disabled" value="Download" onclick="p5downloadButton()" />
867 <input type=button id=p5CutButton class="btn btn-primary btn-sm me-1 desktopAction" disabled="disabled" value="Cut" onclick="p5copyFile(1)" />
868 <input type=button id=p5CopyButton class="btn btn-primary btn-sm me-1 desktopAction" disabled="disabled" value="Copy" onclick="p5copyFile(0)" />
869 <input type=button id=p5PasteButton class="btn btn-primary btn-sm me-1 desktopAction" disabled="disabled" value="Paste" onclick="p5pasteFile()" />
870 <div class="btn-group me-1 mobileActionDropdown">
871 <button type="button" class="btn btn-secondary btn-sm" data-bs-toggle="dropdown" aria-expanded="false">More&nbsp;<i class="fa-solid fa-chevron-down fa-xs"></i></button>
872 <div class="dropdown-menu">
873 <button type="button" disabled="disabled" id=p5MobileNewFolderButton class="dropdown-item" onclick="p5createfolder()">New Folder</button>
874 <button type="button" disabled="disabled" id=p5MobileUploadButton class="dropdown-item" onclick="p5uploadFile()">Upload</button>
875 <button type="button" disabled="disabled" id=p5MobileDownloadButton class="dropdown-item" onclick="p5downloadButton()">Download</button>
876 <button type="button" disabled="disabled" id=p5MobileCutButton class="dropdown-item" onclick="p5copyFile(1)">Cut</button>
877 <button type="button" disabled="disabled" id=p5MobileCopyButton class="dropdown-item" onclick="p5copyFile(0)">Copy</button>
878 <button type="button" disabled="disabled" id=p5MobilePasteButton class="dropdown-item" onclick="p5pasteFile()">Paste</button>
879 </div>
880 </div>
881 </div>
882 </td>
883 </tr>
884 <tr>
885 <td id="p5filesubhead" class="d-flex align-items-center">
886 <div class="d-flex align-items-center">&nbsp;&nbsp;<span id="p5currentpath"></span></div>
887 <div style=float:right class="d-flex align-items-center ms-auto">
888 <select id=p5sortdropdown class="form-select-sm me-1" onchange=updateFiles()>
889 <option value="1" selected="selected">Sort by name</option>
890 <option value="2">Sort by size</option>
891 <option value="3">Sort by date</option>
892 <option value="4">Descend by name</option>
893 <option value="5">Descend by size</option>
894 <option value="6">Descend by date</option>
895 </select>
896 </div>
897 </td>
898 </tr>
899 </table>
900 <div id="p5filetable">
901 <!--
902 <form id=p5fileCatchAll method=post enctype=multipart/form-data action=uploadfile.ashx target=fileUploadFrame>
903 <input type=file id=p5fileCatchAllInput name=files style="position:absolute;left:0;width:100%;top:0;bottom:0;opacity:0;display:none" onchange="p5fileCatchAllInputChanged(event)" />
904 <input id=p5fileDragLink2 name="link" style="display:none" />
905 <input type=submit id=p5fileCatchAllSubmit style="display:none" />
906 </form>
907 -->
908 <div id="p5PublicShare" style="">
909 <div>These files are shared publicly, click "link" to get public url.</div>
910 </div>
911 <div id="bigok" style="display:none"><b>&checkmark;</b></div>
912 <div id="bigfail" style="display:none"><b>&#10007;</b></div>
913 <span id="p5files"></span>
914 </div>
915 <table id="p5toolbarBottom" class="table table-hover" style=width:100% cellpadding=0 cellspacing=0>
916 <tr>
917 <td class=style6>&nbsp;<span id="p5bottomstatus"></span></td>
918 </tr>
919 </table>
920 </div>
921 <div id=p6 style="display:none">
922 <div id="p6info" style="overflow-y:auto">
923 <div id="p6title" class="d-flex align-items-center">
924 <div class="fs-4 fw-bold">My Server</div>
925 <img id=MainMeshImage src="serverpic.ashx" class="ms-auto">
926 </div>
927 <div id="p2ServerActions">
928 <p><strong>Server actions</strong></p>
929 <div style="margin-left:25px">
930 <div id="p2ServerActionsBackup">
931 <div class="p2AccountActions"><span
932 style="display:none"><strong>&#x2713;</strong></span></div><a id="p6backuplink"
933 href="{{{domainurl}}}backup.zip" rel="noreferrer noopener" target="_blank">Download server backup</a>
934 </div>
935 <div id="p2ServerActionsRestore">
936 <div class="p2AccountActions"><span
937 style="display:none"><strong>&#x2713;</strong></span></div><a href=#
938 onclick="return server_showRestoreDlg()">Restore server with backup</a>
939 </div>
940 <div id="p2ServerActionsGoogleBackup" style="display:none">
941 <div class="p2AccountActions"><span id="p2ServerActionsGoogleBackupCheck"
942 style="display:none"><strong>&#x2713;</strong></span></div><span><a href=#
943 onclick="return server_setupGoogleDriveBackup()">Google Drive backup</a><br /></span>
944 </div>
945 <div id="p2ServerActionsVersion">
946 <div class="p2AccountActions"><span
947 style="display:none"><strong>&#x2713;</strong></span></div><a href=#
948 onclick="return server_showVersionDlg()">Check server version</a>
949 </div>
950 <div id="p2ServerActionsErrors">
951 <div class="p2AccountActions"><span
952 style="display:none"><strong>&#x2713;</strong></span></div><a href=#
953 onclick="return server_showErrorsDlg()">Show server error log</a>
954 </div>
955 <div id="p2ServerActionsConfig">
956 <div class="p2AccountActions"><span
957 style="display:none"><strong>&#x2713;</strong></span></div><a href=#
958 onclick="return server_showConfigDlg()">Show server configuration</a>
959 </div>
960 </div>
961 <br />
962 </div>
963 <strong>Server Statistics</strong><br /><br />
964 <div id="serverStats">
965 <div id="serverCpuChartView" style="display:none">
966 <div class="chartViewCanvas" style="height:40px; text-align:center"><canvas
967 id="serverCpuChart" style="display:inline-block"></canvas></div>
968 <div class="chartViewText" id="serverCpuChartText"></div>
969 </div>
970 <div id="serverMemoryChartView" style="display:none">
971 <div class="chartViewCanvas" style="height:40px; text-align:center"><canvas
972 id="serverMemoryChart" style="display:inline-block"></canvas></div>
973 <div class="chartViewText" id="serverMemoryChartText"></div>
974 </div><br /><br />
975 <div id="serverStatsTable"></div>
976 </div>
977 <div id="serverWarningsDiv" style="display:none">
978 <br /><strong>Server Warnings</strong><br /><br />
979 <div id="serverCertWarnings"></div>
980 <div id="serverWarnings"></div>
981 </div>
982 </div>
983 </div>
984 <div id=p10 style="display:none">
985 <div id=p10title class="d-flex align-items-center">
986 <div id="p10BackButton" class="pe-2">
987 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
988 </div>
989 <div class='fs-4 fw-bold'><i class="fa-solid fa-circle-info fa-sm me-2 text-secondary"></i>General - <span id=p10deviceName></span></div>
990 </div>
991 <div id=p10info style="overflow-y:auto" class="pt-3">
992 <table style="width:100%" cellpadding="0" cellspacing="0">
993 <tr>
994 <td style=width:auto valign=top>
995 <div id=p10html></div>
996 </td>
997 <td style=width:100px;display:none id=notesPanel valign=top>
998 <table>
999 <thead>
1000 <tr>
1001 <th>Notes</th>
1002 </tr>
1003 </thead>
1004 <tbody>
1005 <tr>
1006 <td><div id=notesPanelArea style=width:300px;height:200px;resize:none;overflow-y:scroll></div></td>
1007 </tr>
1008 </tbody>
1009 </table>
1010 </td>
1011 <td style=width:20px></td>
1012 <td style=width:200px;vertical-align:top;position:relative valign=top>
1013 <div class="deviceNotifyLargeDot">
1014 <div id="p10deviceMsg" onclick=showDeviceMessages(null,null,event) class=deviceNotifyDotSub></div>
1015 <i title="Starred" role=button class="fa-solid fa-star" id="p10deviceStar" onclick=showDeviceSessions(null,null,event) data-fa-transform="shrink-5" data-fa-mask="fa-solid fa-circle"></i>
1016 <i title="List of remote sessions active on this computer." role=button class="fa-solid fa-arrow-right-arrow-left" id="p10deviceNotify" onclick=showDeviceSessions(null,null,event) data-fa-transform="shrink-5" data-fa-mask="fa-solid fa-circle"></i>
1017 <i title="Help Requested" role=button class="fa-solid fa-question" id="p10deviceHelp" onclick=showDeviceHelpRequests(null,null,event) data-fa-transform="shrink-5" data-fa-mask="fa-solid fa-circle"></i>
1018 </div>
1019 <div id="p10deviceBattery" class="deviceBatteryLarge deviceBatteryLarge1"></div>
1020 <a href=# onclick=p10showiconselector()><img id=MainComputerImage></a>
1021 <div id=MainComputerState></div>
1022 </td>
1023 </tr>
1024 </table><br />
1025 <div id=p10html2></div>
1026 <div id=p10html3></div>
1027 <div id=p10html4></div>
1028 <div id=p10html5></div>
1029 </div>
1030 </div>
1031 <div id=p11 class="noselect" style="display:none">
1032 <!-- softKeyboard: lives at p11 level (outside deskarea4) so it stays
1033 focusable even when the toolbar is hidden in fullscreen/landscape.
1034 position:fixed keeps it off-screen while remaining a valid focus target. -->
1035 <input id="softKeyboard" autocapitalize="off" autocomplete="off" autocorrect="off" spellcheck="false"
1036 type="text" inputmode="text"
1037 style="position:fixed;opacity:0;width:1px;height:1px;left:-9999px;top:-9999px"
1038 oninput="onSoftKeyboardInput(this)"
1039 onblur="onSoftKeyboardFocusChange(false)"
1040 onfocus="onSoftKeyboardFocusChange(true)" />
1041 <!-- Trackpad cursor -->
1042 <svg id="trackpadCursor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 20" width="18" height="30"
1043 style="display:none;position:fixed;pointer-events:none;z-index:9999;transform-origin:0 0;filter:drop-shadow(1px 1px 2px rgba(0,0,0,0.8))">
1044 <path d="M0,0 L0,16 L4,12 L6.5,18 L8.5,17 L6,11 L11,11 Z" fill="white" stroke="black" stroke-width="1.2" stroke-linejoin="round"/>
1045 </svg>
1046 <div id="p11title" class="d-flex align-items-center">
1047 <div id="p11BackButton" class="pe-2">
1048 <i class="fa-solid fa-2xl fa-square-caret-left" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1049 </div>
1050 <div class='fs-4 fw-bold'><i class="fa-solid fa-display fa-sm me-2 text-secondary"></i>Desktop - <span id=p11deviceName></span></div>
1051 <div id="p11KeyboardLights" style="font-size:x-small;color:black" class="ms-auto pe-2">
1052 <div id="p11numlock" style="display:inline-block;margin-left:1px;border-radius:5px;background-color:#A3FFB8;padding:2px">NUM</div>
1053 <div id="p11capslock" style="display:inline-block;margin-left:1px;border-radius:5px;background-color:#A3FFB8;padding:2px">CAPS</div>
1054 <div id="p11scrolllock" style="display:inline-block;margin-left:1px;border-radius:5px;background-color:#A3FFB8;padding:2px">SCROLL</div>
1055 </div>
1056 <div id="devListToolbarViewIcons">
1057 <i class="fa-solid fa-xl fa-maximize fa-border" role="button" onclick=deskToggleFull(event) title="Full Screen. Hold shift to browser full screen."></i>
1058 </div>
1059 </div>
1060 <div id="p11warning" onclick="showFeaturesDlg()">
1061 <div class="icon2"></div>
1062 <div class="warningbox">Redirection port or KVM feature is disabled<span id="p11warninga">, click here to enable it.</span></div>
1063 </div>
1064 <div id="p11warning2" onclick="showPowerActionDlg()">
1065 <div class="icon2"></div>
1066 <div class="warningbox">Remote computer is not powered on, click here to issue a power command.
1067 </div>
1068 </div>
1069 <div id=deskarea0 cellpadding=0 cellspacing=0>
1070 <div id=deskarea1 class="areaHead d-flex flex-wrap">
1071 <div class="d-flex align-items-center">
1072 <input type="button" class="btn btn-primary btn-sm me-1" id="autoconnectbutton1"
1073 value="AutoConnect" onclick=autoConnectDesktop(event) onkeypress="return false"
1074 onkeydown="return false" style="display:none;margin-right:4px" />
1075 <div class="btn-group dropdown-center me-1" id=connectbutton1span>
1076 <button type="button" class="btn btn-success btn-sm" id=connectbutton1 title="Connect using MeshAgent remote desktop" onclick=connectDesktop(event,3) onkeypress="return false" onkeydown="return false" disabled="disabled">Connect</button>
1077 <button type="button" class="btn btn-success btn-sm dropdown-toggle dropdown-toggle-split" id=connectbutton1d data-bs-toggle="dropdown" aria-expanded="false">
1078 <span class="visually-hidden">Toggle Dropdown</span>
1079 </button>
1080 <ul class="dropdown-menu">
1081 <li><a class="dropdown-item" onclick="cmdeskaction(1,event)">Ask Consent + Bar</a></li>
1082 <li><a class="dropdown-item" onclick="cmdeskaction(2,event)">Ask Consent</a></li>
1083 <li><a class="dropdown-item" onclick="cmdeskaction(3,event)">Privacy Bar</a></li>
1084 </ul>
1085 </div>
1086 <div class="btn-group dropdown-center me-1" id=connectbutton1rspan>
1087 <button type="button" class="btn btn-success btn-sm" id=connectbutton1r title="Connect using RDP" onclick=askRdpCredentials() onkeypress="return false" onkeydown="return false" disabled="disabled">RDP Connect</button>
1088 <button type="button" class="btn btn-success btn-sm dropdown-toggle dropdown-toggle-split" id=connectbutton1rd data-bs-toggle="dropdown" aria-expanded="false"><span class="visually-hidden">Toggle Dropdown</span></button>
1089 <ul class="dropdown-menu">
1090 <li><a class="dropdown-item" onclick="cmaltportaction(1,event)">Alternate Port</a></li>
1091 </ul>
1092 </div>
1093 <span id=connectbutton1hspan><button class="btn btn-primary btn-sm me-1" type=button
1094 id=connectbutton1h title="Connect using hardware KVM"
1095 onclick=connectDesktop(event,2) onkeypress="return false" onkeydown="return false"
1096 disabled="disabled">HW Connect</button></span>
1097 <div class="btn-group dropdown-center me-1" id=disconnectbutton1span>
1098 <button type="button" class="btn btn-danger btn-sm" id=disconnectbutton1 title="Connect using MeshAgent remote desktop" onclick=connectDesktop(event,0) onkeypress="return false" onkeydown="return false">Disconnect</button>
1099 <button type="button" class="btn btn-danger btn-sm dropdown-toggle dropdown-toggle-split" id=diconnectbutton1d data-bs-toggle="dropdown" aria-expanded="false">
1100 <span class="visually-hidden">Toggle Dropdown</span>
1101 </button>
1102 <ul class="dropdown-menu">
1103 <li><a class="dropdown-item" onclick="cmdeskaction(10,event)">Disconnect and Lock</a></li>
1104 <li><a class="dropdown-item" onclick="cmdeskaction(11,event)">Disconnect</a></li>
1105 </ul>
1106 </div>
1107 <span id="deskstatus" style="line-height:22px">Disconnected</span><span id="deskmetadata"></span>
1108 </div>
1109 <div class="d-flex align-items-center ms-auto">
1110 <div id="desktopCustomUiButtons" style="float:left"></div>
1111 <div id="desktopCustomUpperRight" style="float:left;margin-right:6px"></div>
1112 <input type="button" class="btn btn-primary btn-sm" title="Change the power state of the remote machine" onkeypress="return false" onkeydown="return false" value="Power Actions..." onclick="showPowerActionDlg()" style="display:none" />
1113 <input id="deskActionsBtn" type=button class="btn btn-primary btn-sm me-1" title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() class="mR" />
1114 <input id="deskActionsSettings" type="button" class="btn btn-primary btn-sm me-1" value="Settings" title="Edit remote desktop settings" onkeypress="return false" onkeydown="return false" onclick="showDesktopSettings()" class="mR" />
1115 <input id="deskFocusBtn" type="button" title="Toggle focus mode, when active only the region around the mouse is updated" onkeypress="return false" onkeydown="return false" value="Focus All" onclick="deskToggleFocus()" style="margin-right:3px;display:none" />
1116 <div id="deskRecordIcon" class='deskareaicon' title="Server is recording this session" style="display:none;background-color:red;width:12px;height:12px;border-radius:6px;margin-top:5px"></div>
1117 <div class='deskareaicon' title="Rotate Right" onclick="drotate(1)"><i class="fa-solid fa-rotate-right"></i></div>
1118 <div class='deskareaicon' title="Rotate Left" onclick="drotate(-1)"><i class="fa-solid fa-rotate-left"></i></div>
1119 <div class='deskareaicon' title="Toggle View Mode" onclick="toggleAspectRatio(1)">&#8690;</div>
1120 <div id="idx_deskFullBtn2" onclick=deskToggleFull(event) style="float:right">&nbsp;&#x2716;</div>
1121 </div>
1122 </div>
1123 <div id=deskarea3x>
1124 <div id="p11bigok" style="display:none"><b>&checkmark;</b></div>
1125 <div id="p11bigfail" style="display:none"><b>&#10007;</b></div>
1126 <div id=DeskFocus oncontextmenu="return false" onmousedown=dmousedown(event)
1127 onmouseup=dmouseup(event) onmousemove=dmousemove(event)></div>
1128 <div id=DeskParent>
1129 <canvas id=Desk width=640 height=480 oncontextmenu="return false"
1130 onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event)
1131 onmousewheel=dmousewheel(event)
1132 ontouchstart=dtouchstart(event) ontouchmove=dtouchmove(event) ontouchend=dtouchend(event)></canvas>
1133 </div>
1134 <div id=DeskTools>
1135 <div id=deskToolsAreaTop>
1136 <a id=DeskToolsRefreshButton class="link-underline-primary" style="right:2px" onclick="refreshDeskTools()">Refresh</a>
1137 <div id=deskToolsTopTabProcess class="deskToolsTopTab" onclick="changeDeskToolTab(0)"
1138 style="left:0px;bottom:0px">Processes</div>
1139 <div id=deskToolsTopTabService class="deskToolsTopTab" onclick="changeDeskToolTab(1)"
1140 style="display:none;left:90px;color:gray">Services</div>
1141 </div>
1142 <div id=deskToolsArea>
1143 <div id="DeskToolsProcessTab">
1144 <div id=deskToolsHeader>
1145 <a class="colmn1" title="Sort by process id" onclick=sortProcess(0)>PID</a>
1146 <a class="colmn2" title="Sort by name" onclick=sortProcess(1)>Name</a>
1147 </div>
1148 <div id="DeskToolsProcesses" style=""></div>
1149 </div>
1150 <div id="DeskToolsServiceTab" style="display:none">
1151 <div id=deskToolsServiceHeader>
1152 <a class="colmn1" style="width:70px" title="Sort by state"
1153 onclick=sortService(0)>State</a>
1154 <a class="colmn2" title="Sort by name" onclick=sortService(1)>Name</a>
1155 </div>
1156 <div id="DeskToolsServices" style=""></div>
1157 </div>
1158 </div>
1159 </div>
1160 <div id=p11DeskConsoleMsg
1161 style="display:none;text-align:left;cursor:pointer;position:absolute;left:30px;top:17px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px;text-align:left"
1162 onclick=p11clearConsoleMsg()></div>
1163 <div id=p11DeskSessionSelector
1164 style="display:none;position:absolute;left:30px;top:17px;right:30px;bottom:17px;overflow-y:auto">
1165 </div>
1166 </div>
1167 <div id=deskarea4 class="areaFoot d-flex flex-wrap">
1168 <div class="d-flex align-items-center">
1169 <select id="deskkeys" class="form-select-sm me-1" cmenu=deskKeyShortcutContextMenu></select>
1170 <button id="DeskWD" class="btn btn-primary btn-sm me-1" onkeypress="return false" onkeydown="return false" onclick="deskSendKeys()">Send</button>
1171 <input id="DeskESC" style="display:none" type="button" class="btn btn-secondary btn-sm me-1" value="ESC" onkeypress="return false" onkeydown="return false" onclick="sendDeskEsc()" />
1172 <input id="DeskClip" type="button" class="btn btn-secondary btn-sm me-1" value="Clipboard" onkeypress="return false" onkeydown="return false" onclick="showDeskClip()" />
1173 <input id="DeskType" class="btn btn-secondary btn-sm me-1" cmenu="deskPreConfigShortcutContextMenu" type="button" value="Type" onkeypress="return false" onkeydown="return false" onclick="showDeskType()" />
1174 <!-- Mobile-only: toggle button for the soft keyboard -->
1175 <button id="DeskSoftKbdBtn" class="btn btn-secondary btn-sm me-1" type="button"
1176 onclick="toggleMobileKeyboard()" title="Show/hide on-screen keyboard"
1177 style="display:none">
1178 <i class="fa-solid fa-keyboard"></i>
1179 </button>
1180 <div id="DeskControlSpan" title="Toggle mouse and keyboard input" class="form-check"><input id="DeskControl" type="checkbox" class="form-check-input" onkeypress="return false" onkeydown="return false" onclick="toggleKvmControl()"><label class="form-check-label" for="DeskControl">Input</label></div>&nbsp;
1181 </div>
1182 <!-- Mobile-only "More" button — opens a fixed-position action panel -->
1183 <div id="deskMobileActions" class="ms-auto align-items-center" style="display:none">
1184 <button class="btn btn-secondary btn-sm" type="button" onclick="deskToggleMobileActions()" title="More actions">
1185 More&nbsp;<i class="fa-solid fa-chevron-up fa-xs"></i>
1186 </button>
1187 </div>
1188 <div id="deskarea4Icons" class="d-flex ms-auto align-items-center">
1189 <span id=DeskMonitorSelectionSpan></span>
1190 <span id="DeskLatency" style="width:70px" class="text-center" title="Desktop Session Latency"></span>
1191 <span id="DeskTimer" style="width:70px" class="text-center" title="Session time"></span>
1192 <input id=DeskToolsButton type=button class="btn btn-primary btn-sm mx-1" value=Tools title="Toggle tools view" onkeypress="return false" onkeydown="return false" onclick="toggleDeskTools()" />
1193 <span id=DeskGuestShareButton title="Share the device with a guest" role="button">
1194 <i class="fa-solid fa-fw fa-share-nodes" id=DeskInputUnLockedButtonImage onclick=showShareDevice()></i>
1195 </span>
1196 <span id=DeskInputUnLockedButton title="Remote input is unlocked" role="button">
1197 <i class="fa-solid fa-fw fa-keyboard" onclick=deskInputLockFunction(1)></i>
1198 </span>
1199 <span id=DeskInputLockedButton title="Remote input is locked" role="button">
1200 <i class="fa-solid fa-fw fa-keyboard" style=color:red onclick=deskInputLockFunction(0)></i>
1201 </span>
1202 <span id=DeskRefreshButton title="Refresh the desktop" role="button">
1203 <i class="fa-solid fa-fw fa-rotate" onclick=deskRefreshFunction()></i>
1204 </span>
1205 <span id=DeskClipboardOutButton title="Upload local clipboard to remote device" role="button">
1206 <i class="fa-solid fa-fw fa-up-long" data-fa-transform="shrink-9 down-4" data-fa-mask="fa-solid fa-clipboard" onclick=deskClipboardOutFunction()></i>
1207 </span>
1208 <span id=DeskClipboardInButton title="Download remote clipboard to local clipboard" style="display:none" role="button">
1209 <i class="fa-solid fa-fw fa-down-long" data-fa-transform="shrink-9 down-4" data-fa-mask="fa-solid fa-clipboard" onclick=deskClipboardInFunction()></i>
1210 </span>
1211 <span id=DeskRecordButton cmenu=deskPlayerContextMenu title="Record remote desktop session to file" style="display:none" role="button">
1212 <i class="fa-solid fa-fw fa-video" onclick=deskRecordSession() id=DeskRecordButtonImage></i>
1213 </span>
1214 <span id=DeskSaveImageButton title="Save a screenshot of the remote desktop" role="button">
1215 <i class="fa-solid fa-fw fa-camera" onclick=deskSaveImage()></i>
1216 </span>
1217 <span id=DeskBackgroundButton title="Toggle remote desktop background" role="button">
1218 <i class="fa-solid fa-fw fa-image" onclick=deviceToggleBackground(event) id=DeskBackgroundButtonImage></i>
1219 </span>
1220 <span id=DeskOpenWebButton title="Open a web address on the remote computer" role="button">
1221 <i class="fa-solid fa-fw fa-globe" onclick=deviceUrlFunction()></i>
1222 </span>
1223 <span id=DeskLockButton title="Lock the remote computer" role="button">
1224 <i class="fa-solid fa-fw fa-lock" onclick=deviceLockFunction()></i>
1225 </span>
1226 <span id=DeskNotifyButton title="Display a notification on the remote computer" role="button">
1227 <i class="fa-solid fa-fw fa-bell" onclick=deviceToastFunction()></i>
1228 </span>
1229 <span id=DeskChatButton class="deskarea" title="Open chat window to this computer" role="button">
1230 <i class="fa-solid fa-fw fa-message" onclick=deviceChat(event)></i>
1231 </span>
1232 <span id=DeskRunButton cmenu="deskPreConfigScriptContextMenu" class="deskarea" title="Run a script on this computer" role="button">
1233 <i class="fa-solid fa-fw fa-play" onclick=runDeviceCmd()></i>
1234 </span>
1235 </div><!-- /deskarea4Icons -->
1236 </div>
1237 <!-- Mobile action panel: fixed above the tab bar, shown via deskToggleMobileActions() -->
1238 <div id="deskMobileActionsBackdrop" onclick="deskCloseMobileActions()" style="display:none;position:fixed;inset:0;z-index:1998"></div>
1239 <div id="deskMobileActionsPanel" style="display:none;position:fixed;right:8px;bottom:82px;z-index:1999;min-width:220px;border-radius:12px;overflow-y:auto;overflow-x:hidden;max-height:calc(100dvh - 100px);box-shadow:0 4px 24px rgba(0,0,0,0.28)">
1240 <a class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="toggleMobileKeyboard();deskCloseMobileActions()"><i class="fa-solid fa-keyboard fa-fw me-2 text-primary"></i>On-screen Keyboard</a>
1241 <a class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="showDeskType();deskCloseMobileActions()"><i class="fa-solid fa-font fa-fw me-2 text-primary"></i>Type Text</a>
1242 <a class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="deviceActionFunction();deskCloseMobileActions()"><i class="fa-solid fa-bolt fa-fw me-2 text-primary"></i>Actions</a>
1243 <a class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="showDesktopSettings();deskCloseMobileActions()"><i class="fa-solid fa-gear fa-fw me-2 text-primary"></i>Settings</a>
1244 <a id="deskTrackpadItem" class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="deskToggleTrackpad();deskCloseMobileActions()"><i class="fa-solid fa-computer-mouse fa-fw me-2 text-primary"></i>Enable Trackpad Mode</a>
1245 <a id="deskRightClickItem" class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="deskToggleRightClickMode();deskCloseMobileActions()"><i class="fa-solid fa-hand-pointer fa-fw me-2 text-primary"></i>Enable Right-click Mode</a>
1246 <a class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="deskCustomizeKeys();deskCloseMobileActions()"><i class="fa-solid fa-sliders fa-fw me-2 text-primary"></i>Customize Shortcuts</a>
1247 <a class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="deskRefreshFunction();deskCloseMobileActions()"><i class="fa-solid fa-rotate fa-fw me-2 text-primary"></i>Refresh Desktop</a>
1248 <a class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="showDeskClip();deskCloseMobileActions()"><i class="fa-solid fa-paste fa-fw me-2 text-primary"></i>Clipboard</a>
1249 <a class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="deskClipboardOutFunction();deskCloseMobileActions()"><i class="fa-solid fa-clipboard fa-fw me-2 text-primary"></i>Upload Clipboard</a>
1250 <a class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="deviceUrlFunction();deskCloseMobileActions()"><i class="fa-solid fa-globe fa-fw me-2 text-primary"></i>Open URL on Remote</a>
1251 <a class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="deviceLockFunction();deskCloseMobileActions()"><i class="fa-solid fa-lock fa-fw me-2 text-primary"></i>Lock Remote</a>
1252 <a class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="deviceToastFunction();deskCloseMobileActions()"><i class="fa-solid fa-bell fa-fw me-2 text-primary"></i>Send Notification</a>
1253 <a class="d-block px-3 py-2 text-decoration-none border-bottom" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="deviceChat(event);deskCloseMobileActions()"><i class="fa-solid fa-message fa-fw me-2 text-primary"></i>Chat</a>
1254 <a class="d-block px-3 py-2 text-decoration-none" style="font-size:15px;background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529)" onclick="runDeviceCmd();deskCloseMobileActions()"><i class="fa-solid fa-play fa-fw me-2 text-primary"></i>Run Script</a>
1255 </div>
1256 <!-- Fullscreen floating button - replaces all toolbars when in fulldesk mode -->
1257 <div id="deskFsBtn" onclick="deskToggleMobileActions()" style="display:none;position:fixed;bottom:54px;right:12px;z-index:2000;width:48px;height:48px;border-radius:50%;background:rgba(0,0,0,0.55);color:#fff;align-items:center;justify-content:center;cursor:pointer;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);border:1.5px solid rgba(255,255,255,0.22);font-size:20px">
1258 <i class="fa-solid fa-bars"></i>
1259 </div>
1260 </div>
1261 </div>
1262 <div id=p12 style="display:none">
1263 <div id="p12title" class="d-flex align-items-center">
1264 <div id="p12BackButton" class="pe-2">
1265 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1266 </div>
1267 <div class='fs-4 fw-bold'><i class="fa-solid fa-terminal fa-sm me-2 text-secondary"></i>Terminal - <span id=p12deviceName></span></div>
1268 <div id="devListToolbarViewIcons2" class="ms-auto">
1269 <i class="fa-solid fa-xl fa-maximize fa-border" role="button" onclick=deskToggleFull(event) title="Full Screen. Hold shift to browser full screen."></i>
1270 </div>
1271 </div>
1272 <div id="p12warning" onclick=showFeaturesDlg()>
1273 <div class="icon2"></div>
1274 <div class="warningbox">Redirection port or KVM feature is disabled<span id="p12warninga">, click here to enable it.</span></div>
1275 </div>
1276 <div id="p12warning2" onclick=showPowerActionDlg()>
1277 <div class="icon2"></div>
1278 <div class="warningbox">Remote computer is not powered on, click here to issue a power command.
1279 </div>
1280 </div>
1281 <div id=termTable style="position:relative">
1282 <table style="width:100%" cellpadding=0 cellspacing=0>
1283 <tr>
1284 <td class="areaHead d-flex flex-wrap">
1285 <div class="d-flex align-items-center">
1286 <button type="button" id="autoconnectbutton2" class="btn btn-primary btn-sm me-1"
1287 onclick=autoConnectTerminal(event) onkeypress="return false"
1288 onkeydown="return false" style="display:none"><i class="fa-solid fa-play"></i>
1289 AutoConnect</button>
1290 <div class="btn-group dropdown-center me-1" id=connectbutton2span>
1291 <button type="button" class="btn btn-success btn-sm" id=connectbutton2 title="Connect using MeshAgent remote desktop" onclick=connectTerminal(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled">Connect</button>
1292 <button type="button" class="btn btn-success btn-sm dropdown-toggle dropdown-toggle-split" id=connectbutton2d data-bs-toggle="dropdown" aria-expanded="false" onclick=newContextMenu(event,1)><span class="visually-hidden">Toggle Dropdown</span></button>
1293 <ul class="dropdown-menu">
1294 <li><a class="dropdown-item" onclick="cmtermaction(1,0,event)"><b>Admin Shell</b></a></li>
1295 <li><a class="dropdown-item" onclick="cmtermaction(6,0,event)">Admin PowerShell</a></li>
1296 <li><a class="dropdown-item" onclick="cmtermaction(8,0,event)">User Shell</a></li>
1297 <li><a class="dropdown-item" onclick="cmtermaction(9,0,event)">User PowerShell</a></li>
1298 <li><a class="dropdown-item" onclick="cmtermaction(1,0x10,event)">Ask Admin Shell</a></li>
1299 <li><a class="dropdown-item" onclick="cmtermaction(6,0x10,event)">Ask Admin PowerShell</a></li>
1300 <li><a class="dropdown-item" onclick="cmtermaction(8,0x10,event)">Ask User Shell</a></li>
1301 <li><a class="dropdown-item" onclick="cmtermaction(9,0x10,event)">Ask User PowerShell</a></li>
1302 <li class="ass"><a class="dropdown-item" onclick="cmtermaction(8,0,event)"><b>User Shell</b></a></li>
1303 <li class="ass"><a class="dropdown-item" onclick="cmtermaction(9,0,event)">User PowerShell</a></li>
1304 <li class="lin"><a class="dropdown-item" onclick="cmtermaction(1,0,event)"><b>Root Shell</b></a></li>
1305 <li class="lin"><a class="dropdown-item" onclick="cmtermaction(8,0,event)">User Shell</a></li>
1306 <li class="lin"><a class="dropdown-item" onclick="cmtermaction(100,0,event)">Login Shell</a></li>
1307 </ul>
1308 </div>
1309 <div class="btn-group dropdown-center me-1" id=connectbutton2sspan>
1310 <button type="button" class="btn btn-success btn-sm" id=connectbutton2s title="Connect using MeshAgent remote desktop" onclick=connectTerminal(event,3) onkeypress="return false" onkeydown="return false" disabled="disabled">SSH Connect</button>
1311 <button type="button" class="btn btn-success btn-sm dropdown-toggle dropdown-toggle-split" id=connectbutton2sd data-bs-toggle="dropdown" aria-expanded="false"><span class="visually-hidden">Toggle Dropdown</span></button>
1312 <ul class="dropdown-menu">
1313 <li><a class="dropdown-item" onclick="cmsshportaction(1,event)">Alternate Port</a></li>
1314 </ul>
1315 </div>
1316 <span id="connectbutton2hspan"><button type="button"
1317 class="btn btn-success btn-sm me-1" id="connectbutton2h"
1318 title="Connect using Intel&reg; AMT hardware KVM"
1319 onclick=connectTerminal(event,2) onkeypress="return false"
1320 onkeydown="return false" disabled="disabled">HW Connect</button></span>
1321 <span id="disconnectbutton2span"><button type="button"
1322 class="btn btn-danger btn-sm me-1" id="disconnectbutton2"
1323 onclick=connectTerminal(event,0) onkeypress="return false"
1324 onkeydown="return false">Disconnect</button></span>
1325 <span id="termstatus" style="line-height:22px">Disconnected</span><span id="termtitle"></span>
1326 </div>
1327 <div class="d-flex align-items-center ms-auto">
1328 <div id="idx_termFullBtn2" onclick=deskToggleFull(event) style="float:right">
1329 &nbsp;&#x2716;</div>
1330 <div id="termRecordIcon" class='deskareaicon'
1331 title="Server is recording this session"
1332 style="display:none;background-color:red;width:12px;height:12px;border-radius:6px;margin-top:5px;margin-left:5px">
1333 </div>
1334 <input id="termActionsBtn" type=button title="Perform power actions on the device"
1335 onkeypress="return false" onkeydown="return false" value=Actions
1336 class="btn btn-primary me-1 btn-sm" onclick=deviceActionFunction() />
1337 <div id="terminalCustomUpperRight" style="float:left;margin-right:6px"></div>
1338 <div id="terminalCustomUiButtons" style="float:left"></div>
1339 </div>
1340 </td>
1341 </tr>
1342 <tr>
1343 <td id="termarea3x">
1344 <div id="termarea3xdiv" style="width:100%;height:100%;text-align:left"></div>
1345 <pre id="Term"></pre>
1346 </td>
1347 </tr>
1348 <tr>
1349 <td class="areaFoot d-flex flex-wrap">
1350 <div class="d-flex align-items-center">
1351 <input type=button onkeypress="return false" onkeydown="return false"
1352 class="btn btn-primary btn-sm me-1" id="ctrlcbutton"
1353 value="Ctl-C" onclick="termSendKey(3,'ctrlcbutton')" />
1354 <input type=button onkeypress="return false" onkeydown="return false"
1355 class="btn btn-primary btn-sm me-1" id="ctrlxbutton"
1356 value="Ctl-X" onclick="termSendKey(24,'ctrlxbutton')" />
1357 <input type=button onkeypress="return false" onkeydown="return false"
1358 class="btn btn-primary me-1 btn-sm" id="escbutton"
1359 value="ESC" onclick="termSendKey(27,'escbutton')" />
1360 <input type=button onkeypress="return false" onkeydown="return false"
1361 class="btn btn-primary me-1 btn-sm" id="tabbutton"
1362 value="Tab" onclick="termSendKey(9,'tabbutton')" style="display:none" />
1363 <input type=button onkeypress="return false" onkeydown="return false"
1364 class="btn btn-primary me-1 btn-sm" id="bsbutton"
1365 value="Backspace" onclick="termSendKey(8,'bsbutton')" style="display:none" />
1366 <input type=button onkeypress="return false" onkeydown="return false"
1367 class="btn btn-primary me-1 btn-sm" id="pastebutton" value="Paste"
1368 title="Paste text into the terminal" onclick="showTermPasteDialog()"
1369 style="display:none" />
1370 </div>
1371 <div class="d-flex align-items-center ms-auto">
1372 <span id="TermTimer" title="Session time"></span>&nbsp;
1373 <span id="terminalSettingsButtons" style="display:none">
1374 <input id="id_tcrbutton" type="button" onkeypress="return false"
1375 onkeydown="return false" class="btn btn-primary me-1 btn-sm" value="CR+LF"
1376 title="Toggle what the return key will send" onclick="termToggleCr()" />
1377 <input id="id_tfxkeysbutton" type="button" onkeypress="return false"
1378 onkeydown="return false" class="btn btn-primary me-1 btn-sm" value="Intel (F10 = ESC+[OM)"
1379 title="Toggle F1 to F10 keys emulation type" onclick="termToggleFx()" />
1380 <input id="id_ttypebutton" type="button" onkeypress="return false"
1381 onkeydown="return false" class="btn btn-primary me-1 btn-sm" value="Extended Ascii"
1382 title="Toggle terminal emulation type" onclick="termToggleType()" />
1383 </span>
1384 <span id="terminalSizeDropDown" style="display:none">
1385 <select id="termSizeList" onkeypress="return false" class="form-select-sm me-1">
1386 <option value="1">80x25</option>
1387 <option value="2">100x30</option>
1388 </select>
1389 </span>
1390 <select id="specialkeylist" class="form-select-sm me-1" onkeypress="return false"></select>
1391 <input id="specialkeylistinput" type="button" onkeypress="return false"
1392 class="btn btn-primary btn-sm me-1" value="Send"
1393 title="Send the selected special key" onclick="sendSpecialKey()" />
1394 </div>
1395 </td>
1396 </tr>
1397 </table>
1398 <div id=p12TermConsoleMsg
1399 style="display:none;text-align:left;cursor:pointer;position:absolute;left:30px;top:45px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px"
1400 onclick=p12clearConsoleMsg()></div>
1401 </div>
1402 </div>
1403 <div id=p13 style="display:none">
1404 <div id="p13title" class="d-flex align-items-center">
1405 <div id="p13BackButton" class="pe-2">
1406 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1407 </div>
1408 <div class='fs-4 fw-bold'><i class="fa-solid fa-folder-open fa-sm me-2 text-secondary"></i>Files - <span id=p13deviceName></span></div>
1409 </div>
1410 <table id="p13toolbar" cellpadding="0" cellspacing="0">
1411 <tr>
1412 <td class="areaHead d-flex flex-wrap">
1413 <div class="d-flex align-items-center">
1414 <button id=p13AutoConnect onclick=autoConnectFiles(event)
1415 class="btn btn-primary btn-sm me-1" type="button" style="display:none"><i
1416 class="fa-solid fa-play"></i> AutoConnect</button>
1417 <div class="btn-group dropdown-center me-1" id=p13Connectspan>
1418 <button type="button" class="btn btn-success btn-sm" id=p13Connect title="Connect using MeshAgent remote desktop" onclick=connectFiles(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled">Connect</button>
1419 <button type="button" class="btn btn-success btn-sm dropdown-toggle dropdown-toggle-split" id=p13Connectdrop data-bs-toggle="dropdown" aria-expanded="false"><span class="visually-hidden">Toggle Dropdown</span></button>
1420 <ul class="dropdown-menu">
1421 <li><a class="dropdown-item" onclick="cmconnectfilesaction()">Ask Consent</a></li>
1422 </ul>
1423 </div>
1424 <button id=p13Disconnect class="btn btn-danger btn-sm me-1" onclick=connectFiles(event) type="button">Disconnect</button>
1425 <div class="btn-group dropdown-center me-1" id=p13Connectsspan>
1426 <button type="button" class="btn btn-success btn-sm" id=p13Connects title="Connect using MeshAgent remote desktop" onclick=connectFiles(event,2) onkeypress="return false" onkeydown="return false" disabled="disabled">SFTP Connect</button>
1427 <button type="button" class="btn btn-success btn-sm dropdown-toggle dropdown-toggle-split" id=p13Connectsdrop data-bs-toggle="dropdown" aria-expanded="false"><span class="visually-hidden">Toggle Dropdown</span></button>
1428 <ul class="dropdown-menu">
1429 <li><a class="dropdown-item" onclick="cmsshportaction(1,event)">Alternate Port</a></li>
1430 </ul>
1431 </div>
1432 <span id=p13Status>Disconnected</span>
1433 </div>
1434 <div class="d-flex align-items-center ms-auto">
1435 <input id="filesActionsBtn" class="btn btn-primary me-1 btn-sm" type=button
1436 title="Perform power actions on the device" value=Actions
1437 onclick=deviceActionFunction() />
1438 <div id="filesRecordIcon" class='deskareaicon' title="Server is recording this session"
1439 style="display:none;background-color:red;width:12px;height:12px;border-radius:6px;margin-top:5px;margin-left:5px">
1440 </div>
1441 <div id="filesCustomUpperRight" style="float:left;margin-right:6px"></div>
1442 <div id="filesCustomUiButtons" style="float:left"></div>
1443 </div>
1444 </td>
1445 </tr>
1446 <tr>
1447 <td class="areaHead2 d-flex align-items-center flex-wrap" valign=bottom>
1448 <div id="p13rightOfButtons" class="toright2 d-flex align-items-center"></div>
1449 <div class="d-flex align-items-center">
1450 <input type=button disabled="disabled" id=p13FolderUp class="btn btn-secondary me-1 btn-sm" value="Up" onclick="p13folderup()" />
1451 <input type=button disabled="disabled" id=p13SelectAllButton class="btn btn-secondary me-1 btn-sm" value="Select All" onclick="p13selectallfile()" />
1452 <input type=button disabled="disabled" id=p13RenameFileButton class="btn btn-primary me-1 btn-sm" value="Rename" onclick="p13renamefile()" />
1453 <input type=button disabled="disabled" id=p13DeleteFileButton class="btn btn-primary me-1 btn-sm" value="Delete" onclick="p13deletefile()" />
1454 <input type=button disabled="disabled" id=p13ViewFileButton class="btn btn-primary me-1 btn-sm" value="Edit" onclick="p13viewfile()" />
1455 <input type=button disabled="disabled" id=p13NewFolderButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="New Folder" onclick="p13createfolder()" />
1456 <input type=button disabled="disabled" id=p13NewFileButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="New File" onclick="p13createfile()" />
1457 <input type=button disabled="disabled" id=p13UploadButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="Upload" onclick="p13uploadFile()" />
1458 <input type=button disabled="disabled" id=p13DownloadButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="Download" onclick="p13downloadButton()" />
1459 <input type=button disabled="disabled" id=p13CutButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="Cut" onclick="p13copyFile(1)" />
1460 <input type=button disabled="disabled" id=p13CopyButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="Copy" onclick="p13copyFile(0)" />
1461 <input type=button disabled="disabled" id=p13PasteButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="Paste" onclick="p13pasteFile()" />
1462 <input type=button disabled="disabled" id=p13ZipButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="Zip" onclick="p13zipFiles()" />
1463 <input type=button disabled="disabled" id=p13UnzipButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="Unzip" onclick="p13unzipFile()" />
1464 <input type=button disabled="disabled" id=p13RefreshButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="Refresh" onclick="p13folderup(9999)" />
1465 <input type=button disabled="disabled" id=p13FindButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="Find" onclick="p13findfile()" />
1466 <input type=button disabled="disabled" id=p13GoToFolderButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="GoTo" onclick="p13gotofolder()" />
1467 <input type=button disabled="disabled" id=p13OpenButton class="btn btn-primary me-1 btn-sm desktopFileAction" value="Open" onclick="p13openfilefolder()" />
1468 <div class="btn-group me-1 mobileFileActionDropdown">
1469 <button type="button" class="btn btn-secondary btn-sm" data-bs-toggle="dropdown" aria-expanded="false">More&nbsp;<i class="fa-solid fa-chevron-down fa-xs"></i></button>
1470 <div class="dropdown-menu">
1471 <button type="button" disabled="disabled" id=p13MobileNewFolderButton class="dropdown-item" onclick="p13createfolder()">New Folder</button>
1472 <button type="button" disabled="disabled" id=p13MobileNewFileButton class="dropdown-item" onclick="p13createfile()">New File</button>
1473 <button type="button" disabled="disabled" id=p13MobileUploadButton class="dropdown-item" onclick="p13uploadFile()">Upload</button>
1474 <button type="button" disabled="disabled" id=p13MobileDownloadButton class="dropdown-item" onclick="p13downloadButton()">Download</button>
1475 <button type="button" disabled="disabled" id=p13MobileCutButton class="dropdown-item" onclick="p13copyFile(1)">Cut</button>
1476 <button type="button" disabled="disabled" id=p13MobileCopyButton class="dropdown-item" onclick="p13copyFile(0)">Copy</button>
1477 <button type="button" disabled="disabled" id=p13MobilePasteButton class="dropdown-item" onclick="p13pasteFile()">Paste</button>
1478 <button type="button" disabled="disabled" id=p13MobileZipButton class="dropdown-item" onclick="p13zipFiles()">Zip</button>
1479 <button type="button" disabled="disabled" id=p13MobileUnzipButton class="dropdown-item" onclick="p13unzipFile()">Unzip</button>
1480 <button type="button" disabled="disabled" id=p13MobileRefreshButton class="dropdown-item" onclick="p13folderup(9999)">Refresh</button>
1481 <button type="button" disabled="disabled" id=p13MobileFindButton class="dropdown-item" onclick="p13findfile()">Find</button>
1482 <button type="button" disabled="disabled" id=p13MobileGoToFolderButton class="dropdown-item" onclick="p13gotofolder()">GoTo</button>
1483 <button type="button" disabled="disabled" id=p13MobileOpenButton class="dropdown-item" onclick="p13openfilefolder()">Open</button>
1484 </div>
1485 </div>
1486 </div>
1487 </td>
1488 </tr>
1489 <tr>
1490 <td class="areaHead3">
1491 <div class="toright2">
1492 <select id=p13sizedropdown class="form-select-sm me-2" onchange=p13updateFiles()>
1493 <option value=0 selected="selected">Human Readable</option>
1494 <option value=1 title=Bytes>Bytes</option>
1495 <option value=10 title=KB>Kilobytes</option>
1496 <option value=20 title=MB>Megabytes</option>
1497 <option value=30 title=GB>Gigabyte</option>
1498 </select>
1499 <select id=p13sortdropdown class="form-select-sm me-2" onchange=p13updateFiles()>
1500 <option value=1 selected="selected">Sort by name</option>
1501 <option value=2>Sort by size</option>
1502 <option value=3>Sort by date</option>
1503 <option value=4>Descend by name</option>
1504 <option value=5>Descend by size</option>
1505 <option value=6>Descend by date</option>
1506 </select>
1507 </div>
1508 <div id="p13pathrow" style="display:none" ondblclick="p13pathRowClick(event)">&nbsp;&nbsp;<span id="p13currentpath" ondblclick="p13pathRowClick(event)"></span><input type="text" id="p13currentpathinput" class="form-control form-control-sm" style="width: auto; min-width: 400px; display:none;" onkeydown="return p13onPathKeyDown(event)" onblur="p13hidePathInput()" /></div>
1509 </td>
1510 </tr>
1511 </table>
1512 <div id=p13FilesConsoleMsg
1513 style="display:none;text-align:left;cursor:pointer;position:absolute;left:30px;top:165px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px"
1514 onclick=p13clearConsoleMsg()></div>
1515 <div id="p13filetable" style="">
1516 <div id="p13bigok" style="display:none"><b>&checkmark;</b></div>
1517 <div id="p13bigfail" style="display:none"><b>&#10007;</b></div>
1518 <span id="p13files"></span>
1519 </div>
1520 <table id="p13toolbarBottom" cellpadding=0 cellspacing=0>
1521 <tr>
1522 <td class=style6>&nbsp;<span id="p13bottomstatus"></span></td>
1523 </tr>
1524 </table>
1525 </div>
1526 <div id=p14 style="display:none">
1527 <div id="p14title" class="d-flex align-items-center">
1528 <div id="p14BackButton" class="pe-2">
1529 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1530 </div>
1531 <div class='fs-4 fw-bold'><span id=p14deviceNamePrefix>Intel&reg; AMT</span> - <span id=p14deviceName></span></div>
1532 <div id="devListToolbarViewIcons" class="ms-auto">
1533 <i class="fa-solid fa-xl fa-maximize fa-border" role="button" onclick=deskToggleFull(event) title="Full Screen. Hold shift to browser full screen."></i>
1534 </div>
1535 </div>
1536 <iframe id=p14iframe src="{{{domainurl}}}commander.ashx"></iframe>
1537 </div>
1538 <div id=p15 style="display:none">
1539 <div id="p15title" class="d-flex align-items-center">
1540 <div id="p15BackButton" class="pe-2">
1541 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1542 </div>
1543 <div class='fs-4 fw-bold'><span id=p15deviceName></span></div>
1544 </div>
1545 <table id="consoleTable" cellpadding=0 cellspacing=0>
1546 <tr>
1547 <td class="areaHead d-flex flex-wrap">
1548 <div id="p15statetext" class="d-flex align-items-center"></div>
1549 <div class="d-flex align-items-center ms-auto">
1550 <div id=p15coreName class="me-1" title="Information about current core running on this agent"></div>
1551 <input type=button id=p15uploadCore class="btn btn-primary me-1 btn-sm" value="Agent Action" onclick=p15uploadCore(event) title="Change the agent Java Script code module" />
1552 <i onclick=p15downloadConsoleText() title="Download console text" class="fa-solid fa-download me-1"></i>
1553 </div>
1554 </td>
1555 </tr>
1556 <tr>
1557 <td>
1558 <div class="areaProgress">
1559 <div id="consoleprogressbar" style=""></div>
1560 </div>
1561 </td>
1562 </tr>
1563 <tr>
1564 <td id=p15agentConsole>
1565 <pre id=p15agentConsoleText></pre>
1566 </td>
1567 </tr>
1568 <tr>
1569 <td class="areaFoot">
1570 <table style="width:100%">
1571 <tr>
1572 <td style="width:99%">
1573 <input id=p15consoleText class="form-control" onkeyup=p15consoleSend(event) onfocus=onConsoleFocus(1) onblur=onConsoleFocus(0) />
1574 </td>
1575 <td>&nbsp;</td>
1576 <td id="p15outputselecttd">
1577 <select id=p15outputselect onchange="setupConsole()">
1578 <option id="p15outputselect1" value=1>Agent</option>
1579 <option id="p15outputselect3" value=3>Push</option>
1580 <option id="p15outputselect2" value=2>MQTT</option>
1581 </select>
1582 </td>
1583 <td style="width:1%"><input id="id_p15consoleClear" type="button"
1584 class="btn btn-primary bottombutton" value="Clear"
1585 onclick="p15consoleClear()" /></td>
1586 </tr>
1587 </table>
1588 </td>
1589 </tr>
1590 </table>
1591 </div>
1592 <div id=p16 style="display:none">
1593 <div id="p16title" class="d-flex align-items-center">
1594 <div id="p16BackButton" class="pe-2">
1595 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1596 </div>
1597 <div class='fs-4 fw-bold'><i class="fa-solid fa-calendar fa-sm me-2 text-secondary"></i>Events - <span id=p16deviceName></span></div>
1598 </div>
1599 <table class="pTable">
1600 <tr>
1601 <td class="auto-style1 d-flex justify-content-end p-1">
1602 <div class="d-flex align-items-center">
1603 <label class="me-1" for="p16filterevents">Filter</label>
1604 <select id=p16filterevents class="form-control-sm me-2" onchange=refreshDeviceEvents()>
1605 <option notransval=1 value="">All Logs</option>
1606 <option notransval=1 value=agentlog>Agent Logs</option>
1607 <option notransval=1 value=relaylog>Relay Logs</option>
1608 <option notransval=1 value=manual>Manual Logs</option>
1609 <option notransval=1 value=runcommands>Run Command Logs</option>
1610 <option notransval=1 value=batchupload>Batch Upload Logs</option>
1611 <option notransval=1 value=changenode>Change Node Logs</option>
1612 <option notransval=1 value=removenode>Remove Node Logs</option>
1613 </select>
1614 <label class="me-1" for="p16limitdropdown">Show</label>
1615 <select id=p16limitdropdown class="form-control-sm me-2" onchange=refreshDeviceEvents()>
1616 <option notransval=1 value=60>Last 60</option>
1617 <option notransval=1 value=120>Last 120</option>
1618 <option notransval=1 value=250>Last 250</option>
1619 <option notransval=1 value=500>Last 500</option>
1620 <option notransval=1 value=1000>Last 1000</option>
1621 </select>
1622 <i onclick=p3showDownloadEventsDialog(1) class="fa-solid fa-download" title="Download Events" style=cursor:pointer></i>
1623 </div>
1624 </td>
1625 </tr>
1626 </table>
1627 <div id=p16events></div>
1628 </div>
1629 <div id=p17 style="display:none;">
1630 <div id="p17title" class="d-flex align-items-center">
1631 <div id="p17BackButton" class="pe-2">
1632 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1633 </div>
1634 <div class='fs-4 fw-bold'><i class="fa-solid fa-list fa-sm me-2 text-secondary"></i>Details - <span id=p17deviceName></span></div>
1635 <div id="devListToolbarViewIcons3" class="ms-auto">
1636 <i class="fa-solid fa-rotate-right fa-xl fa-border" role="button" onclick=refreshDetails(event) title="Refresh details information."></i>
1637 <i class="fa-solid fa-chart-line fa-xl fa-border" role="button" onclick=deskToggleCpuGraph(event) title="Show device CPU and memory usage."></i>
1638 </div>
1639 </div>
1640 <div id=p17info class="ps-0 pb-5 overflow-y-auto container-fluid">
1641 <div id=p17graph style="width:100%">
1642 <table style=width:100%>
1643 <tr>
1644 <td style=width:64px;vertical-align:top>
1645 <img src=images/details/graph64.png border=0 width=64 />
1646 </td>
1647 <td>
1648 <div class=DevSt style=margin-bottom:3px;margin-left:16px><b>Live Graph</b></div>
1649 <div
1650 style="margin-bottom:10px;margin-left:16px;height:240px;width:calc(100% - 16px);position:relative">
1651 <canvas id=deviceDetailsStats style="position:absolute;top:0;left:0;right:0;bottom:0"></canvas>
1652 </div>
1653 <div id="extraGraphValues" style="display:none;margin-left:30px;margin-bottom:15px">
1654 </div>
1655 </td>
1656 </tr>
1657 </table>
1658 </div>
1659 <div id=p17info2></div>
1660 </div>
1661 </div>
1662 <div id=p18 style="display:none">
1663 <div id="p18title">
1664 <div id="p18BackButton" style="float:left">
1665 <div class="backButton" tabindex=0 onclick=goBack() title="Back"
1666 onkeypress="if (event.key == 'Enter') goBack()">
1667 <div class="backButtonEx"></div>
1668 </div>
1669 </div>
1670 <h1>Software - <span id=p18deviceName></span></h1>
1671 </div>
1672 <div id="p18toolbar" style="padding:10px;background-color:#f5f5f5;border-bottom:1px solid #ddd">
1673 <input type="button" id="p18RefreshBtn" class="btn btn-primary btn-sm" value="Refresh" onclick="loadInstalledApps()" />
1674 <input type="text" id="p18SearchInput" class="form-control-sm" placeholder="Search software..."
1675 style="margin-left:10px;width:250px" onkeyup="filterInstalledApps()" />
1676 <label style="margin-left:15px">
1677 <input type="checkbox" id="p18ShowStore" onchange="toggleStoreApps()" /> Show Store Apps
1678 </label>
1679 <span id="p18Status" style="margin-left:20px;color:gray"></span>
1680 </div>
1681 <div id="p18info" style="overflow-y:auto;padding:10px">
1682 <div id="p18html">
1683 <div style="text-align:center;padding:50px;color:gray">
1684 <i>Click Refresh to load installed software...</i>
1685 </div>
1686 </div>
1687 </div>
1688 </div>
1689 <div id=p19 style="display:none">
1690 <div id="p19title" class="d-flex align-items-center">
1691 <div id="p19BackButton" class="pe-2">
1692 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1693 </div>
1694 <div class='fs-4 fw-bold'>Plugins - <span id=p19deviceName></span></div>
1695 </div>
1696 <div id="p19headers"></div>
1697 <div id=p19pages></div>
1698 </div>
1699 <div id=p20 style="display:none">
1700 <div id=p20main style="overflow-y:auto">
1701 <div id=p20title class="d-flex align-items-center">
1702 <div id="p20BackButton" class="pe-2">
1703 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1704 </div>
1705 <div class='fs-4 fw-bold'>General - <span id=p20meshName></span></div>
1706 </div>
1707 <div class="d-flex">
1708 <div class="flex-grow-1">
1709 <div id=p20info3></div>
1710 </div>
1711 <div>
1712 <picture id=MainMeshImage>
1713 <source type="image/webp" width=200 height=200 srcset="images/webp/mesh-256.webp" />
1714 <img alt="" width=200 height=200 src=images/mesh-256.png />
1715 </picture>
1716 </div>
1717 </div>
1718 <p id=p20info></p>
1719 <p id=p20info2></p>
1720 </div>
1721 </div>
1722 <div id=p21 style="display:none">
1723 <div id=p21title class="d-flex align-items-center">
1724 <div id="p21BackButton" class="pe-2">
1725 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1726 </div>
1727 <div class='fs-4 fw-bold'>Summary - <span id=p21meshName></span></div>
1728 </div>
1729 <div id=p21main style="overflow-y:auto">
1730 <div style="width:100%">
1731 <div style="display:table;width:93%">
1732 <div id="meshPowerChartDiv" style="width:23%;display:inline-block;text-align:center;max-width:250px">
1733 <div style="margin:10px;font-size:16px">Power States</div>
1734 <canvas id="meshPowerChart" style="width:250px;height:250px"></canvas>
1735 </div>
1736 <div id="meshOsChartDiv" style="width:23%;display:inline-block;text-align:center;max-width:250px">
1737 <div style="margin:10px;font-size:16px">Agent Types</div>
1738 <canvas id="meshOsChart" style="width:250px;height:250px"></canvas>
1739 </div>
1740 <div id="meshConnChartDiv" style="width:23%;display:inline-block;text-align:center;max-width:250px">
1741 <div style="margin:10px;font-size:16px">Connectivity</div>
1742 <canvas id="meshConnChart" style="width:250px;height:250px"></canvas>
1743 </div>
1744 <div id="meshSecurityChartDiv" style="width:23%;display:inline-block;text-align:center;max-width:250px">
1745 <div style="margin:10px;font-size:16px">Security</div>
1746 <canvas id="meshSecurityChart" style="width:250px;height:250px"></canvas>
1747 </div>
1748 </div>
1749 </div>
1750 <p id=p21info style="overflow-y:auto"></p>
1751 </div>
1752 </div>
1753 <div id=p30 style="display:none">
1754 <div id=p30title class="d-flex align-items-center">
1755 <div id="p30BackButton" class="pe-2">
1756 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1757 </div>
1758 <div class='fs-4 fw-bold'>General - <span id=p30userName></span></div>
1759 </div>
1760 <div id="p30info" class='pe-2' style="overflow-y:auto">
1761 <div class="d-flex">
1762 <div class="col">
1763 <div id="p30html"></div>
1764 </div>
1765 <div class="col-auto">
1766 <img id="p30userAuthServiceLogo" style="display:none" class="userAuthStrategyLogo" width="64" height="64">
1767 <picture id="MainUserImage"
1768 style="display:none; border-width:0px; height:200px; width:200px; float:right"
1769 onclick="account_manageImage(1)">
1770 <source type="image/webp" width="200" height="200" srcset="images/webp/user-256.webp" />
1771 <img alt="" width="200" height="200" src="images/user-256.png" />
1772 </picture>
1773 <img id="MainUserImageEx" alt="" width="200" height="200" src="images/user-256.png" onclick="account_manageImage(1)" />
1774 <div style="width:100%;text-align:center;">
1775 <strong><span id="MainUserState"></span></strong>
1776 </div>
1777 </div>
1778 </div>
1779 <br />
1780
1781
1782 <div id=p30html2></div>
1783 <div id=p30html3></div>
1784 </div>
1785 </div>
1786 <div id=p31 style="display:none">
1787 <div id=p31title class="d-flex align-items-center">
1788 <div id="p31BackButton" class="pe-2">
1789 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1790 </div>
1791 <div class='fs-4 fw-bold'>Events - <span id=p31userName></span></div>
1792 </div>
1793 <table class="pTable">
1794 <tr>
1795 {{!-- <td class="h1"></td> --}}
1796 <!--<td>&nbsp;<input type=button onclick=refreshUsersEvents() value="Refresh" /></td>-->
1797 <td class="auto-style1 d-flex justify-content-end p-1">
1798 <div class="d-flex align-items-center">
1799 <label for="p31filterevents" class="me-1">Filter</label>
1800 <select id=p31filterevents class="form-select-sm me-2" onchange=refreshUsersEvents()>
1801 <option notransval=1 value="">All Logs</option>
1802 <option notransval=1 value=agentlog>Agent Logs</option>
1803 <option notransval=1 value=relaylog>Relay Logs</option>
1804 <option notransval=1 value=manual>Manual Logs</option>
1805 <option notransval=1 value=runcommands>Run Command Logs</option>
1806 <option notransval=1 value=batchupload>Batch Upload Logs</option>
1807 <option notransval=1 value=changenode>Change Node Logs</option>
1808 <option notransval=1 value=removenode>Remove Node Logs</option>
1809 </select>
1810 <label for="p31limitdropdown" class="me-1">Show</label>
1811 <select id=p31limitdropdown class="form-select-sm me-2" onchange=refreshUsersEvents()>
1812 <option notransval=1 value=60>Last 60</option>
1813 <option notransval=1 value=120>Last 120</option>
1814 <option notransval=1 value=250>Last 250</option>
1815 <option notransval=1 value=500>Last 500</option>
1816 <option notransval=1 value=1000>Last 1000</option>
1817 <option notransval=1 value="">No limit</option>
1818 </select>
1819 <a href=# onclick=p3showDownloadEventsDialog(3)><i class="fa-solid fa-download" role="button" title="Download Events"></i></a>
1820 </div>
1821 </td>
1822 {{!-- <td class="h2"></td> --}}
1823 </tr>
1824 </table>
1825 <div id=p31events style=""></div>
1826 </div>
1827 <div id=p40 style="display:none">
1828 <div id="p40title" class="d-flex align-items-center">
1829 <div class="fs-4 fw-bold">My Server Stats</div>
1830 </div>
1831 <div class="areaHead d-flex align-items-center flex-wrap p-1">
1832 <div class="d-flex align-items-center">
1833 <input value="Refresh" type="button" class="btn btn-primary btn-sm me-2" onclick="refreshServerTimelineStats()" />
1834 <div class="form-check"><input class="form-check-input me-1" type=checkbox id=p40log onclick="updateServerTimelineHours()" /><label class="form-check-label" for="p40log">Log-X</label></div>
1835 </div>
1836 <div class="toright2 d-flex align-items-center ms-auto">
1837 <select id=p40server style="display:none" onchange=updateServerTimelineStats()></select>
1838 <select id=p40type class="form-select-sm me-1" onchange=updateServerTimelineStats()>
1839 <option value=0>Connections</option>
1840 <option value=1>Memory</option>
1841 <option value=5>CPU</option>
1842 <option value=3>Inbound Traffic</option>
1843 <option value=4>Outbound Traffic</option>
1844 </select>
1845 <select id=p40time class="form-select-sm me-1" onchange=updateServerTimelineHours()>
1846 <option value=3>Last 3 hours</option>
1847 <option value=8>Last 8 hours</option>
1848 <option value=24>Last day</option>
1849 <option value=168>Last week</option>
1850 <option value=720>Last 30 days</option>
1851 </select>
1852 <i class="fa-solid fa-download" title="Download data points (.csv)" style=cursor:pointer onclick=p40downloadEvents()></i>
1853 </div>
1854 </div>
1855 <canvas id=serverMainStats style=""></canvas>
1856 </div>
1857 <div id=p41 style="display:none">
1858 <div id="p41title" class="d-flex align-items-center">
1859 <div class="fs-4 fw-bold">My Server Tracing</div>
1860 </div>
1861 <div class="areaHead d-flex align-items-center flex-wrap p-1">
1862 <div class="d-flex align-items-center">
1863 <input value="Tracing" type="button" class="btn btn-primary btn-sm me-1" onclick="setServerTracing()" />
1864 <span id="p41traceStatus">None</span>
1865 </div>
1866 <div class="toright2 d-flex align-items-center ms-auto">
1867 <label for="p41limitdropdown" class="me-1">Show</label>
1868 <select id=p41limitdropdown class="form-select-sm me-1" onchange=displayServerTrace()>
1869 <option value=100>Last 100</option>
1870 <option value=250>Last 250</option>
1871 <option value=500>Last 500</option>
1872 <option value=1000>Last 1000</option>
1873 </select>
1874 <input value="Clear" type="button" class="btn btn-primary btn-sm me-1" onclick="clearServerTracing()" />
1875 <i class="fa-solid fa-download" title="Download trace (.csv)" style=cursor:pointer onclick=p41downloadServerTrace()></i>
1876 </div>
1877 </div>
1878 <div id=p41events style=""></div>
1879 </div>
1880 <div id=p42 style="display:none">
1881 <div id="p42title" class="d-flex align-items-center">
1882 <div class="fs-4 fw-bold">My Server Plugins</div>
1883 </div>
1884 <div class="areaHead">
1885 <div class="toright2">
1886 </div>
1887 <div>
1888 <input value="Download Plugin" type="button" onclick="return pluginHandler.addPluginDlg();" />
1889 </div>
1890 </div>
1891 <div id="pluginRestartNotice" class="areaHead" style="background-color:gold;display:none">
1892 <div class="toright2">
1893 <input value="Refresh Agent Cores" type="button" onclick="distributeCore();return false" />
1894 </div>
1895 <div style="padding:2px">
1896 <div style="padding:2px"><b>Notice:</b> Plugins have been altered, this may require agent core update.</div>
1897 </div>
1898 </div>
1899 <table id="p42tbl">
1900 <tr class="DevSt">
1901 <th style="width:26px"></th>
1902 <th style="width:10px"></th>
1903 <th class="chName">Name</th>
1904 <th class="chDescription">Description</th>
1905 <th class="chSite" style="text-align:center">Link</th>
1906 <th class="chVersion" style="text-align:center">Version</th>
1907 <th class="chUpgradeAvail" style="text-align:center">Latest</th>
1908 <th class="chStatus" style="text-align:center">Status</th>
1909 <th class="chAction" style="text-align:center">Action</th>
1910 <th style="width:10px"></th>
1911 </tr>
1912 </table>
1913 <div id="pluginNoneNotice" style="width:100%;text-align:center;padding-top:10px;display:none"><i>No plugins on server.</i></div>
1914 </div>
1915 <div id=p43 style="display:none">
1916 <div id="p43BackButton">
1917 <div class="backButton" tabindex=0 onmouseup=go(42) title="Back"
1918 onkeypress="if (event.key == 'Enter') go(42)">
1919 <div class="backButtonEx"></div>
1920 </div>
1921 </div>
1922 <h1>My Server Plugins - <span id="p43title"></span></h1>
1923 <iframe id="p43iframe" frameBorder=0
1924 style="width:100%;height:calc(100vh - 245px);max-height:calc(100vh - 245px)"></iframe>
1925 </div>
1926 <div id=p50 style="display:none">
1927 <div id="p50title" class="d-flex align-items-center">
1928 <div class="fs-4 fw-bold">My User Groups</div>
1929 </div>
1930 <table class="pTable">
1931 <tr>
1932 <td class="style14 d-flex align-items-center flex-wrap p-1">
1933 <div id="p50userGroupOps" class="d-flex align-items-center">
1934 <input type=button id=UsersGroupsSelectAllButton class="btn btn-secondary btn-sm me-1" onclick="p50usersSelectallButtonFunction()" value="Select All" />
1935 <input type=button id=UsersGroupsGroupActionButton class="btn btn-primary btn-sm me-1" disabled="disabled" value="Group Action" onclick=p50usersGroupActionFunction() />
1936 <input id=NewUserGroupButton type=button class="btn btn-success btn-sm me-1" onclick=showCreateUserGroupDialog(1) value="New Group..." />
1937 <input id=DuplicateUserGroupButton type=button class="btn btn-primary button-outline-success btn-sm me-1" style=display:none onclick=showCreateUserGroupDialog(2) value="Duplicate Group..." />
1938 </div>
1939 </td>
1940 </tr>
1941 </table>
1942 <div id="p50groups"></div>
1943 </div>
1944 <div id=p51 style="display:none">
1945 <div id=p51title class="d-flex align-items-center">
1946 <div id="p51BackButton" class="pe-2">
1947 <i class="fa-solid fa-square-caret-left fa-2xl" role="button" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"></i>
1948 </div>
1949 <div class='fs-4 fw-bold'>User Group - <span id=p51groupName></span></div>
1950 </div>
1951 <div id="p51info" style="overflow-y:auto">
1952 <div class="row">
1953 <div class="col">
1954 <div id="p51group"></div>
1955 </div>
1956 <div class="col-auto" style="width: 20px;"></div>
1957 <div class="col-auto" style="width: 200px;">
1958 <picture id="MainUserImage" style="border-width:0px;height:200px;width:200px;float:right">
1959 <source type="image/webp" width="200" height="200"
1960 srcset="images/webp/group-256.webp" />
1961 <img alt="" width="200" height="200" src="images/group-256.png" />
1962 </picture>
1963 <div style="width:100%;text-align:center">
1964 <strong><span id="MainUserState"></span></strong>
1965 </div>
1966 </div>
1967 </div>
1968 <div id=p51group2></div>
1969 <br />
1970 </div>
1971 </div>
1972 <div id=p52 style="display:none">
1973 <div id="p52title" class="d-flex align-items-center">
1974 <div class="fs-4 fw-bold">My User Recordings</div>
1975 </div>
1976 <table class="pTable">
1977 <tr>
1978 <td class="style14 d-flex align-items-center flex-wrap p-1">
1979 <div class="d-flex align-items-center">
1980 <input type=button class="btn btn-primary btn-sm" onclick=refreshRecodings() value="Refresh" />
1981 </div>
1982 <div class="d-flex align-items-center ms-auto">
1983 <input type=button class="btn btn-primary btn-sm" onclick=openRecodringPlayer() value="Open Player..." />
1984 </div>
1985 </td>
1986 </tr>
1987 </table>
1988 <div id=p52recordings style="overflow-y:auto"></div>
1989 </div>
1990 <div id=p60 style="display:none">
1991 <div id="p60title" class="d-flex align-items-center">
1992 <div class="fs-4 fw-bold">My Reports</div>
1993 </div>
1994 <table class="pTable">
1995 <tr>
1996 {{!-- <td class="h1"></td> --}}
1997 <td class="style14">
1998 <div style="float:right;line-height:22px">
1999 <a id="p60downloadReportDiv" style="display:none" href=# onclick=p60downloadReport()><i class="fa-solid fa-download" title="Download Report"></i></a>&nbsp;
2000 </div>
2001 <div>
2002 <button class="btn btn-primary btn-sm me-2 m-1" onclick=generateReportDialog()><i class="fa-regular fa-file-alt"></i> Generate Report...</button>
2003 </div>
2004 </td>
2005 {{!-- <td class="h2"></td> --}}
2006 </tr>
2007 </table>
2008 <div id=p60report style="overflow-y:auto"></div>
2009 </div>
2010 <br id="column_l_bottomgap" />
2011 </div>
2012 <div id="footer">
2013 <div class="footer1">{{{footer}}}</div>
2014 <div class="footer2">
2015 <div class="row">
2016 <div class="col-md-6 d-flex gap-2">
2017 </div>
2018 <div class="col-md-6">
2019 <a id="verifyEmailId2" style="display:none" href=# onclick="account_showVerifyEmail()">Verify Email</a>
2020 &nbsp;<a id="termsLinkFooter" href=terms>Terms &amp; Privacy</a>
2021 </div>
2022 </div>
2023 </div>
2024 </div>
2025 <div class="modal fade" id="xxAddAgentModal" tabindex="-1" aria-hidden="true">
2026 <div class="modal-dialog modal-dialog-centered" id="xxAddAgentModalConf">
2027 <div class="modal-content">
2028 <div class="modal-header" id="dialogHeader">
2029 <h5 class="modal-title text-white" id="xxAddAgentTitle"></h5>
2030 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
2031 </div>
2032 <div class="modal-body" id="xxAddAgentBody">
2033 <!-- The default dialog is 2 but possible to specify others -->
2034 <div id=dialog1>
2035 <div id=id_dialogMessage style=""></div>
2036 </div>
2037 <div id=dialog2 style="">
2038 <div id=id_dialogOptions></div>
2039 </div>
2040 <div id=dialog3 style="">
2041 <div id=d3upload>
2042 <div>File Selection</div>
2043 <select id=d3uploadMode onchange=d3modechange()>
2044 <option value=1>Local file upload</option>
2045 <option value=2>Server file selection</option>
2046 </select>
2047 </div>
2048 <div id=d3localmode style="display:none">
2049 <div>Upload File</div>
2050 <form id=d3localmodeform method=post enctype=multipart/form-data action=uploadfile.ashx
2051 target=fileUploadFrame>
2052 <input type=text id=d3auth name=auth style="display:none" />
2053 <input type=text id=d3filter name=filter style="display:none" />
2054 <input type=text id=d3attrib name=attrib style="display:none" />
2055 <input type=file id=d3localFile name=files onchange=d3setActions() />
2056 <input type=submit id=d3submit style="display:none" />
2057 </form>
2058 </div>
2059 <div id=d3servermode>
2060 <div id=d3serveraction valign=bottom>
2061 <input type=button id=p3FolderUp disabled="disabled" onclick=d3folderup() value="Up" />&nbsp;<span id=p3CurrentFolder></span>
2062 </div>
2063 <div id=d3serverfiles></div>
2064 </div>
2065 </div>
2066 <div id=dialog4 style="">
2067 <input id="d4WrapButton" class="btn btn-primary me-1 mb-1" type="button" value="Wrap On" onclick="d4ToggleWrap()" />
2068 <input id="d4SizeButton" class="btn btn-primary me-1 mb-1" type="button" value="Small" onclick="d4ToggleSize()" />
2069 <input id="d4EncodingButton" class="btn btn-primary me-1 mb-1" type="button" value="Raw" onclick="d4ToggleEncoding()" />
2070 <input id="d4LineBreakButton" class="btn btn-primary me-1 mb-1" type="button" value="Windows" onclick="d4ToggleLineBreak()" />
2071 <textarea id=d4editorarea autocomplete="off" style="height:calc(100vh - 286px);width:100%;overflow:scroll;resize:none;white-space:pre"></textarea>
2072 </div>
2073 <div id=dialog7 class="tab-content" style="">
2074 <div class="nav nav-pills mb-3">
2075 <button id="td7meshkvm" class="nav-link" onclick="changeDesktopSettingsTab(event, 'd7meshkvm')">Agent</button>
2076 <button id="td7rdpkvm" class="nav-link" onclick="changeDesktopSettingsTab(event, 'd7rdpkvm')">RDP</button>
2077 <button id="td7amtkvm" class="nav-link" onclick="changeDesktopSettingsTab(event, 'd7amtkvm')">Intel&reg; AMT</button>
2078 </div>
2079 <div id="d7meshkvm" class="tab-pane">
2080 <!--<h4>Agent Remote Desktop</h4>-->
2081 <div class="form-floating mb-2">
2082 <select id="d7bitmapquality" class="form-select"></select>
2083 <label for="d7bitmapquality">Quality</label>
2084 </div>
2085 <div class="form-floating mb-2">
2086 <select id="d7bitmapscaling" class="form-select">
2087 <option selected=selected value=1024>100%</option>
2088 <option value=896>87.5%</option>
2089 <option value=768>75%</option>
2090 <option value=640>62.5%</option>
2091 <option value=512>50%</option>
2092 <option value=384>37.5%</option>
2093 <option value=256>25%</option>
2094 <option value=128>12.5%</option>
2095 </select>
2096 <label for="d7bitmapscaling">Scaling</label>
2097 </div>
2098 <div class="form-floating mb-2">
2099 <select id="d7framelimiter" class="form-select">
2100 <option selected=selected value=50>Fast</option>
2101 <option value=100>Medium</option>
2102 <option value=400>Slow</option>
2103 <option value=1000>Very slow</option>
2104 </select>
2105 <label for="d7framelimiter">Frame rate</label>
2106 </div>
2107 <div class="form-floating mb-2">
2108 <select id="d7encoding" class="form-select">
2109 <option value=1>JPEG</option>
2110 <option value=2>PNG</option>
2111 <option value=3>TIFF</option>
2112 <option selected=selected value=4>WEBP</option>
2113 </select>
2114 <label for="d7encoding">Encoding</label>
2115 </div>
2116 <fieldset id="d7desktopOtherSettings">
2117 <legend>Other Settings</legend>
2118 <div id="d7otherset2" class="p-2">
2119 <div class="form-check">
2120 <input id="d7deskSwapMouse" class="form-check-input me-2" type="checkbox" />
2121 <label for="d7deskSwapMouse" class="form-check-label">Swap Mouse Buttons</label>
2122 </div>
2123 <div class="form-check">
2124 <input id="d7deskrmw" class="form-check-input me-2" type="checkbox" />
2125 <label for="d7deskrmw" class="form-check-label">Reverse Mouse Wheel</label>
2126 </div>
2127 <div class="form-check">
2128 <input id="d7deskRemoteKeyMap" class="form-check-input me-2" type="checkbox" />
2129 <label for="d7deskRemoteKeyMap" class="form-check-label">Use Remote Keyboard Map</label>
2130 </div>
2131 <div id="d7deskAutoClipboardLabel" class="form-check">
2132 <input id="d7deskAutoClipboard" class="form-check-input me-2" type="checkbox" />
2133 <label for="d7deskAutoClipboard" class="form-check-label">Automatic Clipboard</label>
2134 </div>
2135 <div id="d7deskAutoLockLabel" class="form-check">
2136 <input id="d7deskAutoLock" class="form-check-input me-2" type="checkbox" />
2137 <label for="d7deskAutoLock" class="form-check-label">Lock on Disconnect</label>
2138 </div>
2139 </div>
2140 </fieldset>
2141 </div>
2142 <div id="d7amtkvm" class="tab-pane">
2143 <!--<h4>Intel&reg; AMT Hardware KVM</h4>-->
2144 <div class="form-floating mb-2">
2145 <select id="d7desktopmode" class="form-select">
2146 <option value="1">RLE8, Fastest</option>
2147 <option value="2">RLE16, Recommended</option>
2148 <option value="3">RAW8, Slow</option>
2149 <option value="4">RAW16, Very Slow</option>
2150 </select>
2151 <label for="d7desktopmode">Image Encoding</label>
2152 </div>
2153 <fieldset>
2154 <legend>Other Settings</legend>
2155 <div id="d7otherset" class="p-2">
2156 <div class="form-check">
2157 <input id="d7showfocus" class="form-check-input me-2" type="checkbox" />
2158 <label for="d7showfocus" class="form-check-label">Show Focus Tool</label>
2159 </div>
2160 <div class="form-check">
2161 <input id="d7showcursor" class="form-check-input me-2" type="checkbox" />
2162 <label for="d7showcursor" class="form-check-label">Show Local Mouse Cursor</label>
2163 </div>
2164 <div class="form-check">
2165 <input id="d7localKeyMap" class="form-check-input me-2" type="checkbox" />
2166 <label for="d7localKeyMap" class="form-check-label">Local Keyboard Map</label>
2167 </div>
2168 <div class="form-check">
2169 <input id="d7kvmrmw" class="form-check-input me-2" type="checkbox" />
2170 <label for="d7kvmrmw" class="form-check-label">Reverse Mouse Wheel</label>
2171 </div>
2172 </div>
2173 </fieldset>
2174 </div>
2175 <div id="d7rdpkvm" class="tab-pane">
2176 <!--<h4>Remote Desktop Protocol</h4>-->
2177 <div class="form-floating mb-2">
2178 <select id="d7rdpsize" class="form-select">
2179 <option notransval=1 value="canvas">Canvas Size</option>
2180 <option notransval=1 value="browser">Browser Size</option>
2181 <option notransval=1 value="screen">Screen Size</option>
2182 <option notransval=1 value="640x480">640x480</option>
2183 <option notransval=1 value="1024x768">1024x768</option>
2184 <option notransval=1 value="1280x800">1280x800</option>
2185 <option notransval=1 value="1440x900">1440x900</option>
2186 <option notransval=1 value="1600x900">1600x900</option>
2187 <option notransval=1 value="1680x1050">1680x1050</option>
2188 <option notransval=1 value="1920x1080">1920x1080</option>
2189 </select>
2190 <label for="d7rdpsize">Display Size</label>
2191 </div>
2192 <fieldset>
2193 <legend>Options</legend>
2194 <div id="d7rdpflags" class="p-2">
2195 <div class="form-check">
2196 <input id="d7rdp1" class="form-check-input me-2" type="checkbox" />
2197 <label for="d7rdp1" class="form-check-label">Disable Wallpaper</label>
2198 </div>
2199 <div class="form-check">
2200 <input id="d7rdp2" class="form-check-input me-2" type="checkbox" />
2201 <label for="d7rdp2" class="form-check-label">Disable Full Window Drag</label>
2202 </div>
2203 <div class="form-check">
2204 <input id="d7rdp3" class="form-check-input me-2" type="checkbox" />
2205 <label for="d7rdp3" class="form-check-label">Disable Menu Animations</label>
2206 </div>
2207 <div class="form-check">
2208 <input id="d7rdp4" class="form-check-input me-2" type="checkbox" />
2209 <label for="d7rdp4" class="form-check-label">Disable Theming</label>
2210 </div>
2211 <div class="form-check">
2212 <input id="d7rdp6" class="form-check-input me-2" type="checkbox" />
2213 <label for="d7rdp6" class="form-check-label">Disable Cursor Shadow</label>
2214 </div>
2215 <div class="form-check">
2216 <input id="d7rdp7" class="form-check-input me-2" type="checkbox" />
2217 <label for="d7rdp7" class="form-check-label">Disable Cursor Settings</label>
2218 </div>
2219 <div class="form-check">
2220 <input id="d7rdp8" class="form-check-input me-2" type="checkbox" />
2221 <label for="d7rdp8" class="form-check-label">Enable Font Smoothing</label>
2222 </div>
2223 <div class="form-check">
2224 <input id="d7rdp9" class="form-check-input me-2" type="checkbox" />
2225 <label for="d7rdp9" class="form-check-label">Enable Desktop Composision</label>
2226 </div>
2227 <div class="form-check">
2228 <input id="d7rdpclip" class="form-check-input me-2" type="checkbox" />
2229 <label for="d7rdpclip" class="form-check-label">Automatic Clipboard</label>
2230 </div>
2231 <div class="form-check">
2232 <input id="d7rdpsmb" class="form-check-input me-2" type="checkbox" />
2233 <label for="d7rdpsmb" class="form-check-label">Swap Mouse Buttons</label>
2234 </div>
2235 <div class="form-check">
2236 <input id="d7rdprmw" class="form-check-input me-2" type="checkbox" />
2237 <label for="d7rdprmw" class="form-check-label">Reverse Mouse Wheel</label>
2238 </div>
2239 </div>
2240 </fieldset>
2241 </div>
2242 </div>
2243 </div>
2244 <div class="modal-footer">
2245 <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" id="idx_dlgCancelButton">Cancel</button>
2246 <button type="button" class="btn btn-primary" id="idx_dlgOkButton" >OK</button>
2247 <button type="button" class="btn btn-danger" id="idx_dlgDeleteButton" style="display:none">Delete</button>
2248 <!-- <div id="idx_dlgMoreButtons">
2249 <a href="#" id="idx_dlgMoreButtons1" class="btn btn-primary"
2250 title="Toggle advanced options" onclick="MoreToggle(true)">&#x25BC;</a>
2251 <a href="#" id="idx_dlgMoreButtons2" class="btn btn-primary"
2252 title="Toggle advanced options" onclick="MoreToggle(false)">&#x25B2;</a>
2253 </div> -->
2254 </div>
2255 </div>
2256 </div>
2257 </div>
2258 <iframe name="fileUploadFrame" style="display:none"></iframe>
2259 <form style="display:none" method=post action=uploadfile.ashx enctype=multipart/form-data
2260 target=fileUploadFrame><input id=p5fileDragName name="name" /><input id=p5fileDragAuthCookie
2261 name="auth" /><input id=p5fileDragSize name="size" /><input id=p5fileDragType name="type" /><input
2262 id=p5fileDragData name="data" /><input id=p5fileDragLink name="link"
2263 class="btn btn-outline-success" /><input type=submit id=p5loginSubmit2 style="display:none"
2264 class="btn btn-outline-success" /></form>
2265 <form style="display:none" method=post action=uploadnodefile.ashx enctype=multipart/form-data
2266 target=fileUploadFrame><input id=p13fileDragName name="name" class="btn btn-outline-success" /><input
2267 id=p13fileDragSize name="size" class="btn btn-outline-success" /><input id=p13fileDragType name="type"
2268 class="btn btn-outline-success" /><input id=p13fileDragData name="data"
2269 class="btn btn-outline-success" /><input id=p13fileDragLink name="link"
2270 class="btn btn-outline-success" /><input type=submit id=p13loginSubmit2 style="display:none"
2271 class="btn btn-outline-success" /></form>
2272
2273
2274 <audio id="chimes"><source src="sounds/chimes.mp3" type="audio/mp3" /></audio>
2275 <iframe style=display:none name="fileDownloadFrame"></iframe>
2276 </div>
2277 <script type="text/javascript">
2278 'use strict';
2279 var random = '{{{randomlength}}}' // Random length string for BREACH mitigation
2280
2281 // Process server-side web state
2282 var webState = '{{{webstate}}}';
2283 if (webState != '') { webState = JSON.parse(decodeURIComponent(webState)); }
2284 if ((webState == null) || (typeof webState != 'object')) { webState = {}; }
2285 for (var i in webState) { try { localStorage.setItem(i, webState[i]); } catch (ex) { } }
2286 if (webState && !webState.loctag) { try { delete localStorage.removeItem('loctag'); } catch (ex) { } }
2287
2288 var args, urlargs;
2289 var autoReconnect = true;
2290 var powerStatetable = ['', "Powered", "Sleep", "Sleep", "Sleep", "Hibernating", "Power off", "Present"];
2291 var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected", "Intel&reg; AMT Connected"];
2292 var agentsStr = ["Unknown", "Windows 32bit console", "Windows 64bit console", "Windows 32bit service", "Windows 64bit service", "Linux 32bit", "Linux 64bit", "MIPS", "XENx86", "Android", "Linux ARM", "macOS x86-32bit", "Android x86", "PogoPlug ARM", "Android", "Linux Poky x86-32bit", "macOS x86-64bit", "ChromeOS", "Linux Poky x86-64bit", "Linux NoKVM x86-32bit", "Linux NoKVM x86-64bit", "Windows MinCore console", "Windows MinCore service", "NodeJS", "ARM-Linaro", "ARMv6l / ARMv7l", "ARMv8 64bit", "ARMv6l / ARMv7l / NoKVM", "MIPS24KC (OpenWRT)", "Apple Silicon", "FreeBSD x86-64", "Unknown", "Linux ARM 64 bit", "Alpine Linux x86 64 Bit (MUSL)", "Assistant (Windows)", "Armada370 - ARM32/HF (libc/2.26)", "OpenWRT x86-64", "OpenBSD x86-64", "Unknown", "Unknown", "MIPSEL24KC (OpenWRT)", "ARMADA/CORTEX-A53/MUSL (OpenWRT)", "Windows ARM 64bit console", "Windows ARM 64bit service", "ARMVIRT32 (OpenWRT)", "RISC-V 64bit"];
2293 var agentsStrNoAgent = ['', '', '', '', "Windows", '', "Linux", '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', "Apple"];
2294 var domainStates = ["None", "Azure AD", "On-Premises AD", "Hybrid AD", "Microsoft Account"];
2295 var sort = 0;
2296 var searchFocus = 0;
2297 var mapSearchFocus = 0;
2298 var userSearchFocus = 0;
2299 var consoleFocus = 0;
2300 var showRealNames = false;
2301 var meshserver = null;
2302 var meshes = {};
2303 var loginTokens = {};
2304 var meshcount = 0;
2305 var nodes = null;
2306 var usergroups = null;
2307 var filetree = {};
2308 var userinfo = null;
2309 var serverinfo = null;
2310 var events = [];
2311 var users = null;
2312 var wssessions = null;
2313 var stars = {}; // Devices that have been "stared" by the user.
2314 var nodeShortIdent = 0;
2315 var desktop;
2316 var desktopsettings = { encoding: 2, showfocus: false, showmouse: true, showcad: true, quality: 40, scaling: 1024, framerate: 50, localkeymap: false, swapmouse: false, remotekeymap: false, autoclipboard: false, autolock: false, agentencoding: 4 };
2317 var multidesktopsettings = { quality: 20, scaling: 128, framerate: 1000, agentencoding: 4 };
2318 var terminal;
2319 var files;
2320 var debugLevel = parseInt('{{{debuglevel}}}');
2321 var features = parseInt('{{{features}}}');
2322 var features2 = parseInt('{{{features2}}}');
2323 var features3 = parseInt('{{{features3}}}');
2324 var sessionTime = parseInt('{{{sessiontime}}}');
2325 var webRelayPort = parseInt('{{{webRelayPort}}}');
2326 var webRelayDns = '{{{webRelayDns}}}';
2327 var hidePowerTimeline = '{{{hidePowerTimeline}}}';
2328 var showNotesPanel = '{{{showNotesPanel}}}';
2329 var sessionRefreshTimer = null;
2330 var domain = '{{{domain}}}';
2331 var domainUrl = '{{{domainurl}}}';
2332 var authCookie = '{{{authCookie}}}';
2333 var authRelayCookie = '{{{authRelayCookie}}}';
2334 var logoutControls = JSON.parse(decodeURIComponent('{{{logoutControls}}}'));
2335 var authCookieRenewTimer = null;
2336 var multiDesktop = {};
2337 var serverPublicNamePort = '{{{serverDnsName}}}:{{{serverPublicPort}}}';
2338 var amtScanResults = null;
2339 var debugmode = 0;
2340 var windowsBrowser = detectWindowsBrowser();
2341 var attemptWebRTC = ((features & 128) != 0);
2342 var webrtcconfiguration = '{{{webrtcconfig}}}';
2343 if (webrtcconfiguration == '') { webrtcconfiguration = null; } else { try { webrtcconfiguration = JSON.parse(decodeURIComponent(webrtcconfiguration)); } catch (ex) { console.log('Invalid WebRTC config: "' + webrtcconfiguration + '".'); webrtcconfiguration = null; } }
2344 var passRequirements = '{{{passRequirements}}}';
2345 if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); }
2346 var customui = '{{{customui}}}';
2347 if (customui != '') { customui = JSON.parse(decodeURIComponent(customui)); } else { customui = null; }
2348 var deskAspectRatio = 0;
2349 try { deskAspectRatio = parseInt(getstore('deskAspectRatio', '0')); } catch (ex) { }
2350 var uiMode = parseInt(getstore('uiMode', 1));
2351 var webPageStackMenu = false;
2352 var webPageFullScreen = true;
2353 var nightMode = setNightMode();
2354 var footerBar = (getstore('footerBar', '1') == '1');
2355 var sessionActivity = Date.now();
2356 var updateSessionTimer = null;
2357 var pluginHandlerBuilder = {{{pluginHandler}}};
2358 var pluginHandler = null;
2359 if (pluginHandlerBuilder != null) { pluginHandler = new pluginHandlerBuilder(); }
2360 var installedPluginList = null;
2361 var goBackStack = [];
2362 var CollapsedGroups = {};
2363 var collapseGroups = '{{{collapseGroups}}}';
2364 try { CollapsedGroups = JSON.parse(getstore('_collapse', '{}')); } catch (ex) { }
2365 var deviceViewSettings = {};
2366 try { deviceViewSettings = JSON.parse(getstore('_deviceViewSettings', '{}')); } catch (ex) { }
2367 var userViewSettings = {};
2368 try { userViewSettings = JSON.parse(getstore('_usersViewSettings', '{}')); } catch (ex) { }
2369 var xterm = null;
2370 var xtermfit = null;
2371 var xtermimage = null;
2372 var xtermResizeTimer = null;
2373 var miscState = {};
2374 var checkedNodeids = {};
2375 var deskKeyboardShortcuts = [];
2376 var deskKeyboardStrings = [];
2377 var deskLastClipboardSent = null;
2378 var deskLastClipboardReceived = null;
2379 var requestedLastConnects = false;
2380 var devicePagingState = null;
2381
2382 // Console Message Display Timers
2383 var p11DeskConsoleMsgTimer = null;
2384 var p12TermConsoleMsgTimer = null;
2385 var p13FilesConsoleMsgTimer = null;
2386
2387 //
2388 // INSTALLED SOFTWARE
2389 //
2390 var installedAppsData = null; // Gespeicherte App-Daten
2391 var installedAppsLoading = false; // Ladestatus
2392 var showStoreApps = false; // Store Apps anzeigen?
2393
2394 /*
2395 // Check browser dark mode preference
2396 var prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
2397 var prefersLightScheme = window.matchMedia('(prefers-color-scheme: light)').matches;
2398 console.log(prefersDarkScheme, prefersLightScheme);
2399 var mql = window.matchMedia('(prefers-color-scheme: dark)');
2400 mql.addEventListener('change', function() { console.log('Dark Change'); });
2401 */
2402
2403 // Check if WebP is supported
2404 var webpSupport = false;
2405 check_webp_feature('lossy', function (f, x) {
2406 webpSupport = x;
2407 if (!x) {
2408 d7encoding.options[1].disabled = true;
2409 d7encoding.value = 1;
2410 }
2411 });
2412
2413 function startup() {
2414 if ((features & 32) == 0) {
2415 // Guard against other site's top frames (web bugs).
2416 var loc = null;
2417 try { loc = top.location.toString().toLowerCase(); } catch (e) { }
2418 if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
2419 }
2420
2421 // Fetch URL arguments & do sanitation
2422 urlargs = parseUriArgs();
2423 if (urlargs.key != null) { urlargs.key = "" + urlargs.key; }
2424 if (urlargs.key && (isAlphaNumeric(urlargs.key) == false)) { delete urlargs.key; }
2425 if (urlargs.locale && (isAlphaNumeric(urlargs.locale) == false)) { delete urlargs.locale; }
2426 delete urlargs.user;
2427 delete urlargs.pass;
2428 delete urlargs.viewmode;
2429 delete urlargs.gotonode;
2430 delete urlargs.gotodevicename;
2431 delete urlargs.gotodevicername;
2432 delete urlargs.gotodeviceip;
2433 delete urlargs.gotomesh;
2434 delete urlargs.gotouser;
2435 delete urlargs.gotougrp;
2436
2437 // Fix links if a loginKey is used
2438 if (urlargs.key) { Q('termsLinkFooter').href += '?key=' + urlargs.key; }
2439
2440 // Check if we are in debug mode
2441 args = parseUriArgs();
2442 if (args.key && (isAlphaNumeric(args.key) == false)) { delete args.key; }
2443 if (args.locale && (isAlphaNumeric(args.locale) == false)) { delete args.locale; }
2444
2445 if (!args.locale) { var x = getstore('loctag', 0); if ((x != null) && (x != '*')) { args.locale = x; } }
2446 debugmode = args.debug;
2447
2448 //attemptWebRTC = false; // For now, default WebRTC off unless we set it in the URL.
2449 if (args.webrtc != null) { attemptWebRTC = (args.webrtc == 1); }
2450
2451 QV('p13AutoConnect', debugmode); // Files
2452 QV('autoconnectbutton2', debugmode); // Terminal
2453 QV('autoconnectbutton1', debugmode); // Desktop
2454 //QV('DeskClip', debugmode); // Clipboard feature, not completed so show in in debug mode only.
2455
2456 if (nightMode) {
2457 document.documentElement.setAttribute('data-bs-theme', 'dark');
2458 QC('body').add('night'); QS('body')['background-color'] = '#000';
2459 }
2460 toggleFullScreen();
2461
2462 // Setup stared devices
2463 try { stars = JSON.parse(getstore('stars', '{}')); } catch (ex) { }
2464
2465 // Setup page visuals
2466 var hide = 0;
2467 var globalHide = parseInt('{{{hide}}}');
2468 if (globalHide || args.hide) {
2469 if (args.hide) { hide = parseInt(args.hide); }
2470 if (globalHide) { hide = (hide | globalHide); }
2471 }
2472 args.hide = hide;
2473 QV('uiViewButton5', !(args.hide & 4)); // Hide the footer toggle button if footer is hidden anyway.
2474 QV('toggleFooterMenuItem', !(args.hide & 4)); // Hide the footer toggle menu item if footer is hidden anyway.
2475 QV('toggleNightMenuItem', ((features2 & 0x00100000) == 0) && ((features2 & 0x00200000) == 0)); // Hide night mode toggle if permanent night (1) or day (2) mode is set
2476 //QV('toggleModernUIMenuItem', ((features2 & 0x80000000) == 0)); // Show only if allowed and not forced siteStyle=3
2477 updateMobileTopBarVisibility();
2478 adjustPanels();
2479
2480 setTimeout(updateUserDropdownVisibility, 100);
2481
2482 // Setup the context menu
2483 document.onclick = function (e) { hideContextMenu(); }
2484 document.onkeypress = ondockeypress;
2485 document.onkeydown = ondockeydown;
2486 document.onkeyup = ondockeyup;
2487 //window.addEventListener('focus', ondocfocus, false);
2488 window.addEventListener('blur', ondocblur, false);
2489 window.onresize = function () {
2490 hideContextMenu();
2491 mainUpdate(512);
2492 browserfullscreen = isBrowserFullscreen();
2493 QV('DeskESC', browserfullscreen);
2494 if ((xtermfit != null) && (xxcurrentView == 12)) { xtermfit.fit(); }
2495 }
2496 setTimeout(function () {
2497 if (urlargs.filter) { Q('SearchInput').value = urlargs.filter; Q('KvmSearchInput').value = urlargs.filter; }
2498 mainUpdate(512);
2499 }, 200);
2500
2501 // open notes markdown links in new tab
2502 if (DOMPurify) {
2503 DOMPurify.addHook('afterSanitizeAttributes', function (node) {
2504 if ('target' in node) {
2505 node.setAttribute('target', '_blank');
2506 node.setAttribute('rel', 'noopener noreferrer');
2507 }
2508 if (!node.hasAttribute('target') && (node.hasAttribute('xlink:href') || node.hasAttribute('href'))) {
2509 node.setAttribute('xlink:show', 'new');
2510 }
2511 });
2512 }
2513
2514 // Show the modern ui switcher
2515 QV('textnewui', ((features2 & 0x40000000) == 0) ? false : true);
2516
2517 // Show Connect All and Disconnect All
2518 QV('KvmDesktopButtons', ((features3 & 0x00000001) == 0) ? true : false);
2519
2520 // Connect to the mesh server
2521 meshserver = MeshServerCreateControl(domainUrl);
2522 meshserver.onStateChanged = onStateChanged;
2523 meshserver.onMessage = onMessage;
2524 meshserver.trace = args.trace;
2525 meshserver.Start();
2526
2527 // Setup page controls
2528 Q('sortselect').selectedIndex = sort = getstore('sort', 0);
2529 Q('sizeselect').selectedIndex = getstore('viewsize', 1);
2530 Q('KvmSearchInput').value = Q('SearchInput').value = getstore('_search', '');
2531 showRealNames = (getstore('showRealNames', 0) == 1);
2532 Q('RealNameCheckBox').checked = showRealNames;
2533 Q('DevFilterSelect').value = getstore('devFilterSelect', 0);
2534 Q('viewselect').value = getstore('deviceView', 1);
2535 Q('DeskControl').checked = (getstore('DeskControl', 1) == 1);
2536 QV('accountChangeEmailAddressSpan', (features & 0x200000) == 0);
2537
2538 // Display the page devices
2539 mainUpdate(3)
2540 for (var j = 1; j < 5; j++) { Q('devViewButton' + j).classList.remove('viewSelectorSel'); }
2541 Q('devViewButton' + Q('viewselect').value).classList.add('viewSelectorSel');
2542
2543 // Setup upload drag & drop for server files
2544 Q('p5filetable').addEventListener('drop', p5fileDragDrop, false);
2545 Q('p5filetable').addEventListener('dragover', p5fileDragOver, false);
2546 Q('p5filetable').addEventListener('dragleave', p5fileDragLeave, false);
2547 //Q('p5fileCatchAllInput').addEventListener('drop', p5fileDragDrop, false);
2548 //Q('p5fileCatchAllInput').addEventListener('dragover', p5fileDragOver, false);
2549 //Q('p5fileCatchAllInput').addEventListener('dragleave', p5fileDragLeave, false);
2550
2551 // Setup drag & drop for device desktop
2552 Q('deskarea0').addEventListener('drop', p11fileDragDrop, false);
2553 Q('deskarea0').addEventListener('dragover', p11fileDragOver, false);
2554 Q('deskarea0').addEventListener('dragleave', p11fileDragLeave, false);
2555
2556 // Setup upload drag & drop for device files
2557 Q('p13filetable').addEventListener('drop', p13fileDragDrop, false);
2558 Q('p13filetable').addEventListener('dragover', p13fileDragOver, false);
2559 Q('p13filetable').addEventListener('dragleave', p13fileDragLeave, false);
2560
2561 // Timeline update interval
2562 setInterval(updateDeviceTimeline, 120000); // Check every 2 minutes
2563
2564 // Load desktop settings
2565 var t = null;
2566 try { t = localStorage.getItem('desktopsettings'); } catch (ex) { }
2567 if (t != null) { desktopsettings = JSON.parse(t); }
2568 t = null;
2569 try { t = localStorage.getItem('multidesktopsettings'); } catch (ex) { }
2570 if (t != null) { multidesktopsettings = JSON.parse(t); }
2571 applyDesktopSettings();
2572
2573 // Terminal special keys
2574 var x = '';
2575 for (var c = 1; c < 27; c++) x += '<option value=\'' + c + '\'>' + "Ctrl" + '-' + String.fromCharCode(64 + c) + ' (' + c + ')</option>';
2576 QH('specialkeylist', x);
2577
2578 // Setup server stats panels
2579 setupGeneralServerStats();
2580 setupServerTimelineStats();
2581
2582 // Setup the user interface in the right mode
2583 userInterfaceSelectMenu();
2584
2585 // Setup Mesh summary panel
2586 setupMeshSummaryStats();
2587
2588 // If SSPI or LDAP authentication not used, allow batch account creation.
2589 QV('p4UserBatchCreate', (features & 0x00080000) == 0);
2590 QV('p4MobileUserBatchCreate', (features & 0x00080000) == 0);
2591
2592 // Set the file editor
2593 d4EditWrapVal = Number(getstore('editorWrap', 0));
2594 d4EditSizeVal = Number(getstore('editorSize', 0));
2595 d4EditEncodingVal = Number(getstore('editorEncoding', 0));
2596 d4EditLineBreakVal = Number(getstore('editorLineBreak', 0));
2597 d4ToggleWrap(true);
2598 d4ToggleSize(true);
2599 d4ToggleEncoding(true);
2600 d4ToggleLineBreak(true);
2601 if (pluginHandler != null) pluginHandler.callHook('onWebUIStartupEnd');
2602
2603 // Deleted non-english style and fix all topbar titles
2604 if ((['en', 'ko', 'ja', 'zh-chs', 'zh-cht'].indexOf('{{{lang}}}') == -1) && ('{{{lang}}}'.toLowerCase() != '')) { QC('body').add('nonenglish'); }
2605 var elements = document.getElementsByClassName('topbar_td');
2606 for (var i in elements) { if (elements[i].innerHTML) { elements[i].innerHTML = elements[i].innerHTML.split(' ').join('&nbsp;'); } }
2607
2608 // Custom UI
2609 if (customui != null) {
2610 if (customui.devicesbarbuttons) {
2611 var x = '&nbsp;';
2612 for (var i in customui.devicesbarbuttons) {
2613 var disabled = ((customui.devicesbarbuttons[i].selection == 'one') || (customui.devicesbarbuttons[i].selection == 'many')) ? 'disabled' : '';
2614 x += '<input id="cui:' + i + '" type="button" class="btn btn-secondary" value="' + customui.devicesbarbuttons[i].name + '" ' + disabled + ' onclick=customUIAction(event,"devicesbarbuttons") />&nbsp;';
2615 }
2616 QH('devsCustomUIBar', x);
2617 }
2618 if (customui.desktopbuttons) {
2619 var x = '&nbsp;';
2620 for (var i in customui.desktopbuttons) { x += '<input id="cui:' + i + '" type="button" class="btn btn-secondary" value="' + customui.desktopbuttons[i].name + '" style="float:left" onclick=customUIAction(event,"desktopbuttons") />&nbsp;'; }
2621 QH('desktopCustomUiButtons', x);
2622 }
2623 if (customui.terminalbuttons) {
2624 var x = '&nbsp;';
2625 for (var i in customui.terminalbuttons) { x += '<input id="cui:' + i + '" type="button" class="btn btn-secondary" value="' + customui.terminalbuttons[i].name + '" style="float:left" onclick=customUIAction(event,"terminalbuttons") />&nbsp;'; }
2626 QH('terminalCustomUiButtons', x);
2627 }
2628 if (customui.filesbuttons) {
2629 var x = '&nbsp;';
2630 for (var i in customui.filesbuttons) { x += '<input id="cui:' + i + '" type="button" class="btn btn-secondary" value="' + customui.filesbuttons[i].name + '" style="float:left" onclick=customUIAction(event,"filesbuttons") />&nbsp;'; }
2631 QH('filesCustomUiButtons', x);
2632 }
2633 }
2634
2635 // Session Refresh Timer
2636 if (sessionTime >= 10) { sessionRefreshTimer = setTimeout(refreshCookieSession, Math.round((sessionTime * 60000) * 0.8)); }
2637
2638 // Set the user's desktop shortcut keys
2639 deskKeyboardShortcuts = [];
2640 var deskKeyboardShortcutsStr = getstore('deskKeyShortcuts', '0x0A002E,0x100000,0x100028,0x100026,0x10004C,0x10004D,0x11004D,0x100052,0x020073,0x080057,0x020009,0x100025,0x100027').split(',');
2641 for (var i in deskKeyboardShortcutsStr) { if (deskKeyboardShortcutsStr[i] != "") { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); } }
2642 updateDeskShortcutKeys();
2643
2644 // Set the user's desktop strings
2645 try { deskKeyboardStrings = JSON.parse(getstore('deskStrings', '[]')); } catch (ex) { }
2646 updateDesktopStrings();
2647
2648 // Override the collapse button text
2649 updateCollapseAllButton();
2650
2651 // Make the dialog box movable
2652 dialogBoxDrag();
2653
2654 // Fix links
2655 if (urlargs.key) { Q('p6backuplink').href += '?key=' + urlargs.key; }
2656
2657 // Hide night mode button if needed
2658 QV('uiViewButton4', (features2 & 0x00300000) == 0);
2659
2660 // Fix HTML words that in english have two or more meanings
2661 Q('DeskType').value = multiTranslate("[KeyboardTyping]|Type");
2662
2663 // Show the "Scroll to top" button
2664 QV('ScrollToTopButton', (features2 & 0x08000000) != 0);
2665 }
2666
2667 function refreshCookieSession() {
2668 var xdr = null;
2669 try { xdr = new XDomainRequest(); } catch (e) { }
2670 if (!xdr) xdr = new XMLHttpRequest();
2671 xdr.open('GET', window.location.origin + domainUrl + 'refresh.ashx');
2672 xdr.timeout = 15000;
2673 xdr.onload = function () { sessionRefreshTimer = setTimeout(refreshCookieSession, Math.round((sessionTime * 60000) * 0.8)); };
2674 xdr.onerror = xdr.ontimeout = function () { sessionRefreshTimer = null; };
2675 xdr.send();
2676 }
2677
2678 // Generic handling of custom actions
2679 function customUIAction(e, section) {
2680 var id = e.srcElement.id;
2681 if (id.startsWith('cui:') == false) return;
2682 var info = customui[section][id.substring(4)];
2683 if (info == null) return;
2684 if (section == 'devicesbarbuttons') {
2685 var elements = document.getElementsByClassName('DeviceCheckbox'), selectedDevices = [];
2686 for (var i = 0; i < elements.length; i++) { if (elements[i].checked === true) { selectedDevices.push(elements[i].defaultValue.substring(6)); } }
2687 if (typeof info.action == 'string') {
2688 if (info.action == 'event') { meshserver.send({ action: 'uicustomevent', section: section, element: id.substring(4), selectedDevices: selectedDevices, logmsg: info.logmsg }); }
2689 if (info.action.startsWith('dialog:')) { showCustomUiDialog(info.action.substring(7), { section: section, element: id.substring(4), selectedDevices: selectedDevices }); }
2690 if (info.deselectdevices) { for (var i = 0; i < elements.length; i++) { elements[i].checked = false; } checkedNodeids = {}; p1updateInfo(); }
2691 }
2692 }
2693 if ((section == 'devicebuttons') || (section == 'desktopbuttons') || (section == 'terminalbuttons') || (section == 'filesbuttons')) {
2694 var selectedDevices = [currentNode._id];
2695 if (typeof info.action == 'string') {
2696 if (info.action == 'event') { meshserver.send({ action: 'uicustomevent', section: section, element: id.substring(4), selectedDevices: selectedDevices, logmsg: info.logmsg }); }
2697 if (info.action.startsWith('dialog:')) { showCustomUiDialog(info.action.substring(7), { section: section, element: id.substring(4), selectedDevices: selectedDevices }); }
2698 }
2699 }
2700 }
2701
2702 // Display a generic custom UI dialog
2703 function showCustomUiDialog(name, tag) {
2704 if ((customui == null) || (customui.dialogs == null) || (typeof customui.dialogs[name] != 'object')) return;
2705 var x = '', dialog = customui.dialogs[name], buttons = 3;
2706 if (typeof dialog.text == 'string') { x += '<div style=margin-bottom:8px>' + dialog.text + '</div>'; }
2707 if (typeof dialog.buttons == 'number') { buttons = dialog.buttons; }
2708 if (typeof dialog.elements == 'object') {
2709 for (var i in dialog.elements) {
2710 var elem = dialog.elements[i];
2711 if (elem.type == 'text') { x += addHtmlFormFloating(elem.name, '<input id=cui:' + i + ' class=form-control autocomplete=off />'); }
2712 if (elem.type == 'textarea') {
2713 if (elem.name == null) {
2714 x += '<textarea id=cui:' + i + ' style=width:356px;resize:none;height:100px autocomplete=off></textarea>';
2715 } else {
2716 x += addHtmlFormFloating(elem.name, '<textarea class=form-control id=cui:' + i + ' style=resize:none;height:100px autocomplete=off></textarea>');
2717 }
2718 }
2719 if (elem.type == 'droplist') {
2720 var y = '';
2721 for (var j in elem.options) { y += '<option value="' + j + '">' + EscapeHtml(elem.options[j]) + '</option>'; }
2722 x += addHtmlFormFloating(elem.name, '<select class=form-select id="cui:' + i + '" class=form-select autocomplete=off />' + y + '</select>');
2723 }
2724 }
2725 }
2726 setModalContent('xxAddAgent', dialog.title, x);
2727 showModal('xxAddAgentModal', 'idx_dlgOkButton', () => showCustomUiDialogEx(1, { action: 'uicustomevent', section: 'dialogs', element: name, src: tag, values: {}, logmsg: dialog.logmsg }) );
2728 }
2729
2730 // Handle a generic custom UI dialog event
2731 function showCustomUiDialogEx(b, t) {
2732 if (b != 1) return;
2733 var dialog = customui.dialogs[t.element];
2734 if (typeof dialog.elements == 'object') {
2735 for (var i in dialog.elements) {
2736 var elem = dialog.elements[i];
2737 if ((elem.type == 'droplist') || (elem.type == 'textarea') || (elem.type == 'text')) { t.values[i] = Q('cui:' + i).value; }
2738 }
2739 }
2740 meshserver.send(t);
2741 if (dialog.deselectdevices) {
2742 var elements = document.getElementsByClassName('DeviceCheckbox')
2743 for (var i = 0; i < elements.length; i++) { elements[i].checked = false; } checkedNodeids = {}; p1updateInfo();
2744 }
2745 }
2746
2747 function adjustPanels() {
2748 var hide = args.hide;
2749 if (footerBar == false) { hide |= 4; }
2750 QV('masthead', !(hide & 1));
2751 QV('topbar', !(hide & 2));
2752 QV('footer', !(hide & 4));
2753 if ((hide & 8)) {
2754 QC('p1title').add('d-none');
2755 QC('p2title').add('d-none');
2756 QC('p3title').add('d-none');
2757 QC('p4title').add('d-none');
2758 QC('p5title').add('d-none');
2759 QC('p6title').add('d-none');
2760 QC('p10title').add('d-none');
2761 QC('p11title').add('d-none');
2762 QC('p12title').add('d-none');
2763 QC('p13title').add('d-none');
2764 QC('p14title').add('d-none');
2765 QC('p15title').add('d-none');
2766 QC('p16title').add('d-none');
2767 QC('p17title').add('d-none');
2768 QC('p18title').add('d-none');
2769 QC('p20title').add('d-none');
2770 QC('p21title').add('d-none');
2771 QC('p30title').add('d-none');
2772 QC('p31title').add('d-none');
2773 QC('p40title').add('d-none');
2774 QC('p41title').add('d-none');
2775 QC('p50title').add('d-none');
2776 QC('p51title').add('d-none');
2777 QC('column_l').remove('pt-3');
2778 }
2779 //if (hide & 16) { QV('page_leftbar', false); QS('page_content').left = '0px'; }
2780 if (!footerBar) { QC('body').add('nofooter'); } else { QC('body').remove('nofooter'); }
2781
2782 if (args.hide != 0) {
2783 // Fix the main grid to zero-height elements we want to hide.
2784 if (uiMode == 2) {
2785 QS('container')['grid-template-rows'] = ((hide & 1) ? '0' : '66') + 'px fit-content(48px) auto ' + ((hide & 4) ? '0' : '45') + 'px';
2786 QS('container')['-ms-grid-rows'] = ((hide & 1) ? '0' : '66') + 'px fit-content(48px) auto ' + ((hide & 4) ? '0' : '45') + 'px';
2787 } else {
2788 QS('container')['grid-template-rows'] = ((hide & 1) ? '0' : '66') + 'px ' + ((hide & 2) ? '0' : '24') + 'px auto ' + ((hide & 4) ? '0' : '45') + 'px';
2789 QS('container')['-ms-grid-rows'] = ((hide & 1) ? '0' : '66') + 'px ' + ((hide & 2) ? '0' : '24') + 'px auto ' + ((hide & 4) ? '0' : '45') + 'px';
2790 }
2791 }
2792
2793 // Adjust height of remote desktop, files and Intel AMT
2794 // 1 = Top bar, 2 = Tool Bar, 4 = Bottom Bar, 8 = Tab Title
2795 var xh = (((hide & 1) ? 0 : 66) + ((hide & 2) ? 0 : 24) + ((hide & 4) ? 0 : 45) + ((hide & 8) ? 0 : 60)); // 0 to 195
2796 var xh2 = (uiMode > 1) ? 24 : 0;
2797 QS('p2info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2798 QS('p2info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2799 QS('p3users')['max-height'] = 'calc(100vh - ' + (50 + xh) + 'px)'; // 124
2800 QS('p50groups')['max-height'] = 'calc(100vh - ' + (50 + xh) + 'px)'; // 124
2801 QS('p3events')['height'] = 'calc(100vh - ' + (50 + xh) + 'px)'; // 124
2802 QS('p5filetable')['height'] = 'calc(100vh - ' + (99 + xh) + 'px)'; // 160
2803 QS('p13filetable')['height'] = 'calc(100vh - ' + (127 + xh + xh2) + 'px)'; // 124
2804 QS('serverMainStats')['height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)'; // 110
2805 QS('serverMainStats')['max-height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)'; // 110
2806 QS('xdevices')['max-height'] = 'calc(100vh - ' + (46 + xh) + 'px)'; // 124
2807 QS('xdevicesmap')['max-height'] = 'calc(100vh - ' + (46 + xh) + 'px)'; // 124
2808 QS('p15agentConsole')['height'] = 'calc(100vh - ' + (84 + xh + xh2) + 'px)';
2809 QS('p15agentConsole')['max-height'] = 'calc(100vh - ' + (84 + xh + xh2) + 'px)';
2810 QS('p15agentConsoleText')['height'] = 'calc(100vh - ' + (81 + xh + xh2) + 'px)';
2811 QS('p15agentConsoleText')['max-height'] = 'calc(100vh - ' + (81 + xh + xh2) + 'px)';
2812 var xtermActive = !((args.xterm === 0) || ((terminal != null) && (xterm == null)));
2813 if (fullscreen) {
2814 QS('deskarea3x')['height'] = null;
2815 QS('deskarea3x')['max-height'] = null;
2816 QS('p14iframe')['height'] = null;
2817 QS('p14iframe')['max-height'] = null;
2818 if (xtermActive) {
2819 QS('termarea3x')['height'] = 'calc(100vh - 55px)';
2820 QS('termarea3xdiv')['height'] = 'calc(100vh - 55px)';
2821 QS('termarea3x')['max-width'] = 'calc(1px)';
2822 }
2823 } else {
2824 QS('deskarea3x')['height'] = 'calc(100vh - ' + (74 + xh + xh2) + 'px)';
2825 QS('deskarea3x')['max-height'] = 'calc(100vh - ' + (74 + xh + xh2) + 'px)';
2826 QS('p14iframe')['height'] = 'calc(100vh - ' + (23 + xh + xh2) + 'px)';
2827 QS('p14iframe')['max-height'] = 'calc(100vh - ' + (23 + xh + xh2) + 'px)';
2828 if (xtermActive) {
2829 QS('termarea3x')['height'] = 'calc(100vh - ' + (74 + xh + xh2) + 'px)';
2830 QS('termarea3xdiv')['height'] = 'calc(100vh - ' + (74 + xh + xh2) + 'px)';
2831 QS('termarea3x')['max-width'] = 'calc(1px)';
2832 }
2833 }
2834 QS('p43iframe')['height'] = 'calc(100vh - ' + (84 + xh) + 'px)';
2835 QS('p43iframe')['max-height'] = 'calc(100vh - ' + (84 + xh) + 'px)';
2836 QS('p6info')['height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
2837 QS('p6info')['max-height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
2838 QS('p10info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2839 QS('p10info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2840 QS('p17info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2841 QS('p17info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2842 QS('p18info')['height'] = 'calc(100vh - ' + (120 + xh + xh2) + 'px)';
2843 QS('p18info')['max-height'] = 'calc(100vh - ' + (120 + xh + xh2) + 'px)';
2844 QS('p20main')['height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
2845 QS('p20main')['max-height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
2846 QS('p21main')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2847 QS('p21main')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2848 QS('p30info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2849 QS('p30info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2850 QS('p51info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2851 QS('p51info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2852 QS('p16events')['height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)';
2853 QS('p16events')['max-height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)';
2854 QS('p31events')['height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)';
2855 QS('p31events')['max-height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)';
2856 QS('p41events')['height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
2857 QS('p41events')['max-height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
2858 QS('p52recordings')['height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
2859 QS('p52recordings')['max-height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
2860 QS('p60report')['height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
2861 QS('p60report')['max-height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
2862
2863 // We are looking at a single device, remove all the back buttons
2864 if ((args.hide & 32) || ('{{currentNode}}'.toLowerCase() != '')) {
2865 QV('p10BackButton', false);
2866 QV('p11BackButton', false);
2867 QV('p12BackButton', false);
2868 QV('p13BackButton', false);
2869 QV('p14BackButton', false);
2870 QV('p15BackButton', false);
2871 QV('p16BackButton', false);
2872 QV('p17BackButton', false);
2873 QV('p18BackButton', false);
2874 }
2875 p1updateInfo();
2876 }
2877
2878 // Toggle the web page to full screen
2879 function toggleAspectRatio(toggle) {
2880 if (toggle === 1) { deskAspectRatio = ((deskAspectRatio + 1) % 3); putstore('deskAspectRatio', deskAspectRatio); }
2881 deskAdjust();
2882 }
2883
2884 // If FullScreen, toggle menu to be horizontal or vertical
2885 function toggleStackMenu(toggle) {
2886 if (webPageFullScreen == true) {
2887 if (toggle === 1) {
2888 webPageStackMenu = !webPageStackMenu;
2889 putstore('webPageStackMenu', webPageStackMenu);
2890 }
2891 if (webPageStackMenu == false) {
2892 QC('body').remove('menu_stack');
2893 } else {
2894 QC('body').add('menu_stack');
2895 if (xxcurrentView >= 10) QC('column_l').remove('room4submenu');
2896 }
2897 deskAdjust();
2898 }
2899 }
2900
2901 // Toggle user interface menu
2902 function showUserInterfaceSelectMenu() {
2903 if (xxdialogMode) return;
2904 Q('uiViewButton1').classList.remove('uiSelectorSel');
2905 Q('uiViewButton2').classList.remove('uiSelectorSel');
2906 Q('uiViewButton3').classList.remove('uiSelectorSel');
2907 Q('uiViewButton4').classList.remove('uiSelectorSel');
2908 Q('uiViewButton5').classList.remove('uiSelectorSel');
2909 try { Q('uiViewButton' + uiMode).classList.add('uiSelectorSel'); } catch (ex) { }
2910 QV('uiMenu', (QS('uiMenu').display == 'none'));
2911 //Q('uiViewButton1').focus();
2912 if (nightMode) { Q('uiViewButton4').classList.add('uiSelectorSel'); }
2913 if (footerBar) { Q('uiViewButton5').classList.add('uiSelectorSel'); }
2914 }
2915
2916 // Toggle user dropdown menu
2917 function toggleUserDropdown() {
2918 if (xxdialogMode) return;
2919 var userDropdownMenu = Q('userDropdownMenu');
2920 if (userDropdownMenu) {
2921 var isVisible = (userDropdownMenu.style.display == 'none' || userDropdownMenu.style.display == '');
2922 if (isVisible) {
2923 userDropdownMenu.style.display = 'block';
2924 QV('uiMenu', false);
2925 QV('topMenu', false);
2926 updateNightModeIcon();
2927 updateMobileTopBarVisibility();
2928 } else {
2929 userDropdownMenu.style.display = 'none';
2930 var uiSubmenu = Q('uiSubmenu');
2931 if (uiSubmenu) {
2932 uiSubmenu.style.display = 'none';
2933 }
2934 resetChevronArrow();
2935 }
2936 }
2937 }
2938 function updateUserDropdown() {
2939 if (logoutControls && logoutControls.name) {
2940 var userImageSrc = 'images/user-256.png';
2941 if (userinfo && userinfo.flags && (userinfo.flags & 1)) {
2942 if (userinfo.accountImageRnd == null) { userinfo.accountImageRnd = Math.floor(Math.random() * 9999999999); }
2943 userImageSrc = 'userimage.ashx?rnd=' + userinfo.accountImageRnd;
2944 }
2945
2946 var userImage = Q('userDropdownImage');
2947 if (userImage) {
2948 userImage.src = userImageSrc;
2949 }
2950
2951 var userDropdownButton = Q('userDropdownButton');
2952 if (userDropdownButton) {
2953 userDropdownButton.onclick = function(e) {
2954 e.preventDefault();
2955 e.stopPropagation();
2956 toggleUserDropdown();
2957 return false;
2958 };
2959 }
2960
2961 var userDropdownMenu = Q('userDropdownMenu');
2962 if (userDropdownMenu) {
2963 userDropdownMenu.style.display = 'none';
2964 }
2965 }
2966 }
2967
2968 function toggleUISubmenu(event) {
2969 if (event) {
2970 event.stopPropagation();
2971 event.preventDefault();
2972 }
2973 var uiSubmenu = Q('uiSubmenu');
2974 if (uiSubmenu) {
2975 var isVisible = (uiSubmenu.style.display == 'block');
2976
2977 if (isVisible) {
2978 uiSubmenu.style.display = 'none';
2979 uiSubmenu.classList.remove('show');
2980 var chevronIcon = document.querySelector('.userDropdownUISettings .fa-chevron-right');
2981 if (chevronIcon) {
2982 chevronIcon.classList.remove('rotated');
2983 }
2984 document.removeEventListener('click', closeUISubmenu);
2985 } else {
2986 uiSubmenu.style.display = 'block';
2987 uiSubmenu.classList.add('show');
2988 var chevronIcon = document.querySelector('.userDropdownUISettings .fa-chevron-right');
2989 if (chevronIcon) {
2990 chevronIcon.classList.add('rotated');
2991 }
2992 setTimeout(function() {
2993 document.addEventListener('click', closeUISubmenu);
2994 }, 10);
2995 }
2996 }
2997 }
2998
2999 function closeUISubmenu(event) {
3000 var uiSubmenu = Q('uiSubmenu');
3001 var userDropdown = Q('userDropdown');
3002 if (uiSubmenu && userDropdown) {
3003 if (!uiSubmenu.contains(event.target) && !userDropdown.contains(event.target)) {
3004 uiSubmenu.style.display = 'none';
3005 uiSubmenu.classList.remove('show');
3006 var chevronIcon = document.querySelector('.userDropdownUISettings .fa-chevron-right');
3007 if (chevronIcon) {
3008 chevronIcon.classList.remove('rotated');
3009 }
3010 document.removeEventListener('click', closeUISubmenu);
3011 }
3012 }
3013 }
3014
3015 function userInterfaceSelectMenu(s) {
3016 if (s) { uiMode = s; putstore('uiMode', uiMode); }
3017 webPageFullScreen = (uiMode < 3);
3018 webPageStackMenu = (uiMode > 1);
3019 toggleFullScreen(0);
3020 toggleStackMenu(0);
3021 if (webPageStackMenu && (xxcurrentView >= 10)) { QC('column_l').add('room4submenu'); } else { QC('column_l').remove('room4submenu'); }
3022 updateMobileTopBarVisibility();
3023 adjustPanels();
3024 }
3025
3026 function updateMobileTopBarVisibility() {
3027 try {
3028 var items = document.querySelectorAll('.mobile-menu-item');
3029 for (var i = 0; i < items.length; i++) {
3030 if ((uiMode == 2) || (uiMode == 3)) {
3031 items[i].classList.add('d-none');
3032 } else {
3033 if (!(items[i].classList.contains('users-menu-item') || items[i].classList.contains('files-menu-item') || items[i].classList.contains('server-menu-item'))) {
3034 items[i].classList.remove('d-none');
3035 }
3036 }
3037 }
3038
3039 if ((uiMode != 2) && (uiMode != 3)) {
3040 var serverFeatures = parseInt('{{{serverfeatures}}}');
3041 var siteRights = userinfo.siteadmin;
3042
3043 // Server Menu Item
3044 ((siteRights & 21) && ((serverFeatures & 64) != 0)) ? QC('mobileServerMenuItem').remove('d-none') : QC('mobileServerMenuItem').add('d-none');
3045
3046 // Files Menu Item
3047 (siteRights & 8) ? QC('mobileFilesMenuItem').remove('d-none') : QC('mobileFilesMenuItem').add('d-none');
3048
3049 // Users Menu Item
3050 (((users != null) && ((features & 4) == 0)) || (((userinfo.siteadmin & 512) != 0) && ((features & 0x08000000) != 0))) ? QC('mobileUsersMenuItem').remove('d-none') : QC('mobileUsersMenuItem').add('d-none');
3051 }
3052 } catch (ex) { }
3053 }
3054
3055 function toggleNightMode() {
3056 if (xxdialogMode) return;
3057 var cNightMode = getstore('nightMode', '0');
3058 var x = '<input type=radio id=night0 name=nightmoderadio value=0 ' + ((cNightMode == 0) ? 'checked' : '') + '><label for=night0>' + "Browser default" + '</label><br>';
3059 x += '<input type=radio id=night2 name=nightmoderadio value=2 ' + ((cNightMode == 2) ? 'checked' : '') + '><label for=night2>' + "Light mode" + '</label><br>';
3060 x += '<input type=radio id=night1 name=nightmoderadio value=1 ' + ((cNightMode == 1) ? 'checked' : '') + '><label for=night1>' + "Dark mode" + '</label><br>';
3061 xxdialogButtons = 3;
3062 setModalContent('xxAddAgent', "Night Mode", x);
3063 showModal('xxAddAgentModal', 'idx_dlgOkButton', toggleNightModeEx);
3064
3065 QV('uiMenu', false);
3066 }
3067
3068 function toggleNightModeEx() {
3069 // Save new night mode
3070 var nNightMode = '0';
3071 if (Q('night1').checked) { nNightMode = '1'; }
3072 if (Q('night2').checked) { nNightMode = '2'; }
3073 putstore('nightMode', nNightMode);
3074 setNightMode();
3075 }
3076
3077 if (window.matchMedia) { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', setNightMode); }
3078
3079 function setNightMode() {
3080 // Set night mode
3081 var nNightMode = getstore('nightMode', '0')
3082 nightMode = false;
3083 if ((features2 & 0x00100000) != 0) { nNightMode = '1'; }
3084 if ((features2 & 0x00200000) != 0) { nNightMode = '2'; }
3085 if (nNightMode == '1') { nightMode = true; }
3086 else if ((nNightMode == '0') && (window.matchMedia)) { nightMode = window.matchMedia('(prefers-color-scheme: dark)').matches }
3087 if (nightMode) {
3088 document.documentElement.setAttribute('data-bs-theme', 'dark');
3089 QC('body').add('night'); QS('body')['background-color'] = '#000';
3090 } else {
3091 document.documentElement.setAttribute('data-bs-theme', 'light');
3092 QC('body').remove('night'); QS('body')['background-color'] = '#d3d9d6';
3093 }
3094 return nightMode;
3095 }
3096
3097 function toggleFooterBarMode() {
3098 footerBar = !footerBar;
3099 putstore('footerBar', footerBar ? '1' : '0');
3100 QS('container')['grid-template-rows'] = null;
3101 QS('container')['-ms-grid-rows'] = null;
3102 adjustPanels();
3103 }
3104
3105 // Toggle the web page to full screen
3106 function toggleFullScreen(toggle) {
3107 const leftBar = document.getElementById('page_leftbar');
3108 if (toggle === 1) { webPageFullScreen = !webPageFullScreen; putstore('webPageFullScreen', webPageFullScreen); }
3109 if (webPageFullScreen == false) {
3110 QC('body').remove('menu_stack');
3111 QC('body').remove('fullscreen');
3112 QC('body').remove('arg_hide');
3113 if (xxcurrentView >= 10) QC('column_l').add('room4submenu');
3114 leftBar.classList.add('d-none');
3115 //QV('page_leftbar', false);
3116 } else {
3117 QC('body').add('fullscreen');
3118 if (args.hide & 16) QC('body').add('arg_hide');
3119 QV('page_leftbar', !(args.hide & 16));
3120 QV('MainMenuSpan', !(args.hide & 16));
3121 if (xxcurrentView >= 10) QC('column_l').remove('room4submenu');
3122 leftBar.classList.remove('d-none');
3123 }
3124 mainUpdate(512);
3125 QV('body', true);
3126 }
3127
3128 function saveUserInterfaceMode() {
3129 var nUiViewMode = 3;
3130 if (Q('ui0').checked) { nUiViewMode = 2; }
3131 if (getstore('uiViewMode', 3) != nUiViewMode) {
3132 putstore('uiViewMode', nUiViewMode);
3133 // Check if the URL includes 'sitestyle=' and remove it
3134 var url = new URL(window.location.href);
3135 if (url.searchParams.has('sitestyle')) {
3136 url.searchParams.delete('sitestyle');
3137 window.history.replaceState({}, document.title, url.toString());
3138 }
3139 reload();
3140 }
3141 }
3142
3143 function toggleBootstrapUIMode() {
3144 if (xxdialogMode) return;
3145 var uiViewMode = getstore('uiViewMode', 3);
3146 var x = '<div class=form-check><input type=radio class=form-check-input id=ui0 name=uiradio value=2 ' + ((uiViewMode == 2)?'checked':'') + '><label class=form-check-label for=ui0>' + "Classic" + '</label></div>';
3147 x += '<div class=form-check><input type=radio class=form-check-input id=ui1 name=uiradio value=3 ' + ((uiViewMode == 3)?'checked':'') + '><label class=form-check-label for=ui1>' + "Modern" + '</label></div>';
3148 setModalContent('xxAddAgent', "User Interface", x);
3149 showModal('xxAddAgentModal', 'idx_dlgOkButton', saveUserInterfaceMode);
3150 QV('uiMenu', false);
3151 }
3152
3153 function getNodeFromId(id) { if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; } } return null; }
3154 function reload() {
3155 var x = window.location.href;
3156 if (x.endsWith('/#')) { x = x.substring(0, x.length - 2); }
3157 if (x.endsWith('#')) { x = x.substring(0, x.length - 1); }
3158 window.location.href = x;
3159 }
3160
3161 function onStateChanged(server, state, prevState, errorCode) {
3162 if (state == 0) {
3163 // Control web socket disconnected
3164 setDialogMode(0); // Close any dialog boxes if present
3165 go(0); // Go to disconnection panel
3166
3167 // Clean up
3168 powerTimeline = null;
3169 powerTimelineReq = null;
3170 powerTimelineNode = null;
3171 powerTimelineUpdate = null;
3172 deviceShares = null;
3173 deleteAllNotifications(); // Close and clear notifications if present
3174 hideContextMenu(); // Hide the context menu if present
3175 QV('verifyEmailId2', false);
3176 QV('logoutControl', false);
3177 if (errorCode == 'noauth') { QH('p0span', "Unable to perform authentication"); return; }
3178 if (errorCode == 'invalidorigin') { QH('p0span', "Invalid origin in HTTP request"); return; }
3179 if (prevState == 2) { if (autoReconnect) { setTimeout(serverPoll, 5000); } } else { QH('p0span', "Unable to connect web socket"); }
3180 if (authCookieRenewTimer != null) { clearInterval(authCookieRenewTimer); authCookieRenewTimer = null; }
3181 } else if (state == 2) {
3182 // Fetch list of meshes, nodes, files
3183 meshserver.send({ action: 'usergroups' });
3184 meshserver.send({ action: 'meshes' });
3185 meshserver.send({ action: 'nodes', id: '{{currentNode}}', skip: (devicePagingState == null) ? 0 : devicePagingState.skip });
3186 meshserver.send({ action: 'loginTokens' });
3187 if (pluginHandler != null) { meshserver.send({ action: 'plugins' }); }
3188 if ('{{currentNode}}'.toLowerCase() == '') { meshserver.send({ action: 'files' }); }
3189 if ('{{viewmode}}'.toLowerCase() == '') { go(1); }
3190 authCookieRenewTimer = setInterval(function () { meshserver.send({ action: 'authcookie' }); }, 1800000); // Request a cookie refresh every 30 minutes.
3191 if (xxcurrentView == 40) { refreshServerTimelineStats(); }
3192 }
3193 }
3194
3195 // Poll the server, if it responds, refresh the page.
3196 function serverPoll() {
3197 var xdr = null;
3198 try { xdr = new XDomainRequest(); } catch (e) { }
3199 if (!xdr) xdr = new XMLHttpRequest();
3200 xdr.open('HEAD', window.location.href);
3201 xdr.timeout = 15000;
3202 // Make sure there isn't a reverse proxy in front that may just be returning 5xx codes
3203 // Status code 4xx should still be allowed, since a page could potentially be removed, etc
3204 xdr.onload = function () { if (xdr.status < 500) reload(); else setTimeout(serverPoll, 10000); };
3205 xdr.onerror = xdr.ontimeout = function () { setTimeout(serverPoll, 10000); };
3206 xdr.send();
3207 }
3208
3209 // Return true if this browser is Windows based
3210 function detectWindowsBrowser() {
3211 var userAgent = window.navigator.userAgent.toUpperCase();
3212 return (userAgent.indexOf('WINDOWS') >= 0) || (userAgent.indexOf('WIN32') >= 0) || (userAgent.indexOf('WIN64') >= 0);
3213 }
3214
3215 function updateSiteAdmin() {
3216 if (!userinfo) return;
3217 var serverFeatures = parseInt('{{{serverfeatures}}}');
3218 var siteRights = userinfo.siteadmin;
3219 var accountSettingsLocked = ((siteRights != 0xFFFFFFFF) && ((siteRights & 1024) != 0)) || ((features2 & 0x100) != 0); // Not admin and have account features locked, or using a loginToken
3220
3221 // Update account actions
3222 QV('p2AccountSecurity', ((features & 4) == 0) && (serverinfo.domainauth == false) && ((features & 4096) != 0) && (accountSettingsLocked == false)); // Hide Account Security if in single user mode or domain authentication, 2 factor auth not supported.
3223 QV('p2AccountActions', !accountSettingsLocked)
3224 QV('managePhoneNumber1', (features & 0x02000000) && (features & 0x04000000) && (serverinfo.lock2factor != true));
3225 QV('managePhoneNumber2', (features & 0x02000000) && !(features & 0x04000000) && (serverinfo.lock2factor != true));
3226 QV('manageMessaging1', (features2 & 0x02000000) && (features2 & 0x04000000) && (serverinfo.lock2factor != true));
3227 QV('manageMessaging2', (features2 & 0x02000000) && !(features2 & 0x04000000) && (serverinfo.lock2factor != true));
3228 QV('manageEmail2FA', (features & 0x00800000) && (serverinfo.lock2factor != true));
3229 QV('p2AccountPassActions', ((features & 4) == 0) && (serverinfo.domainauth == false) && (userinfo != null) && (userinfo._id.split('/')[2].startsWith('~') == false)); // Hide Account Actions if in single user mode or domain authentication
3230 //QV('p2AccountImage', ((features & 4) == 0) && (serverinfo.domainauth == false)); // If account actions are not visible, also remove the image on that panel
3231 QV('accountCreateLoginTokenSpan', features2 & 0x00000080);
3232 QV('p2AccountImageFrame', !accountSettingsLocked)
3233 QV('p2ServerActions', (siteRights & 21) && ((serverFeatures & 143) != 0));
3234 QV('LeftMenuMyServer', (siteRights & 21) && ((serverFeatures & 64) != 0)); // 16 + 4 + 1
3235 QV('MainMenuMyServer', siteRights & 21);
3236 QV('accountCustomIconsSpan', true);
3237 QV('p2ServerActionsBackup', (siteRights & 1) && ((serverFeatures & 1) != 0));
3238 QV('p2ServerActionsRestore', (siteRights & 4) && ((serverFeatures & 2) != 0));
3239 QV('p2ServerActionsVersion', (siteRights & 16) && ((serverFeatures & 4) != 0));
3240 QV('p2ServerActionsErrors', (siteRights & 16) && ((serverFeatures & 8) != 0));
3241 QV('p2ServerActionsConfig', (siteRights & 16) && ((serverFeatures & 128) != 0));
3242 QV('MainMenuMyFiles', siteRights & 8);
3243 QV('LeftMenuMyFiles', siteRights & 8);
3244 if (((siteRights & 8) == 0) && (xxcurrentView == 5)) { setDialogMode(0); go(1); }
3245 if (currentNode != null) { gotoDevice(currentNode._id, xxcurrentView, true); }
3246
3247 // Update user image
3248 if ((userinfo.flags != null) && (userinfo.flags & 1)) {
3249 if (userinfo.accountImageRnd == null) { userinfo.accountImageRnd = Math.floor(Math.random() * 9999999999); }
3250 Q('p2AccountImage').src = 'userimage.ashx?rnd=' + userinfo.accountImageRnd;
3251 } else {
3252 Q('p2AccountImage').src = 'images/user-256.png';
3253 }
3254
3255 // Update user management state
3256 if ((userinfo.siteadmin & 2) != 0) {
3257 // We are user administrator
3258 if (users == null) { meshserver.send({ action: 'users' }); }
3259 if (wssessions == null) { meshserver.send({ action: 'wssessioncount' }); }
3260 mainUpdate(8192 + 16384);
3261 } else {
3262 // We are not user administrator
3263 users = null;
3264 wssessions = null;
3265 mainUpdate(16384);
3266 if (xxcurrentView == 4 || ((xxcurrentView >= 30) && (xxcurrentView < 40))) { setDialogMode(0); go(1); currentUser = null; }
3267 }
3268 meshserver.send({ action: 'events', limit: parseInt(p3limitdropdown.value) });
3269 QV('ServerConsole', (userinfo.siteadmin === 0xFFFFFFFF) && ((serverFeatures & 16) != 0));
3270 QV('ServerTrace', (userinfo.siteadmin === 0xFFFFFFFF) && ((serverFeatures & 32) != 0));
3271 if ((xxcurrentView == 115) && (userinfo.siteadmin != 0xFFFFFFFF)) { go(6); }
3272 if ((xxcurrentView == 6) && ((userinfo.siteadmin & 21) == 0)) { go(1); }
3273
3274 // If we are site administrator, register to get server statistics
3275 if ((siteRights & 21) != 0) { meshserver.send({ action: 'serverstats', interval: 10000 }); }
3276 updateUserDropdown();
3277 }
3278
3279 // To boost the speed of the web page when even floods occur, this method perform a delayed update on the web page.
3280 var updateNaggleTimer = null;
3281 var updateNaggleFlags = 0;
3282 function mainUpdate(flags) {
3283 updateNaggleFlags |= flags;
3284 if (updateNaggleTimer == null) {
3285 updateNaggleTimer = setTimeout(function () {
3286 if (updateNaggleFlags & 512) { center(); }
3287 if (updateNaggleFlags & 1) { onSearchInputChanged(); }
3288 if (updateNaggleFlags & 2) { onSortSelectChange(false); }
3289 if (updateNaggleFlags & 128) { updateMeshes(); }
3290 if (updateNaggleFlags & 4) { updateDevices(); updateDeviceDetails(); if (xxcurrentView == 21) { p21updateMesh(); } }
3291 if (updateNaggleFlags & 8) { drawNotifications(); }
3292 if ((updateNaggleFlags & 16) && (features & 0x00008000)) { updateMapMarkers(); }
3293 if (updateNaggleFlags & 32) { eventsUpdate(); }
3294 if ((updateNaggleFlags & 64) && (features & 0x00008000)) { refreshMap(false, true); }
3295 if (updateNaggleFlags & 256) { drawDeviceTimeline(); }
3296 if (updateNaggleFlags & 1024) { deviceEventsUpdate(); }
3297 if (updateNaggleFlags & 2048) { userEventsUpdate(); }
3298 if (updateNaggleFlags & 4096) { p20updateMesh(); }
3299 if (updateNaggleFlags & 8192) { updateUserGroups(); }
3300 if (updateNaggleFlags & 16384) { updateUsers(); }
3301 if (updateNaggleFlags & 32768) { updateRecordings(); }
3302 if (updateNaggleFlags & 65536) { updateLoginTokens(); }
3303 updateNaggleTimer = null;
3304 updateNaggleFlags = 0;
3305 gotoStartViewPage();
3306 }, 150);
3307 }
3308 }
3309
3310 // Return the number of 2nd factor for this account
3311 function count2factoraAuths() {
3312 if (userinfo == null) return -1;
3313 var authFactorCount = 0;
3314 if (userinfo.otpsecret == 1) { authFactorCount++; } // Authenticator time factor
3315 if (userinfo.otpduo == 1) { authFactorCount++; } // Duo factor
3316 if (userinfo.otpdev == 1) { authFactorCount++; } // Push authentication factor
3317 if (userinfo.otphkeys > 0) { authFactorCount += userinfo.otphkeys; } // FIDO hardware factor
3318 if ((features & 0x00800000) && (userinfo.otpekey == 1)) { authFactorCount++; } // EMail factor
3319 if ((features & 0x02000000) && (features & 0x04000000) && (userinfo.phone != null)) { authFactorCount++; } // SMS factor
3320 if ((features2 & 0x02000000) && (features2 & 0x04000000) && (userinfo.msghandle != null)) { authFactorCount++; } // Messaging factor
3321 if ((authFactorCount > 0) && (userinfo.otpkeys > 0) && ((features2 & 0x40000) == 0)) { authFactorCount++; } // Backup keys
3322 return authFactorCount;
3323 }
3324
3325 var backupCodesWarningDone = false;
3326 function updateSelf() {
3327 if (!userinfo) return;
3328 var authFactorCount = count2factoraAuths(); // Get the number of 2nd factors
3329 var accountSettingsLocked = ((userinfo.siteadmin != 0xFFFFFFFF) && ((userinfo.siteadmin & 1024) != 0));
3330 QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
3331 QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true) && (accountSettingsLocked == false));
3332 QV('manageOtp', (serverinfo.lock2factor != true) && (authFactorCount > 0) && ((features2 & 0x40000) == 0));
3333 QV('authPhoneNumberCheck', (userinfo.phone != null));
3334 QV('authMessagingCheck', (userinfo.msghandle != null));
3335 QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
3336 QV('authDuoSetupCheck', (userinfo.otpduo == 1) && ((features2 & 0x20000000) != 0));
3337 QV('authAppSetupCheck', userinfo.otpsecret == 1);
3338 QV('manageAuthApp', (serverinfo.lock2factor != true) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
3339 QV('manageDuoApp', (serverinfo.lock2factor != true) && ((features2 & 0x20000000) != 0));
3340 QV('authKeySetupCheck', userinfo.otphkeys > 0);
3341 QV('authPushAuthDevCheck', (userinfo.otpdev > 0) && ((features2 & 0x40) != 0));
3342 QV('authCodesSetupCheck', userinfo.otpkeys > 0);
3343 QV('managePushAuthDev', (serverinfo.lock2factor != true) && (features2 & 0x40) && (authFactorCount > 0));
3344 QV('manageHardwareOtp', (serverinfo.lock2factor != true));
3345 mainUpdate(4 + 128 + 4096);
3346
3347 // Check if none or at least 2 factors are enabled.
3348 if ((backupCodesWarningDone == false) && (authFactorCount == 1) && ((features2 & 0x80000) == 0)) {
3349 addNotification({ text: "Please add two-factor backup codes. If the current factor is lost, there is no way to recover this account.", title: "Two factor authentication", tag: 'backupcodes' });
3350 backupCodesWarningDone = true;
3351 }
3352
3353 // If we can't create new groups, hide all links that can do that.
3354 var newGroupsAllowed = ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 64) == 0));
3355 QV('p2createMeshLink1', newGroupsAllowed);
3356 QV('p2createMeshLink2', newGroupsAllowed);
3357 QV('getStarted1', newGroupsAllowed);
3358 QV('getStarted2', !newGroupsAllowed);
3359
3360 if (typeof userinfo.passchange == 'number') {
3361 if (userinfo.passchange == -1) { QH('p2nextPasswordUpdateTime', " - Reset on next login."); }
3362 else if ((passRequirements != null) && (typeof passRequirements.reset == 'number')) {
3363 var seconds = (userinfo.passchange) + (passRequirements.reset * 86400) - Math.floor(Date.now() / 1000);
3364 if (seconds < 0) { QH('p2nextPasswordUpdateTime', " - Reset on next login."); }
3365 else if (seconds < 3600) { var secs = Math.floor(seconds / 60); QH('p2nextPasswordUpdateTime', format((secs == 1) ? " - Reset in 1 minute." : " - Reset in {0} minutes.", secs)); }
3366 else if (seconds < 86400) { var hours = Math.floor(seconds / 3600); QH('p2nextPasswordUpdateTime', format((hours == 1) ? " - Reset in 1 hour." : " - Reset in {0} hours.", hours)); }
3367 else { var days = Math.floor(seconds / 86400); QH('p2nextPasswordUpdateTime', format((hours == 1) ? " - Reset in 1 day." : " - Reset in {0} days.", days)); }
3368 }
3369 }
3370
3371 // Adjust "My Users" tabs
3372 QV('MainMenuMyUsers', ((users != null) && ((features & 4) == 0)) || (((userinfo.siteadmin & 512) != 0) && ((features & 0x08000000) != 0)));
3373 QV('LeftMenuMyUsers', ((users != null) && ((features & 4) == 0)) || (((userinfo.siteadmin & 512) != 0) && ((features & 0x08000000) != 0)));
3374 QV('UsersGeneral', ((users != null) && ((features & 4) == 0)));
3375 QV('UsersGroups', ((users != null) && ((features & 4) == 0)));
3376 QV('UsersRecordings', ((userinfo.siteadmin & 512) != 0) && ((features & 0x08000000) != 0));
3377 }
3378
3379 function setSessionActivity() { sessionActivity = Date.now(); QH('idleTimeoutNotify', ''); }
3380 function checkIdleSessionTimeout() {
3381 var delta = (Date.now() - sessionActivity);
3382 if (delta > serverinfo.timeout) {
3383 if (desktop != null) { // Disconnect remote desktop
3384 desktop.Stop();
3385 webRtcDesktopReset();
3386 desktopNode = desktop = null;
3387 if (pluginHandler != null) { pluginHandler.callHook('onDesktopDisconnect'); }
3388 }
3389 if (terminal != null) { // Disconnect terminal
3390 terminal.Stop();
3391 terminal = null;
3392 }
3393 if (files != null) { // Disconnect files
3394 files.Stop();
3395 files = null;
3396 }
3397 if (serverinfo.logoutonidlesessiontimeout) {
3398 if (urlargs.key) { window.location.href = 'logout?key=' + urlargs.key; }
3399 else { window.location.href = 'logout'; }
3400 }
3401 } else {
3402 var ds = Math.round((serverinfo.timeout - delta) / 1000);
3403 var sessionInProgress = (desktop != null || terminal != null || files != null);
3404 var show = serverinfo.logoutonidlesessiontimeout || sessionInProgress;
3405 var isLogout = serverinfo.logoutonidlesessiontimeout;
3406 var theText = '';
3407 if (ds <= 60) {
3408 theText = isLogout
3409 ? (ds == 1 ? "1 second until logout" : "{0} seconds until logout")
3410 : (ds == 1 ? "1 second until disconnect" : "{0} seconds until disconnect");
3411 } else {
3412 ds = Math.round(ds / 60);
3413 if (ds <= 5) {
3414 theText = isLogout
3415 ? (ds == 1 ? "1 minute until logout" : "{0} minutes until logout")
3416 : (ds == 1 ? "1 minute until disconnect" : "{0} minutes until disconnect");
3417 }
3418 }
3419 QH('idleTimeoutNotify', show && theText ? '<br />' + format(theText, ds) : '');
3420 }
3421 }
3422
3423 function onMessage(server, message) {
3424 switch (message.action) {
3425 case 'trace': {
3426 serverTrace.unshift(message);
3427 displayServerTrace();
3428 break;
3429 }
3430 case 'intersession': {
3431 if (message.subaction == 'removeNotify') { notificationDelete(message.id); }
3432 break;
3433 }
3434 case 'traceinfo': {
3435 if (typeof message.traceSources == 'object') {
3436 if ((message.traceSources != null) && (message.traceSources.length > 0)) {
3437 serverTraceSources = message.traceSources;
3438 QH('p41traceStatus', EscapeHtml(message.traceSources.join(', ')));
3439 } else {
3440 serverTraceSources = [];
3441 QH('p41traceStatus', "None");
3442 }
3443 }
3444 break;
3445 }
3446 case 'serverstats': {
3447 updateGeneralServerStats(message);
3448 break;
3449 }
3450 case 'serverwarnings': {
3451 if ((message.warnings != null) && (message.warnings.length > 0)) {
3452 var ServerWarnings = {
3453 1: "",
3454 2: "Missing WebDAV parameters.",
3455 3: "Unrecognized configuration option \"{0}\".",
3456 4: "WebSocket compression is disabled, this feature is broken in NodeJS v11.11 to v12.15 and v13.2",
3457 5: "Unable to load Intel AMT TLS root certificate for default domain.",
3458 6: "Unable to load Intel AMT TLS root certificate for domain {0}.",
3459 7: "CIRA local FQDN's ignored when server in LAN-only or WAN-only mode.",
3460 8: "Can't have more than 4 CIRA local FQDN's. Ignoring value.",
3461 9: "Agent hash checking is being skipped, this is unsafe.",
3462 10: "Missing Let's Encrypt email address.",
3463 11: "Invalid Let's Encrypt host names.",
3464 12: "Invalid Let's Encrypt names, can't contain a *.",
3465 13: "Unable to setup Let's Encrypt module.",
3466 14: "Invalid Let's Encrypt names, unable to resolve: {0}",
3467 15: "Invalid Let's Encrypt email address, unable to resolve: {0}",
3468 16: "Unable to load CloudFlare trusted proxy IPv6 address list.",
3469 17: "SendGrid server has limited use in LAN mode.",
3470 18: "SMTP server has limited use in LAN mode.",
3471 19: "SMS gateway has limited use in LAN mode.",
3472 20: "Invalid \"LoginCookieEncryptionKey\" in config.json.",
3473 21: "Backup path can't be set within meshcentral-data folder, backup settings ignored.",
3474 22: "Failed to sign agent {0}: {1}",
3475 23: "Unable to load agent icon file: {0}.",
3476 24: "Unable to load agent logo file: {0}.",
3477 25: "This NodeJS version does not support OpenID.",
3478 26: "This NodeJS version does not support Discord.js.",
3479 27: "Firebase now requires a service account JSON file, Firebase disabled."
3480 };
3481 var x = '';
3482 for (var i in message.warnings) {
3483 var y = message.warnings[i];
3484 if (typeof y == 'string') {
3485 x += '<div style=color:red;padding-bottom:6px><b>' + "WARNING: " + y + '</b></div>';
3486 } else {
3487 var z = ServerWarnings[y.id];
3488 if (z == null) { z = y.msg; } else { if (y.args != null) { z = format(z, ...y.args); } }
3489 x += '<div style=color:red;padding-bottom:6px><b>' + "WARNING: " + z + '</b></div>';
3490 }
3491 }
3492 QH('serverWarnings', x);
3493 QV('serverWarningsDiv', true);
3494 }
3495 break;
3496 }
3497 case 'servertimelinestats': {
3498 setServerTimelineStats(message.events);
3499 break;
3500 }
3501 case 'authcookie': {
3502 // Got an authentication cookie refresh
3503 authCookie = message.cookie;
3504 authRelayCookie = message.rcookie;
3505 break;
3506 }
3507 case 'serverinfo': {
3508 serverinfo = message.serverinfo;
3509 if (serverinfo.timeout) { setInterval(checkIdleSessionTimeout, 10000); checkIdleSessionTimeout(); }
3510 if (debugmode == 1) { console.log('Server time: ', printDateTime(new Date(serverinfo.serverTime))); }
3511 setupServiceWorker();
3512 if (serverinfo.certExpire != null) {
3513 var days = Math.floor((serverinfo.certExpire - Date.now()) / 86400000);
3514 if ((days >= 0) && (days < 20)) {
3515 QH('serverCertWarnings', '<div style=color:red;padding-bottom:6px><b>' + "WARNING: " + format("Certificate expires in {0} day(s)", days) + '</b></div>');
3516 QV('serverWarningsDiv', true);
3517 addNotification({ text: format("Certificate expires in {0} day(s)", days) });
3518 }
3519 }
3520 if (serverinfo.preConfiguredRemoteInput) {
3521 // Setup the pre-configured keyboard remote input strings
3522 var x = '';
3523 for (var i in serverinfo.preConfiguredRemoteInput) { x += '<div class="cmtext" onclick="cmdeskpreconfigtypeaction(' + i + ',event)">' + EscapeHtml(serverinfo.preConfiguredRemoteInput[i].name) + '</div>'; }
3524 if (x != '') { x += '<hr />'; }
3525 QH('deskPreConfigShortcutContextMenu1', x);
3526 }
3527 if (serverinfo.preConfiguredScripts) {
3528 // Setup the pre-configured scripts
3529 var x = '', y = '';
3530 for (var i in serverinfo.preConfiguredScripts) {
3531 var s = serverinfo.preConfiguredScripts[i];
3532 var z = '<div class="cmtext" onclick="cmdeskpreconfigscriptaction(' + i + ',event)">' + EscapeHtml(serverinfo.preConfiguredScripts[i].name) + '</div>';
3533 if (s.type != 3) { x += z; }
3534 if (s.type >= 3) { y += z; }
3535 }
3536 QH('deskPreConfigScriptContextMenu1', x); // Windows devices
3537 QH('deskPreConfigScriptContextMenu2', y); // Other devices
3538 }
3539 break;
3540 }
3541 case 'userinfo': {
3542 userinfo = message.userinfo;
3543 updateSiteAdmin();
3544 updateSelf();
3545 updateUserDropdown();
3546 break;
3547 }
3548 case 'users': {
3549 users = {};
3550 for (var m in message.users) { users[message.users[m]._id] = message.users[m]; }
3551 if (currentUser != null) { currentUser = users[currentUser._id]; }
3552 mainUpdate(16384);
3553 updateSelf();
3554 break;
3555 }
3556 case 'wssessioncount': {
3557 wssessions = message.wssessions;
3558 mainUpdate(16384);
3559 break;
3560 }
3561 case 'meshes': {
3562 meshes = {};
3563 for (var m in message.meshes) { meshes[message.meshes[m]._id] = message.meshes[m]; }
3564 if (currentMesh != null) { currentMesh = meshes[currentMesh._id]; }
3565 mainUpdate(4 + 128);
3566 break;
3567 }
3568 case 'usergroups': {
3569 var groupCount = 0;
3570 if (Array.isArray(message.ugroups)) {
3571 usergroups = {};
3572 for (var i in message.ugroups) { groupCount++; usergroups[message.ugroups[i]._id] = message.ugroups[i]; }
3573 if (groupCount == 0) { usergroups = null; }
3574 } else {
3575 usergroups = message.ugroups;
3576 for (var i in message.ugroups) { groupCount++; message.ugroups[i]._id = i; }
3577 if (groupCount == 0) { usergroups = null; }
3578 }
3579 mainUpdate(8192);
3580 break;
3581 }
3582 case 'files': {
3583 filetree = setupBackPointers(message.filetree);
3584 updateFiles();
3585 d3updatefiles();
3586 break;
3587 }
3588 case 'nodes': {
3589 nodes = [];
3590 for (var m in message.nodes) {
3591 for (var n in message.nodes[m]) {
3592 if (message.nodes[m][n]._id == null) { console.log('Invalid node (' + n + '): ' + JSON.stringify(message.nodes)); continue; }
3593 if (message.nodes[m][n].name == null) { message.nodes[m][n].name = '???'; }
3594 message.nodes[m][n].namel = message.nodes[m][n].name.toLowerCase();
3595 if (message.nodes[m][n].rname) { message.nodes[m][n].rnamel = message.nodes[m][n].rname.toLowerCase(); } else { message.nodes[m][n].rnamel = message.nodes[m][n].namel; }
3596 message.nodes[m][n].meshnamel = meshes[m] ? meshes[m].name.toLowerCase() : '*';
3597 message.nodes[m][n].meshid = m;
3598 message.nodes[m][n].state = (message.nodes[m][n].state) ? (message.nodes[m][n].state) : 0;
3599 if (!message.nodes[m][n].icon) message.nodes[m][n].icon = 1;
3600 message.nodes[m][n].ident = ++nodeShortIdent;
3601 nodes.push(message.nodes[m][n]);
3602 }
3603 }
3604
3605 // Remove any stars for nodes that don't exist
3606 for (var i in stars) { if (getNodeFromId(i) == null) { delete stars[i]; } }
3607
3608 // If we are currently looking at a node this is now gone, change the view.
3609 if ((currentNode != null) && (IsNodeViewable(currentNode) == false)) { currentNode = null; go(1); }
3610
3611 // Change the reference to the current node
3612 if (currentNode != null) { currentNode = getNodeFromId(currentNode._id); if (currentNode != null) { gotoDevice(currentNode._id, xxcurrentView, true); } else { go(1); } }
3613
3614 // Update device paging
3615 devicePagingState = (message.totalcount == null) ? null : { total: message.totalcount, skip: message.skip, limit: message.limit };
3616 updateDevicePageState();
3617
3618 mainUpdate(1 | 2 | 4 | 64);
3619
3620 // If groups are to be collapsed by default, do it now
3621 if (collapseGroups === 'true' && Object.keys(CollapsedGroups).length === 0 && typeof(getstore('_collapse')) === 'undefined') { cmexpandaction(2); }
3622
3623 break;
3624 }
3625 case 'powertimeline': {
3626 if (message.nodeid != powerTimelineReq) break;
3627 powerTimelineNode = message.nodeid;
3628 powerTimeline = message.timeline;
3629 powerTimelineUpdate = Date.now() + 300000; // Update every 5 minutes
3630 for (var i in powerTimeline) { if (i % 2 == 1) { powerTimeline[i] = powerTimeline[i] * 1000; } } // Decompress time
3631 if (currentNode._id == message.nodeid) { mainUpdate(256); }
3632 break;
3633 }
3634 case 'deviceShares': {
3635 if (message.nodeid != deviceSharesReq) break;
3636 deviceSharesNode = message.nodeid;
3637 deviceShares = message.deviceShares;
3638 if ((currentNode != null) && (currentNode._id == message.nodeid)) { gotoDevice(currentNode._id, xxcurrentView, true); }
3639 break;
3640 }
3641 case 'deviceMeshShares': {
3642 if (message.meshid != deviceSharesReq) break;
3643 deviceSharesNode = message.meshid;
3644 deviceShares = message.deviceShares;
3645 if ((currentMesh != null) && (currentMesh._id == message.meshid)) { p20updateMesh(); }
3646 break;
3647 }
3648 case 'getsysinfo': {
3649 if (message.nodeid != powerTimelineReq) break;
3650 if (message.noinfo === true) {
3651 updateDeviceDetails(getNodeFromId(message.nodeid));
3652 } else {
3653 message.hardware.time = message.time;
3654 updateDeviceDetails(getNodeFromId(message.nodeid), message.hardware);
3655 }
3656 break;
3657 }
3658 case 'software': {
3659 try {
3660 var softwareData = message.value;
3661 if (JSON.parse(message.value).error) {
3662 QH('p18Status', '<span style=color:red>' + "ERROR: " + JSON.parse(message.value).error + '</span>');
3663 QH('p18html', '<div style="text-align:center;padding:50px;color:gray"><i>' + "Click Refresh to load installed software..." + '</i></div>');
3664 installedAppsLoading = false;
3665 installedAppsLoadingType = 0;
3666 return;
3667 }
3668 if (typeof softwareData === 'string' && softwareData.trim().charAt(0) === '[' && softwareData.indexOf('"name"') >= 0) {
3669 handleInstalledAppsResponse(softwareData);
3670 } else if (typeof softwareData === 'string' && (softwareData.indexOf('"success"') >= 0 || softwareData.indexOf('"status"') >= 0 || softwareData.indexOf('"error"') >= 0)) {
3671 handleUninstallResponse(softwareData);
3672 } else {
3673 installedAppsLoading = false;
3674 installedAppsLoadingType = 0;
3675 displayInstalledApps();
3676 }
3677 } catch (xx) { }
3678 break;
3679 }
3680 case 'lastconnect': {
3681 var node = getNodeFromId(message.nodeid);
3682 if (node != null) {
3683 node.lastconnect = message.time;
3684 node.lastaddr = message.addr;
3685 if ((currentNode._id == node._id) && (Q('MainComputerState').innerHTML == '') && (node.lastconnect != null)) {
3686 QH('MainComputerState', '<span>' + "Last seen:" + '<br />' + printDateTime(new Date(node.lastconnect)) + '</span>');
3687 }
3688 }
3689 break;
3690 }
3691 case 'lastconnects': {
3692 var lcnodes = Object.keys(message.lastconnects);
3693 for (var i in lcnodes) {
3694 var lcnodeid = lcnodes[i];
3695 var node = getNodeFromId(lcnodeid);
3696 if (node != null) { node.lastconnect = message.lastconnects[lcnodeid] }
3697 }
3698 mainUpdate(4);
3699 break;
3700 }
3701 case 'msg': {
3702 // Check if this is a message from a node
3703 if (message.nodeid != null) {
3704 var index = -1;
3705 if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == message.nodeid) { index = i; break; } } }
3706 if (index != -1) {
3707 // Node was found, dispatch the message
3708 if ((message.type === 'cpuinfo') && (currentNode != null) && (currentNode._id == message.nodeid)) {
3709 var now = (Date.now() / 1000), cpu = 0, memory = 0;
3710 if (typeof message.cpu.total == 'number') { cpu = message.cpu.total; }
3711 if (typeof message.memory.percentConsumed == 'number') { memory = message.memory.percentConsumed; }
3712 deviceDetailsStatsData.push([now, cpu, memory]);
3713 deviceDetailsStatsDraw(message);
3714 } else if (message.type === 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
3715 else if (message.type === 'notify') { // This is a notification message.
3716 var n = getstore('notifications', 0);
3717 if (((n & 8) == 0) && (message.amtMessage != null)) { break; } // Intel AMT desktop & terminal messages should be ignored.
3718 var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
3719 if (message.id != null) { n.id = message.id; }
3720 if (message.nodeid != null) { n.nodeid = message.nodeid; }
3721 if (message.tag != null) { n.tag = message.tag; }
3722 if (message.url != null) { n.url = message.url; }
3723 if (message.username != null) { n.username = message.username; }
3724 if (typeof message.maxtime === 'number') { n.maxtime = message.maxtime; }
3725 addNotification(n);
3726 } else if (message.type === 'ps') {
3727 showDeskToolsProcesses(message);
3728 } else if (message.type === 'services') {
3729 showDeskToolsServices(message);
3730 } else if (message.type === 'service') {
3731 showServiceDetailsDialog(message);
3732 } else if ((message.type === 'getclip') && (currentNode != null) && (currentNode._id == message.nodeid)) {
3733 if ((message.tag == 1) && (xxdialogTag === 'clipboard')) {
3734 Q('d2clipText').value = message.data; // Put remote clipboard data into dialog box
3735 } else if ((message.tag === 2) || (message.tag === 3)) {
3736 // tag 2 = manual clipboard-in button, tag 3 = auto-clipboard poll
3737 if ((navigator.clipboard != null) && (message.data != deskLastClipboardReceived) && document.hasFocus()) {
3738 deskLastClipboardReceived = message.data;
3739 deskLastClipboardSent = message.data; // prevent echo back to remote
3740 navigator.clipboard.writeText(message.data).then(function () { }).catch(function (err) { });
3741 }
3742 }
3743 } else if ((message.type === 'setclip') && (xxdialogTag === 'clipboard') && (currentNode != null) && (currentNode._id == message.nodeid)) {
3744 // Display success/fail on the clipboard dialog box.
3745 QH('dlgClipStatus', message.success ? '<span style=color:green>' + "Success" + '</span>' : '<span style=color:red>' + "Failed" + '</span>')
3746 setTimeout(function () { try { QH('dlgClipStatus', ''); } catch (ex) { } }, 2000);
3747 } else if ((message.type === 'userSessions') && (currentNode != null) && (currentNode._id === message.nodeid) && (desktop == null)) {
3748 // Got list of user sessions
3749 var userSessions = [];
3750 if (message.data != null) { for (var i in message.data) { if ((message.data[i].State == 'Active') || (message.data[i].State == 'Connected') || (message.data[i].StationName == 'Console') || (debugmode == 3)) { userSessions.push(message.data[i]); } } }
3751 if (userSessions.length === 0) { connectDesktop(null, 1, null, message.tag); } // No active sessions, do a normal connection.
3752 else if (userSessions.length === 1) { connectDesktop(null, 1, userSessions[0].SessionId, message.tag); } // One active session, connect to it
3753 else {
3754 var x = '';
3755 var sortBy = "{{{userSessionsSort}}}";
3756 if (sortBy != '') {
3757 userSessions.sort(function (a, b) {
3758 if (!a[sortBy]) return -1; // a comes before b
3759 if (!b[sortBy]) return 1; // b comes before a
3760 if (a[sortBy] < b[sortBy]) return -1;
3761 if (a[sortBy] > b[sortBy]) return 1;
3762 return 0;
3763 });
3764 }
3765 for (var i in userSessions) {
3766 x += '<div style="text-align:left;cursor:pointer;background-color:gray;margin:5px;padding:5px;border-radius:5px" onclick=connectDesktop(event,1,' + userSessions[i].SessionId + ',' + message.tag + ')>' + userSessions[i].State + (userSessions[i].StationName ? (', ' + userSessions[i].StationName) : '');
3767 if (userSessions[i].Username) { if (userSessions[i].Domain) { x += ' - ' + userSessions[i].Domain + '/' + userSessions[i].Username; } else { x += ' - ' + userSessions[i].Username; } }
3768 x += '</div>';
3769 }
3770 QH('p11DeskSessionSelector', x);
3771 QV('p11DeskSessionSelector', true);
3772 }
3773 } else if (message.type === 'psinfo') {
3774 if (xxdialogTag === ('ps|' + message.nodeid + '|' + message.pid)) {
3775 var x = '<div style=max-height:200px;overflow-y:auto>';
3776 //x += addHtmlValue4("Process ID", message.pid);
3777 if ((typeof message.value == 'object') && (Object.keys(message.value).length > 0)) {
3778 // lets fix agent psinfo not being camelcase
3779 message.value = jsonToCamel(message.value);
3780 if (!message.value.cmd && message.value.path) { message.value.cmd = message.value.path }
3781 if (!message.value.processUser && message.value.userName) { message.value.processUser = message.value.userName.split("\\")[1]; }
3782 if (!message.value.processDomain && message.value.userName) { message.value.processDomain = message.value.userName.split("\\")[0]; }
3783 if (message.value.processName) { x += '<div style=padding:4px><div style=padding-bottom:4px>' + "Process Name" + '</div><div style=word-wrap:break-word;padding-left:6px><b>' + message.value.processName + '</b></div></div>'; }
3784 if (message.value.machineName) { x += addHtmlValue5("Machine Name", message.value.machineName); }
3785 if (message.value.cmd) { x += '<div style=padding:4px><div style=padding-bottom:4px>' + "Command Line" + '</div><div style=word-wrap:break-word;padding-left:6px><b>' + message.value.cmd + '</b></div></div>'; }
3786 if (message.value.mainWindowTitle) { x += '<div style=padding:4px><div style=padding-bottom:4px>' + "Window Title" + '</div><div style=word-wrap:break-word;padding-left:6px><b>' + message.value.mainWindowTitle + '</b></div></div>'; }
3787 if (message.value.processUser) { x += addHtmlValue5("User", message.value.processUser); }
3788 if (message.value.processDomain) { x += addHtmlValue5("Domain", message.value.processDomain); }
3789 if (message.value.startTime) { x += addHtmlValue5("Start Time", message.value.startTime); }
3790 x += '<hr />';
3791 if (message.value.PriorityBoostEnabled) { x += addHtmlValue5("Priority Boost", message.value.PriorityBoostEnabled ? "Enabled" : "Disabled"); }
3792 if (message.value.sessionId) { x += addHtmlValue5("Session Id", message.value.sessionId); }
3793 if (message.value.handleCount) { x += addHtmlValue5("Handle Count", message.value.handleCount); }
3794 if (message.value.privilegedProcessorTime) { x += addHtmlValue5("Privileged Processor Time", format("{0} seconds", message.value.privilegedProcessorTime)); }
3795 if (message.value.totalProcessorTime) { x += addHtmlValue5("Total Processor Time", format("{0} seconds", message.value.totalProcessorTime)); }
3796 if (message.value.userProcessorTime) { x += addHtmlValue5("User Processor Time", format("{0} seconds", message.value.userProcessorTime)); }
3797 if (message.value.nonpagedSystemMemorySize) { x += addHtmlValue5("Non Paged Memory", getNiceSize2(message.value.nonpagedSystemMemorySize)); }
3798 if (message.value.pagedMemorySize) { x += addHtmlValue5("Paged Memory", getNiceSize2(message.value.pagedMemorySize)); }
3799 if (message.value.privateMemorySize) { x += addHtmlValue5("Private Memory", getNiceSize2(message.value.privateMemorySize)); }
3800 if (message.value.virtualMemorySize) { x += addHtmlValue5("Virtual Memory", getNiceSize2(message.value.virtualMemorySize)); }
3801 if (message.value.workingSet) { x += addHtmlValue5("Working Set", getNiceSize2(message.value.workingSet)); }
3802 if (message.value.peakWorkingSet) { x += addHtmlValue5("Peak Working Set", getNiceSize2(message.value.peakWorkingSet)); }
3803 if (message.value.peakPagedMemorySize) { x += addHtmlValue5("Peak Paged Memory", getNiceSize2(message.value.peakPagedMemorySize)); }
3804 if (message.value.peakVirtualMemorySize) { x += addHtmlValue5("Peak Virtual Memory", getNiceSize2(message.value.peakVirtualMemorySize)); }
3805 } else {
3806 x += "No information provided";
3807 }
3808 x += '</div>';
3809 QH('id_dialogOptions', x);
3810 }
3811 } else if (message.type === 'deskBackground') {
3812 if (message.data != "") {
3813 Q('DeskBackgroundButtonImage').style.color = '';
3814 } else {
3815 Q('DeskBackgroundButtonImage').style.color = 'red';
3816 }
3817 }
3818 }
3819 } else {
3820 if (message.type === 'notify') { // This is a notification message.
3821 var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
3822 if (message.id != null) { n.id = message.id; }
3823 if (message.tag != null) { n.tag = message.tag; }
3824 if (message.url != null) { n.url = message.url; }
3825 if (message.username != null) { n.username = message.username; }
3826 if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
3827 addNotification(n);
3828 }
3829 }
3830 break;
3831 }
3832 case 'getnetworkinfo': {
3833 if (currentNode._id != message.nodeid) return;
3834 updateDeviceDetails(getNodeFromId(message.nodeid), null, message);
3835 if ((xxdialogMode == 2) && (xxdialogTag == 'if' + message.nodeid)) {
3836 var x = '<div class=dialogText>';
3837 if (currentNode.firstconnect) { x += addHtmlValue2("First agent connection", printDateTime(new Date(currentNode.firstconnect))); }
3838 if (currentNode.lastconnect) { x += addHtmlValue2("Last agent connection", printDateTime(new Date(currentNode.lastconnect))); }
3839 if (currentNode.lastaddr) {
3840 var splitip = currentNode.lastaddr.split(':');
3841 if (splitip.length > 2) {
3842 // IPv6
3843 x += addHtmlValue2("Last agent address", currentNode.lastaddr + ' <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(currentNode.lastaddr) + '")></i>');
3844 } else {
3845 // IPv4
3846 if (isPrivateIP(currentNode.lastaddr)) {
3847 x += addHtmlValue2("Last agent address", splitip[0] + ' <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(splitip[0]) + '")></i>');
3848 } else {
3849 x += addHtmlValue2("Last agent address", '<a href="https://iplocation.com/?ip=' + splitip[0] + '" rel="noreferrer noopener" target="MeshIPLoopup">' + splitip[0] + '</a> <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(splitip[0]) + '")></i>');
3850 }
3851 }
3852 }
3853 if (message.updateTime != null) { x += addHtmlValue2("Last interfaces update", printDateTime(new Date(message.updateTime))); }
3854
3855 if (message.netif != null) {
3856 // Old style
3857 for (var i in message.netif) {
3858 var net = message.netif[i];
3859 x += '<hr />'
3860 if (net.name) { x += addHtmlValue2("Name", '<b>' + EscapeHtml(net.name) + '</b>'); }
3861 if (net.desc) { x += addHtmlValue2("Description", EscapeHtml(net.desc).replace('(R)', '&reg;').replace('(r)', '&reg;')); }
3862 if (net.dnssuffix) { x += addHtmlValue2("DNS suffix", EscapeHtml(net.dnssuffix) + ' <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy name to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(net.dnssuffix) + '")></i>'); }
3863 if (net.mac) { x += addHtmlValue2("MAC address", '<a href="https://maclookup.app/search/result?mac=' + net.mac.substring(0, 6) + '" rel="noreferrer noopener" target="MeshMACLoopup">' + EscapeHtml(net.mac.toLowerCase()) + '</a> <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy MAC address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(net.mac.toLowerCase()) + '")></i>'); }
3864 if (net.v4addr) { x += addHtmlValue2("IPv4 address", EscapeHtml(net.v4addr) + ' <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(net.v4addr) + '")></i>'); }
3865 if (net.v4mask) { x += addHtmlValue2("IPv4 mask", EscapeHtml(net.v4mask) + ' <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(net.v4mask) + '")></i>'); }
3866 if (net.v4gateway) { x += addHtmlValue2("IPv4 gateway", EscapeHtml(net.v4gateway) + ' <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(net.v4gateway) + '")></i>'); }
3867 if (net.gatewaymac) { x += addHtmlValue2("Gateway MAC", '<a href="https://maclookup.app/search/result?mac=' + net.gatewaymac.substring(0, 6) + '" rel="noreferrer noopener" target="MeshMACLoopup">' + EscapeHtml(net.gatewaymac.toLowerCase()) + '</a> <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy MAC address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(net.gatewaymac.toLowerCase()) + '")></i>'); }
3868 }
3869 } else if (message.netif2 != null) {
3870 // New style
3871 for (var i in message.netif2) {
3872 var net = message.netif2[i];
3873 if ((Array.isArray(net) == false) || (net.length < 1) || (net[0] == null) || ((typeof net[0].mac == 'string') && (net[0].mac.startsWith('00:00:00:00')))) continue;
3874 x += '<hr />'
3875 x += addHtmlValue2("Name", '<b>' + EscapeHtml(i) + '</b>');
3876 if (typeof net[0].mac == 'string') { x += addHtmlValue2("MAC address", '<a href="https://maclookup.app/search/result?mac=' + net[0].mac.split(':').join('').substring(0, 6) + '" rel="noreferrer noopener" target="MeshMACLoopup">' + EscapeHtml(net[0].mac.toLowerCase()) + '</a> <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy MAC address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(net[0].mac.toLowerCase()) + '")></i>'); }
3877 if (net[0].fqdn) { x += addHtmlValue2("FQDN", net[0].fqdn); }
3878 for (var j = 0; j < net.length; j++) {
3879 var netif = net[j];
3880 if (netif.family == 'IPv6') {
3881 if (netif.address) { x += addHtmlValue2("IPv6 address", EscapeHtml(netif.address) + ' <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(netif.address) + '")></i>'); }
3882 if (netif.netmask) { x += addHtmlValue2("IPv6 mask", EscapeHtml(netif.netmask) + ' <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(netif.netmask) + '")></i>'); }
3883 if (netif.gateway) { x += addHtmlValue2("IPv6 gateway", EscapeHtml(netif.gateway) + ' <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(netif.gateway) + '")></i>'); }
3884 } else if (netif.family == 'IPv4') {
3885 if (netif.address) { x += addHtmlValue2("IPv4 address", EscapeHtml(netif.address) + ' <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(netif.address) + '")></i>'); }
3886 if (netif.netmask) { x += addHtmlValue2("IPv4 mask", EscapeHtml(netif.netmask) + ' <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(netif.netmask) + '")></i>'); }
3887 if (netif.gateway) { x += addHtmlValue2("IPv4 gateway", EscapeHtml(netif.gateway) + ' <i class="fa-fw fa-regular fa-clipboard fa-sm" role=button title="' + "Copy address to clipboard" + '" onclick=copyTextToClip2("' + encodeURIComponentEx(netif.gateway) + '")></i>'); }
3888 }
3889 }
3890 }
3891 } else {
3892 x += '<hr />'
3893 x += "No network interface information available for this device.";
3894 }
3895 x += '</div>';
3896 QH('d2netinfo', x);
3897 }
3898 break;
3899 }
3900 case 'serverversion': {
3901 if ((xxdialogMode == 2) && (xxdialogTag == 'MeshCentralServerUpdate')) {
3902 var x = '<div class=dialogText>';
3903 if (!message.tags.current) { message.tags.current = "Unknown"; }
3904 if (!message.tags.stable) { message.tags.stable = "Unknown"; }
3905 if (!message.tags.latest) { message.tags.latest = "Unknown"; }
3906 x += addHtmlValue2("Current Version", '<b>' + EscapeHtml(message.tags.current) + '</b>');
3907 x += '<hr />';
3908 x += addHtmlValue2('<label><input id=d2updateCheck1 type=checkbox class="form-check-input me-2" onclick=server_showVersionDlgUpdate()' + (((message.tags.stable == "Unknown") || (message.tags.current == message.tags.stable) || ((features & 2048) == 0)) ? ' disabled' : '') + ' /> ' + "Stable Version" + '</label>', '<b>' + EscapeHtml(message.tags.stable) + '</b>');
3909 x += addHtmlValue2('<label><input id=d2updateCheck2 type=checkbox class="form-check-input me-2" onclick=server_showVersionDlgUpdate()' + (((message.tags.latest == "Unknown") || (message.tags.current == message.tags.latest) || ((features & 2048) == 0)) ? ' disabled' : '') + ' /> ' + "Latest Version" + '</label>', '<b>' + EscapeHtml(message.tags.latest) + '</b>');
3910 x += '</div>';
3911 if (((message.tags.current == message.tags.latest) && (message.tags.current == message.tags.stable)) || ((features & 2048) == 0)) {
3912 setModalContent('xxAddAgent', "MeshCentral Version", x);
3913 showModal('xxAddAgentModal', 'idx_dlgOkButton');
3914 } else {
3915 x += '<hr />' + "Check and click OK to start server self-update.";
3916 xxdialogTag = message.tags;
3917 setModalContent('xxAddAgent', "MeshCentral Version", x);
3918 showModal('xxAddAgentModal', 'idx_dlgOkButton', function () {
3919 server_showVersionDlgEx(2, message.tags);
3920 });
3921 server_showVersionDlgUpdate();
3922 }
3923 }
3924 break;
3925 }
3926 case 'servererrors': {
3927 if ((xxdialogMode == 2) && (xxdialogTag == 'MeshCentralServerErrors')) {
3928 if (message.data == null) {
3929 setModalContent('xxAddAgent', "Server Errors", "Server has no error log.");
3930 showModal('xxAddAgentModal', 'idx_dlgOkButton');
3931 } else {
3932 var x = '<div class="dialogText dialogTextLog"><pre id=d2ServerErrorsLogPre>' + EscapeHtml(message.data) + '</pre></div>';
3933 x += '<br /><div style=float:right><i role=button class="fa-fw fa-solid fa-download fa-sm" title="' + "Download error log" + '" onclick=d2CopyServerErrorsToClip()></i></div>';
3934 x += '<div><label><input id=d2clearErrorsCheck type=checkbox class="form-check-input me-2" onclick=server_showErrorsDlgUpdate() /> ' + "Check and click OK to clear error log." + '</label></div>';
3935 setModalContent('xxAddAgent', "MeshCentral Server Errors", x);
3936 showModal('xxAddAgentModal', 'idx_dlgOkButton', server_showErrorsDlgEx);
3937 server_showErrorsDlgUpdate();
3938 }
3939 }
3940 break;
3941 }
3942 case 'serverconfig': {
3943 if ((xxdialogMode == 2) && (xxdialogTag == 'MeshCentralServerConfig')) {
3944 if (message.data == null) {
3945 setModalContent('xxAddAgent', "Server Configuration", 'Server has no config file.', 'extra-large');
3946 } else {
3947 setModalContent('xxAddAgent', "Server Configuration", 4, 'extra-large');
3948 QV('d4EncodingButton', false);
3949 QV('d4LineBreakButton', false);
3950 Q('d4editorarea').value = message.data;
3951 Q('d4editorarea').setAttribute('readonly', 'readonly');
3952 }
3953 showModal('xxAddAgentModal', 'idx_dlgOkButton');
3954 }
3955 break;
3956 }
3957 case 'serverconsole': {
3958 p15consoleReceive('serverconsole', message.value);
3959 break;
3960 }
3961 case 'events': {
3962 if ((message.nodeid != null) && (currentNode != null) && (message.nodeid == currentNode._id)) {
3963 currentDeviceEvents = message.events;
3964 mainUpdate(1024);
3965 } else if ((message.userid != null) && (currentUser != null) && (message.userid == currentUser._id)) {
3966 currentUserEvents = message.events;
3967 mainUpdate(2048);
3968 } else {
3969 events = message.events;
3970 mainUpdate(32);
3971 }
3972 break;
3973 }
3974 case 'recordings': {
3975 p52recordings = message.events;
3976 if (message.error != null) { p52recordings = message.error; }
3977 updateRecordings();
3978 break;
3979 }
3980 case 'getcookie': {
3981 if (message.tag == 'MCRouter') {
3982 var servername = serverinfo.name;
3983 if ((servername.indexOf('.') == -1) || ((features & 2) != 0)) { servername = window.location.hostname; } // If the server name is not set or it's in LAN-only mode, use the URL hostname as server name.
3984 var domainUrlNoSlash = domainUrl.substring(0, domainUrl.length - 1);
3985 var portStr = (serverinfo.port == 443) ? '' : (':' + serverinfo.port);
3986 var url = 'mcrouter://' + servername + portStr + domainUrl + 'control.ashx?c=' + authCookie + '&t=' + serverinfo.tlshash + '&l={{{lang}}}' + (urlargs.key ? ('&key=' + urlargs.key) : '');
3987 if (message.nodeid != null) { url += ('&nodeid=' + message.nodeid); }
3988 if (message.tcpport != null) { url += ('&protocol=1&remoteport=' + message.tcpport); }
3989 if (message.localRelay) { url += '&local=1'; }
3990 if (message.localport) { url += '&localport=' + message.localport; }
3991 if (message.name != null) { url += ('&name=' + encodeURIComponentEx(message.name)); }
3992 if (message.ip != null) { url += ('&remoteip=' + message.ip); }
3993 url += ('&appid=' + message.protocol + '&autoexit=1'); // Protocol: 0 = Custom, 1 = HTTP, 2 = HTTPS, 3 = RDP, 4 = PuTTY, 5 = WinSCP, 6 = MCRDesktop, 7 = MCRFiles
3994 //console.log(url);
3995 downloadFile(url, '');
3996 } else if (message.tag == 'novnc') {
3997 var vncurl = window.location.origin + domainUrl + 'novnc/vnc.html?ws=wss%3A%2F%2F' + window.location.host + encodeURIComponentEx(domainUrl) + (message.localRelay ? 'local' : 'mesh') + 'relay.ashx%3Fauth%3D' + message.cookie + '&show_dot=1' + (urlargs.key ? ('&key=' + urlargs.key) : '') + '&l={{{lang}}}';
3998 var node = getNodeFromId(message.nodeid);
3999 if (node != null) { vncurl += '&name=' + encodeURIComponentEx(node.name); }
4000 safeNewWindow(vncurl, 'mcnovnc/' + message.nodeid);
4001 } else if (message.tag == 'mstsc') {
4002 var rdpurl = window.location.origin + domainUrl + 'mstsc.html?ws=' + message.cookie + (urlargs.key ? ('&key=' + urlargs.key) : '');
4003 var node = getNodeFromId(message.nodeid);
4004 if (node != null) { rdpurl += '&name=' + encodeURIComponentEx(node.name); }
4005 if (message.localRelay) { rdpurl += '&local=1'; }
4006 safeNewWindow(rdpurl, 'mcmstsc/' + message.nodeid);
4007 } else if (message.tag == 'ssh') {
4008 var sshurl = window.location.origin + domainUrl + 'ssh.html?ws=' + message.cookie + (urlargs.key ? ('&key=' + urlargs.key) : '');
4009 var node = getNodeFromId(message.nodeid);
4010 if (node != null) { sshurl += '&name=' + encodeURIComponentEx(node.name); }
4011 if (message.localRelay) { sshurl += '&local=1'; }
4012 safeNewWindow(sshurl, 'mcssh/' + message.nodeid);
4013 }
4014 break;
4015 }
4016 case 'getNotes': {
4017 var n = Q('d2devNotes');
4018 if (n && (message.id == decodeURIComponent(n.attributes['noteid'].value))) {
4019 if (message.notes) { QH('d2devNotes', decodeURIComponent(message.notes)); } else { QH('d2devNotes', ''); }
4020 var ro = (n.attributes['ro'].value == 'true');
4021 if (ro == false) { // If we have permissions, set read/write on this note.
4022 n.removeAttribute('readonly');
4023 QE('idx_dlgOkButton', true);
4024 QV('idx_dlgOkButton', true);
4025 focusTextBox('d2devNotes');
4026 }
4027 } else {
4028 Q('notesPanelArea').innerHTML = (message.notes && marked && DOMPurify) ? DOMPurify.sanitize(marked.parse(decodeURIComponent(message.notes), { breaks: true }), { USE_PROFILES: { html: true } }) : '';
4029 if ((showNotesPanel === 'true') && message.notes) { QV('notesPanel', true); } else { QV('notesPanel', false); }
4030 }
4031 break;
4032 }
4033 case 'otpauth-request': {
4034 if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-request')) {
4035 if (message.err != null) {
4036 var otpauthErrors = ['', "2FA is locked", "Backup codes are locked", "Login token in use", "OTP 2FA not allowed", "Account is locked", "Unable to load OTPLIB"];
4037 if ((message.err > 0) && (message.err < otpauthErrors.length)) { QH('d2optinfo', otpauthErrors[message.err]); } else { QH('d2optinfo', format("Error #{0}", message.err)); }
4038 } else {
4039 var secret = message.secret;
4040 if (secret.length == 52) { secret = secret.split(/(.............)/).filter(Boolean).join(' '); }
4041 else if (secret.length == 32) { secret = secret.split(/(....)/).filter(Boolean).join(' '); secret = secret.substring(0, 20) + '<br/>' + secret.substring(20) }
4042 QH('d2optinfo', '<table><tr><td style=vertical-align:top>' + format("Install" + ' <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2" rel="noreferrer noopener" target=_blank>' + "Google Authenticator" + '</a> ' + "or a compatible application and scan the barcode, use <a href=\"{0}\" rel=\"noreferrer noopener\" target=_blank>this link</a> or enter the secret. Then, enter the current 6 digit token below to activate 2-Step login.", message.url) + '<br /><br />' + 'Secret <img src=images/link4.png height=10 width=10 title="' + "Copy Secret to clipboard" + '" style=cursor:pointer onclick=d2CopySecretToClip()>' + '<br /><tt id=d2optsecret secret="' + message.secret + '" style=font-size:12px>' + secret + '</tt><br /><br /></td><td style=width:1px;vertical-align:top><a href="' + message.url + '" rel="noreferrer noopener" target=_blank><div id="qrcode"></div></a></td><tr><td colspan=2 style="text-align:center;border-top:1px solid black"><br />' + "Enter the token here for 2-step login:" + ' <input type=text autocomplete="one-time-code" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" onkeyup=account_addOtpCheck(event) onkeydown=account_addOtpCheck() maxlength=6 id=d2otpauthinput type=text></td></table>');
4043 new QRCode(Q('qrcode'), { text: message.url, width: 128, height: 128, colorDark: '#000000', colorLight: '#EEE', correctLevel: QRCode.CorrectLevel.H });
4044 QV('idx_dlgOkButton', true);
4045 QE('idx_dlgOkButton', false);
4046 Q('d2otpauthinput').focus();
4047 }
4048 }
4049 break;
4050 }
4051 case 'otpauth-setup': {
4052 if (xxdialogMode) return;
4053 setModalContent('xxAddAgent', "Authenticator App", message.success ? ('<b style=color:green>' + "Authenticator app activation successful." + '</b> ' + "You will now need a valid token to login again.") : ('<b style=color:red>' + "2-step login activation failed." + '</b> ' + "Clear the secret from the application and try again. You only have a few minutes to enter the proper code."));
4054 showModal('xxAddAgentModal', 'idx_dlgOkButton');
4055 break;
4056 }
4057 case 'otpauth-clear': {
4058 if (xxdialogMode) return;
4059 setModalContent('xxAddAgent', "Authenticator App", message.success ? ('<b>' + "Authenticator application removed." + '</b> ' + "You can reactivate this feature at any time.") : ('<b style=color:red>' + "2-step login activation removal failed." + '</b> ' + "Try again."));
4060 showModal('xxAddAgentModal', 'idx_dlgOkButton');
4061 break;
4062 }
4063 case 'otpauth-getpasswords': {
4064 if (xxdialogMode == 2 && (xxdialogTag != 'otpauth-manage')) return;
4065 var x = "One time tokens can be used as secondary authentication. Generate a set, print them and keep them in a safe place.";
4066 x += '<div style="border-radius:6px;border: 2px dashed #888;width:100%;margin-top:8px"><div style="padding:8px;font-family:Arial, Helvetica, sans-serif;font-size:20px;font-weight:bold"><table class=selecttext style=width:100%;text-align:center>';
4067 if (message.passwords) {
4068 var j = 0, clipb = '';
4069 for (var i in message.passwords) {
4070 if (++j % 2) { x += '<tr>'; }
4071 var p = '' + message.passwords[i].p;
4072 while (p.length < 8) { p = '0' + p; }
4073 if (message.passwords[i].u === true) {
4074 x += '<td>' + p.substring(0, 4) + '&nbsp;' + p.substring(4);
4075 if (clipb != '') { clipb += ' '; }
4076 clipb += p;
4077 } else {
4078 x += '<td><strike style=color:#BBB>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); + '</strike>';
4079 }
4080 }
4081 } else {
4082 x += '<tr><td>' + "No Active Tokens";
4083 }
4084 x += '</table></div></div><br />';
4085 x += '<div class="btn-group">';
4086 x += '<button type=button class="btn btn-warning" onclick="account_manageOtp(1);">' + "Generate New Tokens" + '</input>';
4087 if (message.passwords != null) {
4088 x += '<button type=button class="btn btn-danger" onclick="account_manageOtp(2);">' + "Clear Tokens" + '</button></div>';
4089 x += '&nbsp;<i class="fa-regular fa-clipboard" title="' + "Copy valid codes to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(clipb) + '")></i>';
4090 } else {
4091 x += '</div>'
4092 }
4093 xxdialogTag = 'otpauth-manage';
4094 setModalContent('xxAddAgent', "Manage Backup Codes", x);
4095 showModal('xxAddAgentModal', 'idx_dlgOkButton');
4096 break;
4097 }
4098 case 'otp-hkey-get': {
4099 if (xxdialogMode && (xxdialogTag != 'otpauth-hardware-manage')) return;
4100 var start = '<div style="border-radius:6px;border:2px solid #CCC;background-color:#BBB;width:100%;box-sizing:border-box;margin-bottom:6px"><div style="margin:3px;font-family:Arial, Helvetica, sans-serif;font-size:16px;font-weight:bold"><table style=width:100%;text-align:left>';
4101 var end = '</table></div></div>';
4102 var x = '<a href="https://www.yubico.com/" rel="noreferrer noopener" target="_blank">' + "Hardware keys" + '</a> ' + "are used as secondary login authentication.";
4103 x += '<div class="backgroundContainer" style="max-height:150px;overflow-y:auto;overflow-x:hidden;margin-top:6px;margin-bottom:6px">';
4104 if (message.keys && message.keys.length > 0) {
4105 for (var i in message.keys) {
4106 var key = message.keys[i], type = (key.type == 2) ? 'OTP' : 'WebAuthn';
4107 x += start + '<tr style=margin:5px><td style=width:30px><img width=24 height=18 src="images/hardware-key-' + type + '-24.png" style=margin-top:4px><td style=width:250px>' + key.name + '<td style=text-align:right><button type=button class="btn btn-danger" onclick=account_removehkey(' + key.i + ')>' + "Remove" + '</button>' + end;
4108 }
4109 } else {
4110 x += start + '<tr style=text-align:center><td>' + "No Keys Configured" + end;
4111 }
4112 x += '</div>';
4113 x += '<div>';
4114 var hkeycount = (typeof userinfo.otphkeys == 'number') ? userinfo.otphkeys : 0;
4115 if ((typeof serverinfo.maxfidokeys != 'number') || (serverinfo.maxfidokeys > hkeycount)) { // Check if we we reached maximum hardware keys
4116 if ((features & 0x00020000) != 0) { x += '<button class="btn btn-primary" id=d2addkey3 type=button onclick="account_addhkey(3);">' + "Add Key" + '</input>'; }
4117 if ((features & 0x00004000) != 0) { x += '<button class="btn btn-primary" id=d2addkey2 type=button onclick="account_addhkey(2);">' + "Add YubiKey&reg; OTP" +'</input>'; }
4118 } else {
4119 x += "Maximum keys reached.";
4120 }
4121 x += '</div><br />';
4122 xxdialogTag = 'otpauth-hardware-manage';
4123 setModalContent('xxAddAgent', "Manage Security Keys", x);
4124 showModal('xxAddAgentModal', 'idx_dlgOkButton');
4125 if (u2fSupported() == false) { QE('d2addkey1', false); }
4126 break;
4127 }
4128 case 'otp-hkey-yubikey-add': {
4129 if (message.result) {
4130 meshserver.send({ action: 'otp-hkey-get' }); // Success, ask for the full list of keys.
4131 } else {
4132 setModalContent('xxAddAgent', "Add Security Key", '<br />' + "Error, Unable to add key." + '<br /><br />');
4133 showModal('xxAddAgentModal', 'idx_dlgOkButton');
4134 }
4135 break;
4136 }
4137 case 'otp-hkey-setup-response': {
4138 if (xxdialogMode && (xxdialogTag != 'otpauth-hardware-manage')) return;
4139 if (message.result == true) {
4140 meshserver.send({ action: 'otp-hkey-get' }); // Success, ask for the full list of keys.
4141 } else {
4142 setModalContent('xxAddAgent', "Add Security Key", '<br />' + "Error, Unable to add key." + '<br /><br />');
4143 showModal('xxAddAgentModal', 'idx_dlgOkButton');
4144
4145 }
4146 break;
4147 }
4148 case 'webauthn-startregister': {
4149 if (xxdialogMode && (xxdialogTag != 'otpauth-hardware-manage')) return;
4150 var x = "Press the key button now." + '<br /><br /><div style=width:100%;text-align:center><img width=120 height=117 src="images/hardware-keypress-120.png" /></div><input id=dp1keyname style=display:none value=' + message.name + ' />';
4151 setModalContent('xxAddAgent', "Add Security Key", x);
4152 var publicKey = message.request;
4153 message.request.challenge = Uint8Array.from(atob(message.request.challenge), function (c) { return c.charCodeAt(0) })
4154 message.request.user.id = Uint8Array.from(atob(message.request.user.id), function (c) { return c.charCodeAt(0) })
4155 navigator.credentials.create({ publicKey: publicKey }).then(function (newCredentialInfo) {
4156 // Public key credential
4157 meshserver.send({ action: 'webauthn-endregister', response: { rawId: btoa(String.fromCharCode.apply(null, new Uint8Array(newCredentialInfo.rawId))), response: { attestationObject: btoa(String.fromCharCode.apply(null, new Uint8Array(newCredentialInfo.response.attestationObject))), clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(newCredentialInfo.response.clientDataJSON))) }, type: newCredentialInfo.type } });
4158 }, function (error) {
4159 // Error
4160 console.log("ERROR: " + error);
4161 setModalContent('xxAddAgent', "Add Security Key", "ERROR: " + error);
4162 });
4163 showModal('xxAddAgentModal', 'idx_dlgOkButton');
4164 break;
4165 }
4166 case 'verifyPhone': {
4167 if (xxdialogMode && (xxdialogTag != 'verifyPhone')) return;
4168 var x = '<table><tr><td><img src="images/phone80.png" style=padding:8px>';
4169 x += '<td>' + "Check your phone and enter the verification code.";
4170 x += '<br /><br /><div style=width:100%;text-align:center>' + "Verification code:" + ' <input type=tel pattern="[0-9]" inputmode="number" maxlength=6 id=d2phoneCodeInput onKeyUp=account_managePhoneCodeValidate() onkeypress="if (event.key==\'Enter\') account_managePhoneCodeValidate(1)"></div></table>';
4171 setModalContent('xxAddAgent', "Phone Notifications", x);
4172 showModal('xxAddAgentModal', 'idx_dlgOkButton', () => account_managePhoneConfirm(3, message.cookie));
4173 Q('d2phoneCodeInput').focus();
4174 account_managePhoneCodeValidate();
4175 break;
4176 }
4177 case 'verifyMessaging': {
4178 if (xxdialogMode && (xxdialogTag != 'verifyMessaging')) return;
4179 var x = '<table><tr><td><img src="images/messaging40.png" style=padding:8px>';
4180 x += '<td>' + "Check your messaging application and enter the verification code.";
4181 x += '<br /><br /><div style=width:100%;text-align:center>' + "Verification code:" + ' <input type=tel pattern="[0-9]" inputmode="number" maxlength=6 id=d2phoneCodeInput onKeyUp=account_managePhoneCodeValidate() onkeypress="if (event.key==\'Enter\') account_managePhoneCodeValidate(1)"></div></table>';
4182 setModalContent('xxAddAgent', "Messaging Notifications", x);
4183 showModal('xxAddAgentModal', 'idx_dlgOkButton', () => account_manageMessagingConfirm(3, message.cookie));
4184 Q('d2phoneCodeInput').focus();
4185 account_managePhoneCodeValidate();
4186 break;
4187 }
4188 case 'fileoperation': {
4189 // View the file in the dialog box
4190 var p5editSaveBack = function (b, tag) {
4191 var data;
4192 var value = Q('d4editorarea').value;
4193 value = d4EditLineBreakVal === 0 ? value.replace(/\r?\n|\r/g, '\r\n') : // Windows
4194 d4EditLineBreakVal === 2 ? value.replace(/\r\n|\n/g, '\r') : // Mac
4195 value.replace(/\r\n|\r/g, '\n'); // Linux
4196 if (d4EditEncodingVal == 1) {
4197 data = encode_utf8(value); // UTF8 encoding
4198 } else {
4199 data = value; // RAW encoding
4200 }
4201 meshserver.send({ action: 'fileoperation', fileop: 'set', path: tag.path, file: tag.file, data: btoa(data) });
4202 }
4203 setModalContent('xxAddAgent', EscapeHtml(message.file), 4, 'extra-large');
4204 showModal('xxAddAgentModal', 'idx_dlgOkButton', () => p5editSaveBack(3, message));
4205 QV('d4EncodingButton', true);
4206 QV('d4LineBreakButton', true);
4207 if (d4EditEncodingVal == 1) {
4208 Q('d4editorarea').value = decode_utf8(atob(message.data)); // UTF8 Encoding
4209 } else {
4210 Q('d4editorarea').value = atob(message.data); // RAW Encoding
4211 }
4212 break;
4213 }
4214 case 'event': {
4215 if (!message.event.nolog) {
4216 if (currentNode && (message.event.nodeid == currentNode._id) && (currentDeviceEvents != null)) {
4217 // If this event has a nodeid and we are looking at this node, update the log in real time.
4218 if ((message.event.action == p16filterevents.value) || (p16filterevents.value == "")) {
4219 if(currentDeviceEvents != null) {
4220 currentDeviceEvents.unshift(message.event);
4221 var eventLimit = parseInt(p16limitdropdown.value);
4222 while (currentDeviceEvents.length > eventLimit) { currentDeviceEvents.pop(); } // Remove element(s) at the end
4223 }
4224 mainUpdate(1024);
4225 }
4226 }
4227
4228 if (currentUser && (message.event.userid == currentUser._id)) {
4229 // If this event has a userid and we are looking at this user, update the log in real time.
4230 if ((message.event.action == p31filterevents.value) || (p31filterevents.value == "")) {
4231 if(currentUserEvents != null) {
4232 currentUserEvents.unshift(message.event);
4233 var eventLimit = parseInt(p31limitdropdown.value);
4234 while (currentUserEvents.length > eventLimit) { currentUserEvents.pop(); } // Remove element(s) at the end
4235 }
4236 mainUpdate(2048);
4237 }
4238 }
4239
4240 // Add this event to the main events log.
4241 if ((message.event.action == p3filterevents.value) || (p3filterevents.value == "")) {
4242 if(events != null) {
4243 events.unshift(message.event);
4244 var eventLimit = parseInt(p3limitdropdown.value);
4245 while (events.length > eventLimit) { events.pop(); } // Remove element(s) at the end
4246 }
4247 mainUpdate(32);
4248 }
4249 }
4250 if (message.event.noact) break; // Take no action on this event
4251
4252 switch (message.event.action) {
4253 case 'serverinfochange': {
4254 if (message.event.lock2factor != null) { serverinfo.lock2factor = message.event.lock2factor; updateSelf(); updateSiteAdmin(); }
4255 break;
4256 }
4257 case 'deviceShareUpdate': {
4258 if (message.event.nodeid != deviceSharesReq) break;
4259 deviceSharesNode = message.event.nodeid;
4260 deviceShares = message.event.deviceShares;
4261 if (currentNode._id == deviceSharesNode) { gotoDevice(currentNode._id, xxcurrentView, true); }
4262 break;
4263 }
4264 case 'agentlog': {
4265 if (message.event.msgid == 98) {
4266 // This is a agent help request, popup a notification.
4267 if (GetNodeRights(message.event.nodeid) > 0) {
4268 var n = getNodeFromId(message.event.nodeid);
4269 addNotification({ text: format("Help requested from {0}: {1}", message.event.msgArgs[0], message.event.msgArgs[1]), title: n.name, icon: n.icon, nodeid: message.event.nodeid });
4270 }
4271 }
4272 break;
4273 }
4274 case 'recording': {
4275 if ((p52recordings != null) && (typeof p52recordings == 'object')) { p52recordings.unshift(message.event); message.event.present = 1; updateRecordings(); }
4276 break;
4277 }
4278 case 'userWebState': {
4279 // New user web state, update the web page as needed
4280 try {
4281 if (localStorage != null) {
4282 // TODO: The problem with this "old" values is that if changed from a different tab, the old and new values will be the same.
4283 // So comparing against these values is not a good idea.
4284 var oldShowRealNames = localStorage.getItem('showRealNames');
4285 var oldUiMode = localStorage.getItem('uiMode');
4286 var oldSort = localStorage.getItem('sort');
4287 var oldLoctag = localStorage.getItem('loctag');
4288 var oldNightMode = localStorage.getItem('nightMode');
4289 var oldFooterBar = localStorage.getItem('footerBar');
4290
4291 var webstate = JSON.parse(message.event.state);
4292 for (var i in webstate) { localStorage.setItem(i, webstate[i]); }
4293 customIconValues = loadCustomIconState();
4294 applyIconCustomization(customIconValues);
4295
4296 // Update the web page
4297 //if ((webstate.deskAspectRatio != null) && (webstate.deskAspectRatio != deskAspectRatio)) { deskAspectRatio = webstate.deskAspectRatio; deskAdjust(); }
4298 if ((webstate.showRealNames != null) && (webstate.showRealNames != oldShowRealNames)) { showRealNames = Q('RealNameCheckBox').checked = (webstate.showRealNames == '1'); mainUpdate(6); }
4299 if ((webstate.uiMode != null) && (webstate.uiMode != oldUiMode)) { userInterfaceSelectMenu(parseInt(webstate.uiMode)); }
4300 if ((webstate.sort != null) && (webstate.sort != oldSort)) { document.getElementById('sortselect').selectedIndex = sort = parseInt(webstate.sort); mainUpdate(6); }
4301 if ((webstate.loctag != null) && (webstate.loctag != oldLoctag)) { if (webstate.loctag != null) { args.locale = webstate.loctag; } else { delete args.locale; } mainUpdate(0xFFFFFFFF); }
4302 if ((webstate.nightMode != null) && (webstate.nightMode != oldNightMode)) { putstore('nightMode', webstate.nightMode); setNightMode(); }
4303 if ((webstate.footerBar != null) && (webstate.footerBar != oldFooterBar)) { footerBar = (webstate.footerBar == '1'); QS('container')['grid-template-rows'] = null; QS('container')['-ms-grid-rows'] = null; adjustPanels(); }
4304 if ((webstate.stars != null) && (webstate.stars != JSON.stringify(stars))) {
4305 stars = JSON.parse(webstate.stars);
4306 if (Q('DevFilterSelect').value == 3) { mainUpdate(5); } else { mainUpdate(4); }
4307 if (currentNode) { refreshDevice(currentNode._id); }
4308 }
4309 if (webstate.deskKeyShortcuts != null) {
4310 // Set the user's desktop shortcut keys
4311 deskKeyboardShortcuts = [];
4312 var deskKeyboardShortcutsStr = webstate.deskKeyShortcuts.split(',');
4313 for (var i in deskKeyboardShortcutsStr) { if (deskKeyboardShortcutsStr[i] != "") { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); } }
4314 updateDeskShortcutKeys();
4315 }
4316 if (webstate.deskStrings != null) {
4317 // Set the user's desktop strings
4318 var s = null;
4319 try { s = JSON.parse(webstate.deskStrings); } catch (ex) { }
4320 if (Array.isArray(s)) { deskKeyboardStrings = s; updateDesktopStrings(); }
4321 }
4322 }
4323 } catch (ex) { }
4324 break;
4325 }
4326 case 'servertimelinestats': { addServerTimelineStats(message.event.data); break; }
4327 case 'accountcreate':
4328 case 'accountchange': {
4329 // An account was created or changed
4330 if ((typeof message.event.account != 'object') || (message.event.account == null)) { console.log(message.event); return; }
4331 if (userinfo._id == message.event.account._id) {
4332 var newsiteadmin = message.event.account.siteadmin ? message.event.account.siteadmin : 0;
4333 var oldsiteadmin = userinfo.siteadmin ? userinfo.siteadmin : 0;
4334 var newRemoveRights = message.event.account.removeRights ? message.event.account.removeRights : 0;
4335 var oldRemoveRights = userinfo.removeRights ? userinfo.removeRights : 0;
4336 if ((message.event.account.quota != userinfo.quota) || (((userinfo.siteadmin & 8) == 0) && ((message.event.account.siteadmin & 8) != 0))) { meshserver.send({ action: 'files' }); }
4337 var oldgroups = userinfo.groups;
4338 userinfo = message.event.account;
4339 if ((oldsiteadmin != newsiteadmin) || (oldRemoveRights != newRemoveRights) || (message.event.accountImageChange == 1)) { // If the site admin permission or user image has changed...
4340 if (message.event.accountImageChange == 1) { userinfo.accountImageRnd = Math.floor(Math.random() * 9999999999); }
4341 updateSiteAdmin();
4342 }
4343 updateSelf();
4344
4345 if ((userinfo.siteadmin & 2) != 0) {
4346 // Compare our groups
4347 var og = oldgroups ? oldgroups : [];
4348 var ng = userinfo.groups ? userinfo.groups : [];
4349 if (og.join(',') != ng.join(',')) {
4350 // Our groups have changed, re-ask for a list of users.
4351 users = wssessions = null;
4352 meshserver.send({ action: 'users' });
4353 meshserver.send({ action: 'wssessioncount' });
4354 }
4355 }
4356
4357 // If our list of nodes may have changes, request the new list now.
4358 if (message.event.nodeListChange == userinfo._id) { meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip }); }
4359 }
4360 if (currentNode) { refreshDevice(currentNode._id); }
4361 if (users == null) break;
4362
4363 // Check if the account is part of our user group
4364 if ((userinfo.groups == null) || (userinfo.groups.length == 0) || (findOne(message.event.account.groups, userinfo.groups) == true)) {
4365 users[message.event.account._id] = message.event.account; // Part of our groups, update this user.
4366 } else {
4367 delete users[message.event.account._id]; // No longer part of our groups, remove this user.
4368 }
4369
4370 mainUpdate(4 | 16384);
4371 break;
4372 }
4373 case 'accountremove': {
4374 // An account was removed
4375 if (users == null) break;
4376 delete users[message.event.userid];
4377 mainUpdate(16384);
4378 break;
4379 }
4380 case 'createusergroup':
4381 case 'usergroupchange': {
4382 // User group changed
4383 if (usergroups == null) { usergroups = {}; }
4384 var ugroup = usergroups[message.event.ugrpid];
4385 if (ugroup == null) {
4386 // This is a new user group for us
4387 usergroups[message.event.ugrpid] = { _id: message.event.ugrpid, name: message.event.name, desc: message.event.desc, domain: message.event.domain, links: message.event.links };
4388 } else {
4389 // This is an existing user group
4390 ugroup.name = message.event.name;
4391 if (message.event.desc) { ugroup.desc = message.event.desc; } else { delete ugroup.desc; }
4392 if (message.event.links) { ugroup.links = message.event.links; } else { delete ugroup.links; }
4393 if (message.event.flags) { ugroup.flags = message.event.flags; } else { delete ugroup.flags; }
4394 if (typeof message.event.consent == 'number') { ugroup.consent = message.event.consent; }
4395 }
4396 mainUpdate(4096 + 8192 + 16384);
4397
4398 // Group update, refresh all our device groups and nodes. TODO: Optimize this to only do this when needed.
4399 meshserver.send({ action: 'meshes' });
4400 meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip });
4401 break;
4402 }
4403 case 'deleteusergroup': {
4404 // User group removed
4405 if ((usergroups != null) && (usergroups[message.event.ugrpid] != null)) {
4406 delete usergroups[message.event.ugrpid];
4407 var c = 0;
4408 for (var i in usergroups) { c++; }
4409 if (c == 0) { usergroups = null; } // If user groups is empty, set it to null.
4410 mainUpdate(8192 + 16384);
4411 }
4412 break;
4413 }
4414 case 'createmesh': {
4415 // A new mesh was created
4416 if ((meshes[message.event.meshid] == null) && ((serverinfo.manageAllDeviceGroups) || (message.event.mesh.links[userinfo._id] != null))) { // Check if this is a mesh create for a mesh we own. If site administrator, we get all messages so need to ignore some.
4417 meshes[message.event.meshid] = message.event.mesh;
4418 mainUpdate(4 + 128 + 8192 + 16384);
4419 meshserver.send({ action: 'files' });
4420 }
4421 break;
4422 }
4423 case 'meshchange': {
4424 // Update mesh information
4425 if (meshes[message.event.meshid] == null) {
4426 // Check if we have any access to this device group
4427 var add = false;
4428 if (message.event.links[userinfo._id] != null) { add = true; }
4429 if (userinfo.links[message.event.meshid] != null) { add = true; }
4430 for (var i in userinfo.links) { if ((i.startsWith('ugrp/')) && (message.event.links[i] != null)) { add = true; } }
4431
4432 // This is a new mesh for us
4433 if (add) {
4434 meshes[message.event.meshid] = { _id: message.event.meshid, name: message.event.name, mtype: message.event.mtype, desc: message.event.desc, links: message.event.links, amt: message.event.amt, invite: message.event.invite, expireDevs: message.event.expireDevs, relayid: message.event.relayid };
4435 meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip }); // Request a refresh of all nodes (TODO: We could optimize this to only request nodes for the new mesh).
4436 }
4437 } else {
4438 // This is an existing device group
4439 if (message.event.name != null) {
4440 meshes[message.event.meshid].name = message.event.name;
4441 for (var i in nodes) { if (nodes[i].meshid == message.event.meshid) { nodes[i].meshnamel = message.event.name.toLowerCase(); } }
4442 }
4443 if (message.event.desc != null) { meshes[message.event.meshid].desc = message.event.desc; }
4444 if (message.event.flags != null) { meshes[message.event.meshid].flags = message.event.flags; }
4445 if (message.event.consent != null) { meshes[message.event.meshid].consent = message.event.consent; }
4446 if (message.event.links) { meshes[message.event.meshid].links = message.event.links; }
4447 if (message.event.amt) { meshes[message.event.meshid].amt = message.event.amt; }
4448 if (message.event.invite != null) { meshes[message.event.meshid].invite = message.event.invite; } else { delete meshes[message.event.meshid].invite; }
4449 if (message.event.expireDevs != null) { if (message.event.expireDevs > 0) { meshes[message.event.meshid].expireDevs = message.event.expireDevs; } else { delete meshes[message.event.meshid].expireDevs; } }
4450 if (message.event.relayid != null) { meshes[message.event.meshid].relayid = message.event.relayid; }
4451
4452 // Check if we lost rights to this mesh in this change.
4453 if (IsMeshViewable(message.event.meshid) == false) {
4454 if ((xxcurrentView == 20) && (currentMesh == meshes[message.event.meshid])) go(2);
4455 delete meshes[message.event.meshid];
4456
4457 // Delete all nodes in that mesh, except ones with direct links
4458 var newnodes = [];
4459 for (var i in nodes) { if ((nodes[i].meshid != message.event.meshid) || ((userinfo.links != null) && (userinfo.links[nodes[i]._id] != null))) { newnodes.push(nodes[i]); } }
4460 nodes = newnodes;
4461
4462 // If we are looking at a node that is no longer visible, move back to "My Devices"
4463 if ((xxcurrentView >= 10) && (xxcurrentView < 20) && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(1); }
4464 }
4465 }
4466 mainUpdate(4 + 128 + 8192 + 16384);
4467 if (currentNode && !IsNodeViewable(currentNode)) { currentNode = null; if ((xxcurrentView >= 10) && (xxcurrentView < 20)) { go(1); } }
4468 //meshserver.send({ action: 'files' }); // TODO: Why do we need to do this??
4469
4470 // If we are looking at a mesh that is now deleted, move back to "My Account"
4471 if (xxcurrentView == 20 && currentMesh._id == message.event.meshid) { mainUpdate(4096); }
4472 break;
4473 }
4474 case 'deletemesh': {
4475 // Delete the mesh
4476 if (meshes[message.event.meshid]) {
4477 delete meshes[message.event.meshid];
4478 mainUpdate(128);
4479 meshserver.send({ action: 'files' });
4480 }
4481
4482 // Delete all nodes in that mesh
4483 var newnodes = [];
4484 if (nodes != null) { for (var i in nodes) { if (nodes[i].meshid != message.event.meshid) { newnodes.push(nodes[i]); } } }
4485 nodes = newnodes;
4486 mainUpdate(4 + 8192 + 16384);
4487
4488 // If we are looking at a mesh that is now deleted, move back to "My Account"
4489 if (xxcurrentView >= 20 && xxcurrentView < 30 && currentMesh._id == message.event.meshid) { setDialogMode(0); go(2); }
4490 // If we are looking at a node in the deleted mesh, move back to "My Devices"
4491 if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(1); }
4492 break;
4493 }
4494 case 'addnode': {
4495 var node = message.event.node;
4496 if (!meshes[node.meshid]) break; // This is a node for a mesh we don't know. Happens when we are site administrator, we get all messages.
4497 if (getNodeFromId(node._id) != null) break; // This node is already known.
4498 node.namel = node.name.toLowerCase();
4499 if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
4500 node.meshnamel = meshes[node.meshid] ? meshes[node.meshid].name.toLowerCase() : '*';
4501 node.state = 0;
4502 if (!node.icon) node.icon = 1;
4503 node.ident = ++nodeShortIdent;
4504 if (nodes == null) { }
4505 nodes.push(node);
4506
4507 // Web page update
4508 mainUpdate(1 | 2 | 4 | 16);
4509
4510 // If we are looking at the device group for this device, update that.
4511 if ((xxcurrentView == 20) && (currentMesh != null) && (currentMesh._id == node.meshid)) { mainUpdate(4096); }
4512
4513 break;
4514 }
4515 case 'removenode': {
4516 var index = -1;
4517 for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
4518 if (index != -1) {
4519 var node = nodes[index];
4520 if (currentNode == node) {
4521 if (xxcurrentView >= 10 && xxcurrentView < 20) { setDialogMode(0); go(1); }
4522 currentNode = null;
4523 // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
4524 }
4525 nodes.splice(index, 1);
4526
4527 // Web page update
4528 mainUpdate(4 | 16);
4529
4530 // If we are looking at the device group for this device, update that.
4531 if ((xxcurrentView == 20) && (currentMesh != null) && (currentMesh._id == node.meshid)) { mainUpdate(4096); }
4532 }
4533 break;
4534 }
4535 case 'changenode': {
4536 var index = -1;
4537 for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
4538 if (index != -1) {
4539 var node = nodes[index];
4540
4541 // Change the node
4542 node.name = message.event.node.name;
4543 node.rname = message.event.node.rname;
4544 node.lusers = message.event.node.lusers;
4545 node.upnusers = message.event.node.upnusers;
4546 node.users = message.event.node.users;
4547 node.host = message.event.node.host;
4548 node.desc = message.event.node.desc;
4549 node.ip = message.event.node.ip;
4550 node.osdesc = message.event.node.osdesc;
4551 node.publicip = message.event.node.publicip;
4552 node.iploc = message.event.node.iploc;
4553 node.wifiloc = message.event.node.wifiloc;
4554 node.gpsloc = message.event.node.gpsloc;
4555 node.tags = message.event.node.tags;
4556 node.ssh = message.event.node.ssh;
4557 node.rdp = message.event.node.rdp;
4558 node.userloc = message.event.node.userloc;
4559 node.rdpport = message.event.node.rdpport;
4560 node.rfbport = message.event.node.rfbport;
4561 node.sshport = message.event.node.sshport;
4562 node.httpport = message.event.node.httpport;
4563 node.httpsport = message.event.node.httpsport;
4564 node.consent = message.event.node.consent;
4565 node.pmt = message.event.node.pmt;
4566 if (message.event.node.links != null) { node.links = message.event.node.links; } else { delete node.links; }
4567 if (message.event.node.agent != null) {
4568 if (node.agent == null) node.agent = {};
4569 if (message.event.node.agent.ver != null) { node.agent.ver = message.event.node.agent.ver; }
4570 if (message.event.node.agent.id != null) { node.agent.id = message.event.node.agent.id; }
4571 if (message.event.node.agent.caps != null) { node.agent.caps = message.event.node.agent.caps; }
4572 if (message.event.node.agent.root != null) { node.agent.root = message.event.node.agent.root; }
4573 if (message.event.node.agent.core != null) { node.agent.core = message.event.node.agent.core; } else { if (node.agent.core) { delete node.agent.core; } }
4574 node.agent.tag = message.event.node.agent.tag;
4575 }
4576 if (message.event.node.intelamt != null) {
4577 if (node.intelamt == null) node.intelamt = {};
4578 if (message.event.node.intelamt.state != null) { node.intelamt.state = message.event.node.intelamt.state; }
4579 if (message.event.node.intelamt.host != null) { node.intelamt.user = message.event.node.intelamt.host; }
4580 if (message.event.node.intelamt.user != null) { node.intelamt.user = message.event.node.intelamt.user; } else { delete node.intelamt.user; }
4581 if (message.event.node.intelamt.tls != null) { node.intelamt.tls = message.event.node.intelamt.tls; }
4582 if (message.event.node.intelamt.ver != null) { node.intelamt.ver = message.event.node.intelamt.ver; }
4583 if (message.event.node.intelamt.tag != null) { node.intelamt.tag = message.event.node.intelamt.tag; }
4584 if (message.event.node.intelamt.uuid != null) { node.intelamt.uuid = message.event.node.intelamt.uuid; }
4585 if (message.event.node.intelamt.realm != null) { node.intelamt.realm = message.event.node.intelamt.realm; }
4586 if (message.event.node.intelamt.flags != null) { node.intelamt.flags = message.event.node.intelamt.flags; }
4587 if (message.event.node.intelamt.warn != null) { node.intelamt.warn = message.event.node.intelamt.warn; } else { delete node.intelamt.warn; }
4588 }
4589 if (message.event.node.av != null) { node.av = message.event.node.av; }
4590 if (message.event.node.lsc != null) { node.lsc = message.event.node.lsc; }
4591 if (message.event.node.wsc != null) { node.wsc = message.event.node.wsc; }
4592 if (message.event.node.defender != null) { node.defender = message.event.node.defender; }
4593 if (message.event.node.volumes != null) { node.volumes = message.event.node.volumes; }
4594 node.namel = node.name.toLowerCase();
4595 if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
4596 if (message.event.node.icon) { node.icon = message.event.node.icon; }
4597 if (message.event.node.lastbootuptime != null) { node.lastbootuptime = message.event.node.lastbootuptime; }
4598 if (message.event.node.idletime != null) { node.idletime = message.event.node.idletime; }
4599
4600 // Check if this device has changed device group
4601 if (message.event.node.meshid != node.meshid) {
4602 if ((meshes[message.event.node.meshid] == null) && ((userinfo.links == null) || (userinfo.links[node._id] == null))) {
4603 // We don't see the new mesh, remove this device
4604 // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
4605 if ((currentNode != null) && (currentNode._id == node._id)) {
4606 if ((xxcurrentView >= 10) && (xxcurrentView < 20) && !IsNodeViewable(currentNode)) { currentNode = null; setDialogMode(0); go(1); }
4607 }
4608 var index = -1;
4609 for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
4610 nodes.splice(index, 1);
4611
4612 mainUpdate(4 | 16);
4613 } else {
4614 // We see the new mesh, move this device
4615 node.meshid = message.event.node.meshid;
4616 node.meshnamel = meshes[message.event.node.meshid] ? meshes[message.event.node.meshid].name.toLowerCase() : '*';
4617 mainUpdate(1 | 2 | 4);
4618 }
4619 }
4620
4621 // Web page update
4622 updateDeviceViewDevice(node);
4623 mainUpdate(2 | 8 | 16);
4624 refreshDevice(node._id);
4625
4626 // If we are looking at the device group for this device, update that.
4627 if ((xxcurrentView == 20) && (currentMesh != null) && (currentMesh._id == node.meshid)) { mainUpdate(4096); }
4628
4629 if ((currentNode == node) && (xxdialogMode != null) && (xxdialogTag == '@xxmap')) { p10showNodeLocationDialog(); }
4630 }
4631 break;
4632 }
4633 case 'nodemeshchange': {
4634 var index = -1;
4635 for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
4636 if (index != -1) {
4637 var node = nodes[index];
4638 if ((meshes[message.event.newMeshId] == null) && ((userinfo.links == null) || (userinfo.links[node._id] == null))) {
4639 // We don't see the new mesh, remove this device
4640
4641 // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
4642 if ((currentNode != null) && (currentNode._id == node._id)) {
4643 if ((xxcurrentView >= 10) && (xxcurrentView < 20) && !IsNodeViewable(currentNode)) { currentNode = null; setDialogMode(0); go(1); }
4644 }
4645 nodes.splice(index, 1);
4646 mainUpdate(4 | 16);
4647 } else {
4648 // We see the new mesh, move this device
4649 node.meshid = message.event.newMeshId;
4650 node.meshnamel = meshes[message.event.newMeshId] ? meshes[message.event.newMeshId].name.toLowerCase() : '*';
4651 mainUpdate(1 | 2 | 4);
4652 }
4653 refreshDevice(message.event.nodeid);
4654 } else {
4655 // This is a new device, add it.
4656 var node = message.event.node;
4657 if (!meshes[node.meshid]) break; // This is a node for a mesh we don't know. Happens when we are site administrator, we get all messages.
4658 node.namel = node.name.toLowerCase();
4659 if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
4660 node.meshnamel = meshes[node.meshid] ? meshes[node.meshid].name.toLowerCase() : '*';
4661 node.state = 0;
4662 if (!node.icon) node.icon = 1;
4663 node.ident = ++nodeShortIdent;
4664 if (nodes == null) { }
4665 nodes.push(node);
4666
4667 // Web page update
4668 mainUpdate(1 | 2 | 4 | 16);
4669 }
4670 break;
4671 }
4672 case 'nodeconnect': {
4673 // Indicated a node has changed connectivity state
4674 var index = -1;
4675 for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
4676 if (index != -1) {
4677 var node = nodes[index];
4678
4679 // Event the connection change if needed
4680 var n = getstore('notifications', 0); // Account notification settings
4681
4682 // Per-group notification settings
4683 if (message.event.meshid && userinfo.links && userinfo.links[message.event.meshid] && userinfo.links[message.event.meshid].notify) {
4684 n |= userinfo.links[message.event.meshid].notify;
4685 }
4686
4687 // Per-user notification settings
4688 if (userinfo.notify != null) {
4689 if (userinfo.notify[message.event.meshid] != null) { n |= userinfo.notify[message.event.meshid]; }
4690 if (userinfo.notify[message.event.nodeid] != null) { n |= userinfo.notify[message.event.nodeid]; }
4691 }
4692
4693 // Show the notification
4694 if (n & 2) {
4695 var agentPrivilages = "Agent connected";
4696 if ((node.agent != null) && (node.agent.root === false)) { agentPrivilages = "Agent connected with limited privilages"; }
4697 var abc = { title: node.name, icon: node.icon, nodeid: node._id, id: message.event.id };
4698 if (((node.conn & 1) == 0) && ((message.event.conn & 1) != 0)) { abc.text = agentPrivilages; addNotification(abc); }
4699 if (((node.conn & 2) == 0) && ((message.event.conn & 2) != 0)) { abc.text = "Intel AMT CIRA connected"; addNotification(abc); }
4700 if (((node.conn & 4) == 0) && ((message.event.conn & 4) != 0)) { abc.text = "Intel AMT connected"; addNotification(abc); }
4701 if (((node.conn & 16) == 0) && ((message.event.conn & 16) != 0)) { abc.text = "MQTT connected"; addNotification(abc); }
4702 }
4703 if (n & 4) {
4704 var abc = { title: node.name, icon: node.icon, nodeid: node._id, id: message.event.id };
4705 if (((node.conn & 1) != 0) && ((message.event.conn & 1) == 0)) { abc.text = "Agent disconnected"; addNotification(abc); }
4706 if (((node.conn & 2) != 0) && ((message.event.conn & 2) == 0)) { abc.text = "Intel AMT CIRA disconnected"; addNotification(abc); }
4707 if (((node.conn & 4) != 0) && ((message.event.conn & 4) == 0)) { abc.text = "Intel AMT disconnected"; addNotification(abc); }
4708 if (((node.conn & 16) != 0) && ((message.event.conn & 16) == 0)) { abc.text = "MQTT disconnected"; addNotification(abc); }
4709 }
4710
4711 // Change the node connection state
4712 node.conn = message.event.conn;
4713 node.pwr = message.event.pwr;
4714 node.lastconnect = Date.now();
4715
4716 // Clear sesssion and battery information if needed
4717 if ((node.conn & 1) == 0) { delete node.sessions; }
4718
4719 // Web page update
4720 var filter = Q('DevFilterSelect').value;
4721 if ((filter == 1) || (filter == 5)) {
4722 // We are looking at online or offline devices. This may change that state.
4723 mainUpdate(1 | 4 | 16);
4724 } else {
4725 // We are looking at a different filter, just update that specific device.
4726 updateDeviceViewDevice(node);
4727 mainUpdate(1 | 16);
4728 }
4729 refreshDevice(node._id);
4730
4731 // If we are looking at the device group for this device, update that.
4732 if ((xxcurrentView == 20) && (currentMesh != null) && (currentMesh._id == node.meshid)) { mainUpdate(4096); }
4733 }
4734 break;
4735 }
4736 case 'wssessioncount': {
4737 // Update the active web socket session count for a user
4738 if (wssessions != null) {
4739 if (message.event.count == 0 && wssessions[message.event.userid]) {
4740 delete wssessions[message.event.userid];
4741 } else {
4742 wssessions[message.event.userid] = message.event.count;
4743 }
4744 mainUpdate(16384);
4745 }
4746 break;
4747 }
4748 case 'login': {
4749 // Update the last login time
4750 if (users != null && users[message.event.userid]) {
4751 users[message.event.userid].login = Math.floor(new Date(message.event.time).getTime() / 1000);
4752 }
4753 break;
4754 }
4755 case 'scanamtdevice': {
4756 // Populate the Intel AMT scan dialog box with the result of the RMCP scan
4757 if ((xxdialogMode == null) || (!Q('dp1range')) || (xxdialogTag != ('AMTSCAN:' + message.event.range))) return;
4758 var x = '';
4759 if (message.event.results == null) {
4760 // The scan could not occur because of an error. Likely the user range was invalid.
4761 x = '<div style=width:100%;text-align:center;margin-top:12px>' + "Unable to scan this address range." + '</div><div style=width:100%;text-align:center;margin-top:12px;color:gray;line-height:1.5>' + "Sample IP range values" + '<br />192.168.0.100<br />192.168.1.0/24<br />192.167.0.1-192.168.0.100</div>';
4762 } else {
4763 // Go thru all the results and populate the dialog box
4764 amtScanResults = message.event.results;
4765 for (var i in message.event.results) {
4766 var r = message.event.results[i], shortname = r.hostname;
4767 if (r.hosttype == 'host') { shortname = capitalizeFirstLetter(shortname.split('.')[0]); }
4768 if (shortname.length > 20) { shortname = shortname.substring(0, 20) + '...'; }
4769 var str = '<b title="' + EscapeHtml(r.hostname) + '">' + EscapeHtml(shortname) + '</b> - v' + r.ver;
4770 if (r.state == 2) { if (r.tls == 1) { str += " with TLS."; } else { str += " without TLS."; } } else { str += ' not activated.'; }
4771 x += '<div style=width:100%;margin-bottom:2px;background-color:lightgray><div style=padding:4px><div style=display:inline-block;margin-right:5px><input class="DevScanCheckbox form-check-input me-2" name=dp1checkbox tag="' + EscapeHtml(i) + '" type=checkbox onclick=addAmtScanToMeshCheckbox() /></div><div class=j1 style=display:inline-block></div><div style=display:inline-block;margin-left:5px;overflow-x:auto;white-space:nowrap>' + str + '</div></div></div>';
4772 }
4773 // If no results where found, display a nice message
4774 if (x == '') { x = '<div style=width:100%;text-align:center;margin-top:12px>' + "Scan returned no results." + '</div><div style=width:100%;text-align:center;margin-top:12px;color:gray;line-height:1.5>' + "Sample IP range values" + '<br />192.168.0.100<br />192.168.1.0/24<br />192.167.0.1-192.168.0.100</div>'; }
4775 }
4776 // Set the html in the dialog box and re-enable the scan button
4777 QH('dp1results', x);
4778 QE('dp1range', true);
4779 QE('dp1rangebutton', true);
4780 QE('d2scanSelectButton', true);
4781 break;
4782 }
4783 case 'notify': {
4784 var n = { text: message.event.value, title: message.event.title, icon: message.event.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
4785 if (message.id != null) { n.id = message.id; }
4786 if (message.event.tag != null) { n.tag = message.event.tag; }
4787 if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
4788 addNotification(n);
4789 break;
4790 }
4791 case 'traceinfo': {
4792 if (typeof message.event.traceSources == 'object') {
4793 if ((message.event.traceSources != null) && (message.event.traceSources.length > 0)) {
4794 serverTraceSources = message.event.traceSources;
4795 QH('p41traceStatus', EscapeHtml(message.event.traceSources.join(', ')));
4796 } else {
4797 serverTraceSources = [];
4798 QH('p41traceStatus', "None");
4799 }
4800 }
4801 break;
4802 }
4803 case 'sysinfohash': {
4804 // If the sysinfo document has changed and we are looking at it, request an update.
4805 if ((currentNode != null) && (message.event.nodeid == powerTimelineReq)) {
4806 meshserver.send({ action: 'getsysinfo', nodeid: message.event.nodeid });
4807 }
4808 break;
4809 }
4810 case 'ifchange': {
4811 // Network interface changed for a device, if we are currently viewing this device, ask for an update.
4812 if ((currentNode != null) && (currentNode._id == message.event.nodeid)) { meshserver.send({ action: 'getnetworkinfo', nodeid: currentNode._id }); }
4813 break;
4814 }
4815 case 'devicesessions': {
4816 // List of sessions for a given device
4817 var node = getNodeFromId(message.event.nodeid);
4818 if (node == null) break; // Unknown node
4819 node.sessions = message.event.sessions;
4820 if (node.sessions != null) {
4821 for (var i in node.sessions) { if (Object.keys(node.sessions[i]).length == 0) { delete node.sessions[i]; } }
4822 if (Object.keys(node.sessions).length == 0) { delete node.sessions; }
4823 }
4824 updateDeviceViewDevice(node);
4825 if ((currentNode != null) && (currentNode._id == message.event.nodeid)) { gotoDevice(currentNode._id, xxcurrentView, true); }
4826
4827 // If we are looking at the sessions dialog box for this device now, update it
4828 if (xxdialogTag == ('SESSIONS-' + message.event.nodeid)) { showDeviceSessions(message.event.nodeid, true); }
4829 if (xxdialogTag == ('MESSAGES-' + message.event.nodeid)) { showDeviceMessages(message.event.nodeid, true); }
4830 if (xxdialogTag == ('HELPREQ-' + message.event.nodeid)) { showDeviceHelpRequests(message.event.nodeid, true); }
4831
4832 // If we are filtering on sessions or help, update the visible devices
4833 if ((Q('DevFilterSelect').value == 2) || (Q('DevFilterSelect').value == 6)) { mainUpdate(1); }
4834 break;
4835 }
4836 case 'loginTokenChanged': { // Login tokens have changed
4837 if (message.event.userid != userinfo._id) return;
4838 loginTokens = message.event.loginTokens;
4839 mainUpdate(65536);
4840 break;
4841 }
4842 case 'loginTokenAdded': { // A login token was added
4843 if (message.event.userid != userinfo._id) return;
4844 if (loginTokens == null) { loginTokens = []; }
4845 loginTokens.push(message.event.newToken);
4846 mainUpdate(65536);
4847 break;
4848 }
4849 case 'stopped': { // Server is stopping.
4850 // Disconnect
4851 //console.log(message.msg);
4852 break;
4853 }
4854 case 'updatePluginList': {
4855 installedPluginList = message.event.list;
4856 updatePluginList();
4857 break;
4858 }
4859 case 'pluginStateChange': {
4860 if (pluginHandler == null) break;
4861 pluginHandler.refreshPluginHandler();
4862 break;
4863 }
4864 case 'plugin': {
4865 if (pluginHandler == null) break;
4866 try { pluginHandler[message.event.plugin][message.event.pluginaction](message); } catch (e) { console.log("PluginHandler could not event message: ", e); }
4867 break;
4868 }
4869 default:
4870 //console.log('Unknown message.event.action', message.event.action);
4871 break;
4872 }
4873 break;
4874 }
4875 case 'pluginPermissions': {
4876 handlePluginPermissions(message);
4877 break;
4878 }
4879 case 'pluginPermissionsSet': {
4880 if (message.success) {
4881 alert("Permissions saved successfully");
4882 } else {
4883 alert("Error saving permissions: " + message.error);
4884 }
4885 break;
4886 }
4887 case 'pluginPermissionList': {
4888 handlePluginPermissionList(message);
4889 break;
4890 }
4891 case 'createInviteLink': { // Agent installation invitation link
4892 if (xxdialogTag != message.meshid) break;
4893 var servername = serverinfo.name;
4894 if ((servername.indexOf('.') == -1) || ((features & 2) != 0)) { servername = window.location.hostname; } // If the server name is not set or it's in LAN-only mode, use the URL hostname as server name.
4895 var domainUrlNoSlash = domainUrl.substring(0, domainUrl.length - 1);
4896 var url;
4897 if (serverinfo.https == true) {
4898 var portStr = (serverinfo.port == 443) ? '' : (':' + serverinfo.port);
4899 url = 'https://' + servername + portStr + domainUrl + 'agentinvite?c=' + message.cookie + (urlargs.key ? ('&key=' + urlargs.key) : '');
4900 } else {
4901 var portStr = (serverinfo.port == 80) ? '' : (':' + serverinfo.port);
4902 url = 'http://' + servername + portStr + domainUrl + 'agentinvite?c=' + message.cookie + (urlargs.key ? ('&key=' + urlargs.key) : '');
4903 }
4904 Q('agentInvitationLink').href = url;
4905 var t = format((message.expire == 1) ? "1 hour" : "{0} hours", message.expire);
4906 if (message.expire == 24) { t = "1 day"; }
4907 if (message.expire == 168) { t = "1 week"; }
4908 if (message.expire == 5040) { t = "1 month"; }
4909 if (message.expire == 0) { t = "Unlimited"; }
4910 QH('agentInvitationLink', format("Invitation Link ({0})", t));
4911 QV('agentInvitationLinkDiv', true);
4912 break;
4913 }
4914 case 'createDeviceShareLink': { // Guest sharing link
4915 var node = getNodeFromId(message.nodeid), x = '';
4916 if (node == null) break;
4917 x += addHtmlValue("Device", EscapeHtml(node.name));
4918 x += addHtmlValue("Guest Name", message.guestname);
4919 x += addHtmlValue("User Input", message.viewOnly ? "Not allowed, view only" : "Allowed");
4920 if (message.start && message.expire) {
4921 if (message.recurring) {
4922 x += addHtmlValue("Start Time", printDateTime(new Date(message.start)));
4923 if (message.expire < 2) { x += addHtmlValue("Duration", format("{0} minute", message.expire)); }
4924 else { x += addHtmlValue("Duration", format("{0} minutes", message.expire)); }
4925 } else {
4926 x += addHtmlValue("Start Time", printDateTime(new Date(message.start)));
4927 x += addHtmlValue("Expire Time", printDateTime(new Date(message.expire)));
4928 }
4929 }
4930 if (message.recurring == 1) { x += addHtmlValue("Recurring", "Daily"); }
4931 if (message.recurring == 2) { x += addHtmlValue("Recurring", "Weekly"); }
4932 var y = [];
4933 if (message.consent & 0x0007) { y.push("Notify"); }
4934 if (message.consent & 0x0038) { y.push("Prompt"); }
4935 if (message.consent & 0x0040) { y.push("Privacy bar"); }
4936 if (y.length == 0) { y.push("None"); }
4937 x += addHtmlValue("User Consent", y.join(', '));
4938 var type = ''; if (message.p <= 7) { type = ['', "Remote Terminal Link", "Remote Desktop Link", "Remote Desktop + Terminal Link", "Remote Files Link", "Remote Terminal + Files Link", "Remote Desktop + Files Link", "Remote Desktop + Terminal + Files Link"][message.p]; } else if (message.p == 8) { type = format("HTTP/{0} link", message.port); } else if (message.p == 16) { type = format("HTTPS/{0}", message.port); }
4939 x += '<div id=agentInvitationLinkDiv style="text-align:center;font-size:large;margin:16px"><a href="' + message.url + '" id=agentInvitationLink rel="noreferrer noopener" target="_blank" style=cursor:pointer>' + type + '</a> <i class="fa-regular fa-copy" title="' + "Copy link to clipboard" + '" style=cursor:pointer onclick=d2CopyInviteToClip()></i></div></div>';
4940 setModalContent('xxAddAgent', "Share Device", x);
4941 showModal('xxAddAgentModal', 'idx_dlgOkButton');
4942 break;
4943 }
4944 case 'getmqttlogin': {
4945 if ((currentNode == null) || (currentNode._id != message.nodeid) || (xxdialogMode != null)) return;
4946 var x = "These settings can be used to connect MQTT for this device." + '<br /><br />';
4947 delete message.action;
4948 delete message.nodeid;
4949 x += '<textarea readonly=readonly style=width:100%;resize:none;height:100px;overflow:auto;font-size:12px readonly>' + JSON.stringify(message) + '</textarea>';
4950 /*
4951 x += addHtmlValue('Username', '<input style=width:230px readonly value="' + message.user + '" />');
4952 x += addHtmlValue('Password', '<input style=width:230px readonly value="' + message.pass + '" />');
4953 x += addHtmlValue('WS URL', '<input style=width:230px readonly value="' + message.wsUrl + '" />');
4954 if (message.mpsUrl && message.mpsCertHash) {
4955 x += addHtmlValue('MPS URL', '<input style=width:230px readonly value="' + message.mpsUrl + '" />');
4956 x += addHtmlValue('MPS Cert Hash', '<input style=width:230px readonly value="' + message.mpsCertHash + '" />');
4957 }
4958 */
4959 setModalContent('xxAddAgent', "MQTT Credentials", x);
4960 showModal('xxAddAgentModal', 'idx_dlgOkButton');
4961 break;
4962 }
4963 case 'stopped': { // Server is stopping.
4964 // Disconnect
4965 autoReconnect = false;
4966 QH('p0span', message.msg);
4967 break;
4968 }
4969 case 'updatePluginList': {
4970 installedPluginList = message.list;
4971 updatePluginList();
4972 break;
4973 }
4974 case 'pluginVersionsAvailable': {
4975 if (pluginHandler == null) break;
4976 updatePluginList(message.list);
4977 break;
4978 }
4979 case 'downgradePluginVersions': {
4980 var vSelect = '<select id="lastPluginVersion">';
4981 message.info.versionList.forEach(function (v) { vSelect += '<option value="' + v.zipball_url + '">' + v.name + '</option>'; });
4982 vSelect += '</select>';
4983 setModalContent('xxAddAgent', "Plugin Action", format('Select the version to downgrade the plugin: {0}', message.info.name) + '<hr />' + vSelect + '<hr />' + "Please be aware that downgrading is not recommended. Please only do so in the event that a recent upgrade has broken something." + + '<input id="lastPluginAct" type="hidden" value="downgrade" /><input id="lastPluginId" type="hidden" value="' + message.info.id + '" />');
4984 showModal('xxAddAgentModal', 'idx_dlgOkButton', pluginActionEx);
4985 break;
4986 }
4987 case 'serverBackup': {
4988 if (message.service == 'googleDrive') {
4989 QV('p2ServerActionsGoogleBackup', message.state > 0);
4990 QV('p2ServerActionsGoogleBackupCheck', message.state > 2);
4991 miscState['googleDrive'] = { state: message.state }
4992 if (message.state == 2) {
4993 var x = '<img style=float:right src="images/googledrive-48.png" /><div>' + "Nagivate to the URL below, grant access and copy the token code back." + '</div><br />';
4994 x += addHtmlValue("Activation", '<a href=\"' + message.url + '\" rel="noreferrer noopener" target="_blank">Browse to this URL</a>');
4995 x += addHtmlFormFloating("Code", '<input id=gdcode onkeyup=server_setupGoogleDriveBackupCheck3() class=form-control></input>');
4996 setModalContent('xxAddAgent', "Google Drive Backup", x);
4997 showModal('xxAddAgentModal', 'idx_dlgOkButton', () => server_setupGoogleDriveBackupEx(3, 'gd2'));
4998 Q('gdcode').focus();
4999 QE('idx_dlgOkButton', false);
5000 }
Showing first 5,000 of 23,671 lines. View raw