master
handlebars 2,371 lines 143 KB
Raw
1 <!DOCTYPE html>
2 <html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
3 <head>
4 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
5 <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
6 <meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />
7 <meta name="apple-mobile-web-app-capable" content="yes" />
8 <meta name="format-detection" content="telephone=no" />
9 <meta name="robots" content="noindex,nofollow">
10 <link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11 <link type="text/css" href="styles/style-sharing.css" media="screen" rel="stylesheet" title="CSS" />
12 <link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
13 {{{customCSSTags}}}
14 <link rel="apple-touch-icon" href="/favicon-303x303.png" />
15 <script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
16 <script type="text/javascript" src="scripts/amt-redir-ws-0.1.0{{{min}}}.js"></script>
17 <script type="text/javascript" src="scripts/amt-wsman-ws-0.2.0{{{min}}}.js"></script>
18 <script type="text/javascript" src="scripts/agent-redir-ws-0.1.1{{{min}}}.js"></script>
19 <script type="text/javascript" src="scripts/agent-redir-rtc-0.1.0{{{min}}}.js"></script>
20 <script type="text/javascript" src="scripts/agent-desktop-0.0.2{{min}}.js"></script>
21 <script type="text/javascript" src="scripts/amt-desktop-0.0.2{{min}}.js"></script>
22 <script type="text/javascript" src="scripts/amt-terminal-0.0.2{{min}}.js"></script>
23 <script type="text/javascript" src="scripts/zlib{{min}}.js"></script>
24 <script type="text/javascript" src="scripts/zlib-inflate{{min}}.js"></script>
25 <script type="text/javascript" src="scripts/zlib-adler32{{min}}.js"></script>
26 <script type="text/javascript" src="scripts/zlib-crc32{{min}}.js"></script>
27 <script type="text/javascript" src="scripts/amt-terminal-0.0.2{{min}}.js"></script>
28 <script type="text/javascript" src="scripts/xterm{{{min}}}.js"></script>
29 <script type="text/javascript" src="scripts/xterm-addon-fit{{{min}}}.js"></script>
30 <script type="text/javascript" src="scripts/xterm-addon-image{{{min}}}.js"></script>
31 <script keeplink=1 type="text/javascript" src="scripts/filesaver.min.js"></script>
32 {{{customJSTags}}}
33 <title>{{{title}}}</title>
34 </head>
35 <body style="overflow:hidden;background-color:black">
36 <div id=LeftSideToolBar style="display:none;position:absolute;left:0;bottom:0;width:52px;top:0;background:#113962;background:linear-gradient(to bottom, #104893 0%,#113962 100%);color:white;border-right: 5px solid #BBB;">
37 <div id=LeftMenuDesktop class="slbbutton slbbuttonsel2" title="Desktop" onclick=go(11)>
38 <div class="slb1" style="position:absolute;top:6px;left:6px"></div>
39 </div>
40 <div id=LeftMenuTerminal class="slbbutton" title="Terminal" onclick=go(12)>
41 <div class="slb2" style="position:absolute;top:6px;left:6px"></div>
42 </div>
43 <div id=LeftMenuFiles class="slbbutton" title="Files" onclick=go(13)>
44 <div class="slb3" style="position:absolute;top:6px;left:6px"></div>
45 </div>
46 </div>
47 <div id=p11 class="noselect" style="overflow:hidden;position:absolute;left:54px;top:0;right:0;bottom:0;display:none">
48 <div id=deskarea0>
49 <div id=deskarea1 class="areaHead" style="line-height:24px">
50 <div class="toright2">
51 <span id="p11power"></span>&nbsp;
52 <div class='deskareaicon' title="Toggle View Mode" onclick="toggleAspectRatio(1)">&#8690;</div>
53 <div class='deskareaicon' title="Rotate Left" onclick="drotate(-1)">&olarr;</div>
54 <div class='deskareaicon' title="Rotate Right" onclick="drotate(1)">&orarr;</div>
55 <input id="deskActionsSettings" type="button" value="Settings..." title="Edit remote desktop settings" onkeypress="return false" onkeydown="return false" onclick="showDesktopSettings()" class="mR" />
56 <div id="desktopCustomUiButtons" style="float:left"></div>
57 <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>
58 </div>
59 <div>
60 <div id="idx_deskFullBtn2" onclick=deskToggleFull(event)>&nbsp;&#x2716;</div>
61 <input type="button" id="autoconnectbutton1" value="AutoConnect" onclick=autoConnectDesktop(event) onkeypress="return false" onkeydown="return false" style="display:none" />
62 <span id=connectbutton1span><input type=button id=connectbutton1 cmenu="deskConnectButton" value="Connect" onclick=connectDesktop(event,3) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
63 <span id=connectbutton1hspan>&nbsp;<input type=button id=connectbutton1h value="HW Connect" title="Connect using Intel AMT hardware KVM" onclick=connectDesktop(event,2) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
64 <span id=disconnectbutton1span>&nbsp;<input type=button id=disconnectbutton1 value="Disconnect" onclick=connectDesktop(event,0) onkeypress="return false" onkeydown="return false" /></span>
65 &nbsp;<span id="deskstatus">Disconnected</span><span id="deskmetadata"></span>
66 </div>
67 </div>
68 <div id=deskarea3x style="max-height:calc(100vh - 52px);height:calc(100vh - 56px);">
69 <div id=DeskFocus oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event)></div>
70 <div id=DeskParent>
71 <canvas id=Desk width=640 height=480 oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event) onmousewheel=dmousewheel(event)></canvas>
72 </div>
73 <div id=p11DeskConsoleMsg 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" onclick=clearConsoleMsg()></div>
74 </div>
75 <div id=deskarea4 class="areaFoot" style="min-height:24px">
76 <div class="toright2">
77 <span id="DeskLatency" style="line-height:22px;width:50px" title="Desktop Session Latency"></span>
78 <span id="DeskTimer" style="line-height:22px" title="Session time"></span>&nbsp;
79 <select id=termdisplays style="display:none" onchange=deskSetDisplay(event) onkeypress="return false" onkeydown="return false"></select>&nbsp;
80 <span id=DeskSaveImageButton title="Save a screenshot of the remote desktop"><img src='images/icon-camera.png' onclick=deskSaveImage() height=16 width=16 style=padding-top:2px /></span>
81 </div>
82 <div style="height:22px">
83 <select id="deskkeys">
84 <option value=10>Ctrl+Alt+Del</option>
85 <option value=5>Win</option>
86 <option value=0>Win+Down</option>
87 <option value=1>Win+Up</option>
88 <option value=2>Win+L</option>
89 <option value=3>Win+M</option>
90 <option value=4>Shift+Win+M</option>
91 <option value=6>Win+R</option>
92 <option value=7>Alt-F4</option>
93 <option value=8>Ctrl-W</option>
94 <option value=9>Alt-Tab</option>
95 <option value=11>Win+Left</option>
96 <option value=12>Win+Right</option>
97 </select>
98 <input id="DeskWD" type=button value="Send" onkeypress="return false" onkeydown="return false" onclick="deskSendKeys()" />
99 <input id="DeskClip" style="" type="button" value="Clipboard" onkeypress="return false" onkeydown="return false" onclick="showDeskClip()" />
100 <input id="DeskType" style="" type="button" value="Type" onkeypress="return false" onkeydown="return false" onclick="showDeskType()" />
101 <label><span id="DeskControlSpan" title="Toggle mouse and keyboard input"><input id="DeskControl" type="checkbox" onkeypress="return false" onkeydown="return false" onclick="toggleKvmControl()" />Input</span></label>&nbsp;
102 </div>
103 </div>
104 </div>
105 </div>
106 <div id=p12 class="noselect" style="overflow:hidden;position:absolute;left:54px;top:0;right:0;bottom:0;display:none;background-color:black">
107 <div id="p12warning" onclick=showFeaturesDlg()>
108 <div class="icon2"></div>
109 <div class="warningbox">Intel&reg; AMT Redirection port or KVM feature is disabled<span id="p12warninga">, click here to enable it.</span></div>
110 </div>
111 <div id="p12warning2" onclick=showPowerActionDlg()>
112 <div class="icon2"></div>
113 <div class="warningbox">Remote computer is not powered on, click here to issue a power command.</div>
114 </div>
115 <div class="areaHead" style="position:absolute;top:0;left:0;right:0;height:24px">
116 <div class="toright2">
117 <div id="termRecordIcon" class='deskareaicon' title="Server is recording this session" style="display:none;background-color:red;width:12px;height:12px;border-radius:6px;margin-top:5px;margin-left:5px"></div>
118 <div id="p12power" style="margin-top:3px;margin-right:4px;float:right"></div>
119 </div>
120 <div>
121 <span id="connectbutton2span"><input type="button" id="connectbutton2" cmenu="termConnectButton" value="Connect" onclick=connectTerminal(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
122 <span id="disconnectbutton2span">&nbsp;<input type="button" id="disconnectbutton2" value="Disconnect" onclick=connectTerminal(event,0) onkeypress="return false" onkeydown="return false" /></span>
123 &nbsp;<span id="termstatus">Disconnected</span><span id="termtitle"></span>
124 </div>
125 </div>
126 <div id="termarea3xdiv" style="position:absolute;top:28px;bottom:28px;left:0;right:0"></div>
127 <div class="areaFoot" style="position:absolute;bottom:0;left:0;right:0;height:24px">
128 <div class="toright2">
129 <span id="TermLatency" title="Terminal Session Latency"></span>
130 <span id="TermTimer" title="Session time"></span>&nbsp;
131 <span id="terminalSettingsButtons" style="display:none">
132 <input id="id_tcrbutton" type="button" onkeypress="return false" onkeydown="return false" class="bottombutton" value="CR+LF" title="Toggle what the return key will send" onclick="termToggleCr()" />
133 <input id="id_tfxkeysbutton" type="button" onkeypress="return false" onkeydown="return false" class="bottombutton" value="Intel (F10 = ESC+[OM)" title="Toggle F1 to F10 keys emulation type" onclick="termToggleFx()" />
134 <input id="id_ttypebutton" type="button" onkeypress="return false" onkeydown="return false" class="bottombutton" value="Extended Ascii" title="Toggle terminal emulation type" onclick="termToggleType()" />
135 </span>
136 <span id="terminalSizeDropDown" style="display:none">
137 <select id="termSizeList" onkeypress="return false"><option value="1">80x25</option><option value="2">100x30</option></select>
138 </span>
139 <span id="specialKeyDropDown">
140 <select id="specialkeylist" onkeypress="return false"></select>
141 <input id="specialkeylistinput" type="button" onkeypress="return false" class="bottombutton" value="Send" title="Send the selected special key" onclick="sendSpecialKey()" />
142 </span>
143 </div>
144 <div>
145 &nbsp;
146 <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="ctrlcbutton" value="Ctl-C" onclick="termSendKey(3,'ctrlcbutton')" />
147 <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="ctrlxbutton" value="Ctl-X" onclick="termSendKey(24,'ctrlxbutton')" />
148 <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="escbutton" value="ESC" onclick="termSendKey(27,'escbutton')" />
149 <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="bsbutton" value="Backspace" onclick="termSendKey(8,'bsbutton')" style="display:none" />
150 <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="pastebutton" value="Paste" title="Paste text into the terminal" onclick="showTermPasteDialog()" style="display:none" />
151 </div>
152 </div>
153 <div id=p12TermConsoleMsg 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" onclick=p12clearConsoleMsg()></div>
154 </div>
155 <div id=p13 class="noselect" style="overflow:hidden;position:absolute;left:54px;top:0;right:0;bottom:0;display:none;background-color:white">
156 <div id="p13toolbar" style="position:absolute;left:0;top:0;right:0;bottom:28px">
157 <div class="areaHead">
158 <div class="toright2">
159 <div id="filesRecordIcon" class='deskareaicon' title="Server is recording this session" style="display:none;background-color:red;width:12px;height:12px;border-radius:6px;margin-top:5px;margin-left:5px"></div>
160 <div id="p13power" style="margin-top:3px;margin-right:4px;float:right"></div>
161 </div>
162 <div>
163 <input id=p13Connect value="Connect" onclick=connectFiles(event) type="button" />
164 &nbsp;<span id=p13Status>Disconnected</span>
165 </div>
166 </div>
167 <div id="fileArea2" class="areaHead2" valign=bottom>
168 <div id="p13rightOfButtons" class="toright2"></div>
169 <div>
170 <input type=button id=p13FolderUp disabled="disabled" onclick="p13folderup()" value="Up" />&nbsp;
171 <input type=button id=p13SelectAllButton disabled="disabled" onclick="p13selectallfile()" value="Select All" />&nbsp;
172 <input type=button id=p13RenameFileButton disabled="disabled" value="Rename" onclick="p13renamefile()" />&nbsp;
173 <input type=button id=p13DeleteFileButton disabled="disabled" value="Delete" onclick="p13deletefile()" />&nbsp;
174 <input type=button id=p13ViewFileButton disabled="disabled" value="Edit" onclick="p13viewfile()" />&nbsp;
175 <input type=button id=p13NewFolderButton disabled="disabled" value="New Folder" onclick="p13createfolder()" />&nbsp;
176 <input type=button id=p13UploadButton disabled="disabled" value="Upload" onclick="p13uploadFile()" />&nbsp;
177 <input type=button id=p13CutButton disabled="disabled" value="Cut" onclick="p13copyFile(1)" />&nbsp;
178 <input type=button id=p13CopyButton disabled="disabled" value="Copy" onclick="p13copyFile(0)" />&nbsp;
179 <input type=button id=p13PasteButton disabled="disabled" value="Paste" onclick="p13pasteFile()" />&nbsp;
180 <input type=button id=p13ZipButton disabled="disabled" value="Zip" onclick="p13zipFiles()" />&nbsp;
181 <input type=button id=p13RefreshButton disabled="disabled" value="Refresh" onclick="p13folderup(9999)" />&nbsp;
182 <input type=button id=p13FindButton disabled="disabled" value="Find" onclick="p13findfile()" />&nbsp;
183 </div>
184 </div>
185 <div class="areaHead3" style="line-height:28px">
186 <div class="toright2">
187 <select id=p13sortdropdown onchange=p13updateFiles()>
188 <option value=1 selected="selected">Sort by name</option>
189 <option value=2>Sort by size</option>
190 <option value=3>Sort by date</option>
191 <option value=4>Descend by name</option>
192 <option value=5>Descend by size</option>
193 <option value=6>Descend by date</option>
194 </select>
195 </div>
196 <div>&nbsp;&nbsp;<span id="p13currentpath"></span></div>
197 </div>
198 <div id="fileArea4" style="height:calc(100vh - 146px)">
199 <div id=p13FilesConsoleMsg 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" onclick=p13clearConsoleMsg()></div>
200 <div id="p13filetable" style="width:100%;height:100%">
201 <div id="p13bigok" style="display:none"><b>&checkmark;</b></div>
202 <div id="p13bigfail" style="display:none"><b>&#10007;</b></div>
203 <span id="p13files"></span>
204 </div>
205 </div>
206 </div>
207 <div class="areaFoot" style="position:absolute;left:0;right:0;bottom:0;height:28px">
208 &nbsp;<span id="p13bottomstatus"></span>
209 </div>
210 </div>
211 <div id=dialog class="noselect" style="display:none">
212 <div id=dialogHeader>
213 <div tabindex=0 id=id_dialogclose onclick=setDialogMode() onkeypress="if (event.key == 'Enter') setDialogMode()">&#x2716;</div>
214 <div id=id_dialogtitle></div>
215 </div>
216 <div id=dialogBody>
217 <div id=dialog1>
218 <div id=id_dialogMessage style=""></div>
219 </div>
220 <div id=dialog2 style="">
221 <div id=id_dialogOptions></div>
222 </div>
223 <div id=dialog4 style="">
224 <input id="d4WrapButton" type="button" value="Wrap On" onclick="d4ToggleWrap()" />
225 <input id="d4SizeButton" type="button" value="Small" onclick="d4ToggleSize()" />
226 <input id="d4EncodingButton" type="button" value="Raw" onclick="d4ToggleEncoding()" />
227 <input id="d4LineBreakButton" type="button" value="Windows" onclick="d4ToggleLineBreak()" />
228 <textarea id=d4editorarea autocomplete="off" style="height:calc(100vh - 286px);width:100%;overflow:scroll;resize:none;white-space:pre"></textarea>
229 </div>
230 <div id=dialog7 style="">
231 <div id="d7meshkvm">
232 <h4>Agent Remote Desktop</h4>
233 <div>
234 <div>Quality</div>
235 <select id="d7bitmapquality" dir="rtl"></select>
236 </div>
237 <div>
238 <div>Scaling</div>
239 <select id="d7bitmapscaling" style="" dir="rtl">
240 <option selected=selected value=1024>100%</option>
241 <option value=896>87.5%</option>
242 <option value=768>75%</option>
243 <option value=640>62.5%</option>
244 <option value=512>50%</option>
245 <option value=384>37.5%</option>
246 <option value=256>25%</option>
247 <option value=128>12.5%</option>
248 </select>
249 </div>
250 <div>
251 <div>Frame rate</div>
252 <select id="d7framelimiter" dir="rtl">
253 <option selected=selected value=50>Fast</option>
254 <option value=100>Medium</option>
255 <option value=400>Slow</option>
256 <option value=1000>Very slow</option>
257 </select>
258 </div>
259 <div id="d7desktopOtherSettings">
260 <div>Other Settings</div>
261 <div id="d7otherset2" style="display:block">
262 <label style="display:block"><input type="checkbox" id="d7deskSwapMouse" />Swap Mouse Buttons</label>
263 <label style="display:block"><input type="checkbox" id="d7deskRemoteKeyMap" />Use Remote Keyboard Map</label>
264 </div>
265 </div>
266 </div>
267 <div id="d7amtkvm">
268 <h4>Intel&reg; AMT Hardware KVM</h4>
269 <div>
270 <div>Image Encoding</div>
271 <select id="d7desktopmode">
272 <option value="1">RLE8, Fastest</option>
273 <option value="2">RLE16, Recommended</option>
274 <option value="3">RAW8, Slow</option>
275 <option value="4">RAW16, Very Slow</option>
276 </select>
277 </div>
278 <div>
279 <div>Other Settings</div>
280 <div id="d7otherset" style="display:block">
281 <label style="display:block"><input type="checkbox" id="d7showfocus" />Show Focus Tool</label>
282 <label style="display:block"><input type="checkbox" id="d7showcursor" />Show Local Mouse Cursor</label>
283 <label style="display:block"><input type="checkbox" id="d7localKeyMap" />Local Keyboard Map</label>
284 </div>
285 </div>
286 </div>
287 </div>
288 </div>
289 <div id="idx_dlgButtonBar">
290 <input id="idx_dlgCancelButton" type="button" value="Cancel" style="" onclick="dialogclose(0)">
291 <input id="idx_dlgOkButton" type="button" value="OK" style="" onclick="dialogclose(1)">
292 <div><input id="idx_dlgDeleteButton" type="button" value="Delete" style="display:none" onclick="dialogclose(2)"></div>
293 </div>
294 </div>
295 <script>
296 var random = '{{{randomlength}}}' // Random length string for BREACH mitigation
297 var sessionActivity = null;
298 var desktop = null;
299 var agentPresent = true;
300 var intelAmtPresent = false;
301 var deskAspectRatio = 0;
302 var desktopsettings = { encoding: 2, showfocus: false, showmouse: true, showcad: true, quality: 40, scaling: 1024, framerate: 100, localkeymap: false, remotekeymap: false };
303 var serverPublicNamePort = '{{{serverDnsName}}}:{{{serverPublicPort}}}';
304 var domain = '{{{domain}}}';
305 var domainUrl = '{{{domainurl}}}';
306 var authCookie = '{{{authCookie}}}';
307 var viewOnly = parseInt('{{{viewOnly}}}');
308 var urlargs = parseUriArgs();
309 var debugmode = urlargs.debug;
310 var attemptWebRTC = false;
311 var updateSessionTimer = null;
312 var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected", "Intel&reg; AMT Connected"];
313 var nodeName = decodeURIComponent('{{{nodeName}}}');
314 var webPageFullScreen = false;
315 var expire = '{{{expire}}}';
316 if (expire != '') {
317 QH('p11power', printFlexDateTime(new Date(parseInt(expire))));
318 QH('p12power', printFlexDateTime(new Date(parseInt(expire))));
319 QH('p13power', printFlexDateTime(new Date(parseInt(expire))));
320 }
321 var features = parseInt('{{{features}}}');
322 var features2 = parseInt('{{{features2}}}');
323 var features3 = parseInt('{{{features3}}}');
324
325 // Load desktop settings
326 var t = null;
327 try { t = localStorage.getItem('desktopsettings'); } catch (ex) { }
328 if (t != null) { try { desktopsettings = JSON.parse(t); } catch (ex) { } }
329 if (((features2 & 1) == 0) && (desktopsettings.quality > 60)) { desktopsettings.quality = 60; }
330
331 // Terminal
332 var terminal = null;
333 var xterm = null;
334 var xtermfit = null;
335 var xtermimage = null;
336 var xtermResizeTimer = null;
337
338 // Files
339 var files = null
340
341 // Console messages timers
342 var p11DeskConsoleMsgTimer = null;
343 var p12TermConsoleMsgTimer = null;
344 var p13FilesConsoleMsgTimer = null;
345
346 function start() {
347 window.onresize = deskAdjust;
348 document.onkeypress = ondockeypress;
349 document.onkeydown = ondockeydown;
350 document.onkeyup = ondockeyup;
351 setupDesktop();
352 setupTerminal();
353 setupFiles();
354
355 // Set the file editor
356 d4EditWrapVal = Number(getstore('editorWrap', 0));
357 d4EditSizeVal = Number(getstore('editorSize', 0));
358 d4EditEncodingVal = Number(getstore('editorEncoding', 0));
359 d4EditLineBreakVal = Number(getstore('editorLineBreak', 0));
360 d4ToggleWrap(true);
361 d4ToggleSize(true);
362 d4ToggleEncoding(true);
363 d4ToggleLineBreak(true);
364
365 // Set the document title
366 if (nodeName.length > 0) { document.title += ' - ' + nodeName; }
367
368 // View only mode
369 if (viewOnly == 1) {
370 QV('deskkeys', false);
371 QV('DeskWD', false);
372 QV('DeskClip', false);
373 QV('DeskType', false);
374 QV('DeskControlSpan', false);
375 }
376
377 // Setup upload drag & drop
378 Q('p13filetable').addEventListener('drop', p13fileDragDrop, false);
379 Q('p13filetable').addEventListener('dragover', p13fileDragOver, false);
380 Q('p13filetable').addEventListener('dragleave', p13fileDragLeave, false);
381
382 // Setup feature visibility
383 QV('LeftMenuDesktop', features & 2);
384 QV('LeftMenuTerminal', features & 1);
385 QV('LeftMenuFiles', features & 4);
386 if (features & 2) { go(11); } // Goto desktop
387 else if (features & 1) { go(12); } // Goto terminal
388 else if (features & 4) { go(13); } // Goto files
389
390 // Only show left bar if two or more features are visible
391 var featureCount = 0;
392 if (features & 1) { featureCount++; }
393 if (features & 2) { featureCount++; }
394 if (features & 4) { featureCount++; }
395 QV('LeftSideToolBar', featureCount > 1);
396 QS('p11')['left'] = (featureCount > 1) ? '54px' : '0px';
397 QS('p12')['left'] = (featureCount > 1) ? '54px' : '0px';
398 QS('p13')['left'] = (featureCount > 1) ? '54px' : '0px';
399
400 deskAdjust();
401 }
402
403
404 //
405 // Desktop
406 //
407
408 function isInputAllowed() { return (viewOnly != 1) && (Q('DeskControl').checked == true); }
409 function clearConsoleMsg() { QH('p11DeskConsoleMsg', ''); }
410
411 // Toggle the web page to full screen
412 function toggleAspectRatio(toggle) {
413 if (toggle === 1) { deskAspectRatio = ((deskAspectRatio + 1) % 3); }
414 deskAdjust();
415 }
416
417 function deskAdjust() {
418 if ((xxcurrentView == 12) && (terminal != null) && (xtermfit != null)) { xtermfit.fit(); } // Terminal
419 QS('fileArea4')['height'] = 'calc(100vh - ' + (90 + Q('fileArea2').clientHeight) + 'px)'; // Files
420 var parentH = Q('DeskParent').clientHeight, parentW = Q('DeskParent').clientWidth;
421 var deskH = Q('Desk').height, deskW = Q('Desk').width;
422
423 if (deskAspectRatio == 2) {
424 // Scale mode
425 QS('Desk')['margin-top'] = null;
426 QS('Desk').height = '100%';
427 QS('Desk').width = '100%';
428 QS('DeskParent').overflow = 'hidden';
429 } else if (deskAspectRatio == 1) {
430 // Zoomed mode
431 QS('Desk')['margin-top'] = '0px';
432 //QS('Desk')['margin-left'] = '0px';
433 QS('Desk').height = deskH + 'px';
434 QS('Desk').width = deskW + 'px';
435 QS('DeskParent').overflow = 'scroll';
436 } else {
437 // Fixed aspect ratio
438 if ((parentH / parentW) > (deskH / deskW)) {
439 var hNew = ((deskH * parentW) / deskW) + 'px';
440 //if (webPageFullScreen || fullscreen) {
441 //QS('deskarea3x').height = null;
442 //} else {
443 // QS('deskarea3x').height = hNew;
444 //QS('deskarea3x').height = null;
445 //}
446 QS('Desk').height = hNew;
447 QS('Desk').width = '100%';
448 } else {
449 var wNew = ((deskW * parentH) / deskH) + 'px';
450 //if (webPageFullScreen || fullscreen) {
451 //QS('Desk').height = null;
452 //} else {
453 QS('Desk').height = '100%';
454 //}
455 QS('Desk').width = wNew;
456 }
457 QS('Desk')['margin-top'] = null;
458 QS('DeskParent').overflow = 'hidden';
459 }
460 }
461
462 function setupDesktop() {
463 // Setup the remote desktop
464 if (desktop != null) { desktop.Stop(); desktop = null; }
465
466 // If the device desktop is already connected in multi-desktop, use that.
467 if (desktop == null) {
468 // Device is not already connected, just setup a blank canvas
469 QH('DeskParent', '<canvas id=Desk oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event)></canvas>');
470
471 // Setup the mouse wheel
472 Q('Desk').addEventListener('DOMMouseScroll', function (e) { return dmousewheel(e); });
473 Q('Desk').addEventListener('mousewheel', function (e) { return dmousewheel(e); });
474 }
475 updateDesktopButtons();
476 deskAdjust();
477 updateMetadata(desktop, 'deskmetadata');
478 }
479
480 // Show and enable the right buttons
481 function updateDesktopButtons() {
482 var deskState = 0;
483 if (desktop != null) { deskState = desktop.State; }
484
485 // Show the right buttons
486 QV('disconnectbutton1span', (deskState != 0));
487 QV('connectbutton1span', (deskState == 0) && (agentPresent));
488 QV('connectbutton1hspan', (deskState == 0) && (intelAmtPresent));
489
490 // Show the right settings
491 QV('d7meshkvm', agentPresent && ((deskState == 0) || (desktop.contype == 1)));
492 QV('d7amtkvm', intelAmtPresent && ((deskState == 0) || (desktop.contype == 2)));
493
494 // Enable buttons
495 QE('connectbutton1', agentPresent);
496 QE('connectbutton1h', intelAmtPresent);
497 //QV('DeskClip', agentPresent && ((desktop == null) || (desktop.contype != 2))); // Clipboard not supported on macOS
498 QV('DeskClip', false); // Clipboard not supported on this page
499 QE('DeskClip', deskState == 3);
500 QE('DeskType', deskState == 3);
501 QV('DeskWD', viewOnly != 1);
502 QE('DeskWD', deskState == 3);
503 QV('deskkeys', viewOnly != 1);
504 QE('deskkeys', deskState == 3);
505 QV('DeskTimer', deskState == 3);
506 QV('DeskLatency', deskState == 3);
507 QS('DeskLatency').display = (deskState == 3 ? 'inline-block' : 'none');
508
509 // Display this only if we have Chat & Notify permissions
510 QV('DeskSaveImageButton', (deskState == 3) && (Q('Desk')['toBlob'] != null));
511 QV('DeskControlSpan', viewOnly != 1);
512 QV('deskActionsBtn', (browserfullscreen == false));
513 QV('deskActionsSettings', (browserfullscreen == false));
514 Q('DeskControl').checked = true;
515 QS('DeskControlSpan').color = Q('DeskControl').checked ? null : 'red';
516 }
517
518 // Debug
519 var autoConnectDesktopTimer = null;
520 function autoConnectDesktop(e) { if (autoConnectDesktopTimer == null) { autoConnectDesktopTimer = setInterval(function () { connectDesktop(null, 1) }, 1000); } else { clearInterval(autoConnectDesktopTimer); autoConnectDesktopTimer = null; } }
521
522 // Used to translate incoming agent console messages
523 var agentConsoleMessages = ['', "Waiting for user to grant access...", "Denied", "Failed to start remote terminal session, {0} ({1})", "Timeout", "Received invalid network data"];
524 function formatAgentConsoleMessage(msg, msgid, msgargs) {
525 var r;
526 if (msgargs == null) { msgargs = []; }
527 while (msgargs.length < 3) { msgargs.push(''); } // We need to call the format function in a way that works with older browsers and minifier, can't use apply() or ...
528 if (msgid && (msgid < agentConsoleMessages.length)) { r = EscapeHtml(format(agentConsoleMessages[msgid], (msgargs[0]), (msgargs[1]), (msgargs[2]))); } else { r = EscapeHtml(msg); }
529 return r.split('\n').join('<br />') + '<br /><br />';
530 }
531
532 function connectDesktop(e, contype, tsid, consent) {
533 if (xxdialogMode) return;
534 if ((e != null) && (e.shiftKey != false) && (contype == 3)) { contype = 1; } // If the shift key is not pressed, don't try to ask for session list.
535 QV('p11DeskSessionSelector', false);
536 p11clearConsoleMsg();
537 if (desktop == null) {
538 if (contype == 2) {
539 // Setup the Intel AMT remote desktop
540 //if ((desktopNode.intelamt.user == null) || (desktopNode.intelamt.user == '')) { editDeviceAmtSettings(desktopNode._id, connectDesktop, 2); return; }
541 desktop = CreateAmtRedirect(CreateAmtRemoteDesktop('Desk'), authCookie);
542 desktop.debugmode = debugmode;
543 desktop.onStateChanged = onDesktopStateChange;
544 desktop.m.stopInput = (viewOnly == 1);
545 desktop.m.bpp = (desktopsettings.encoding == 1 || desktopsettings.encoding == 3) ? 1 : 2;
546 desktop.m.useZRLE = (desktopsettings.encoding < 3);
547 desktop.m.localKeyMap = desktopsettings.localkeymap;
548 desktop.m.showmouse = desktopsettings.showmouse;
549 desktop.m.onScreenSizeChange = deskAdjust;
550 desktop.m.onKvmData = function (x) {
551 //console.log('onKvmData (' + x.length + '): ' + x);
552 // Send the presense probe only once if needed.
553 if (x.length == 0) { if (!desktop.m._sentPresence) { desktop.m._sentPresence = true; desktop.m.sendKvmData(JSON.stringify({ action: 'present', ver: 1 })); } return; }
554 var data = null;
555 try { data = JSON.parse(x); } catch (e) { }
556 if ((data != null) && (data.action != null)) {
557 if (data.action == 'restart') {
558 // Clear WebRTC channel
559 webRtcDesktopReset();
560 desktop.m.sendKvmData(JSON.stringify({ action: 'present', ver: 1 }));
561 } else if ((data.action == 'present') && (webRtcDesktop == null)) {
562 // Setup WebRTC channel
563 webRtcDesktop = { platform: data.platform };
564 var configuration = null; //{ "iceServers": [ { 'urls': 'stun:stun.cloudflare.com:3478' }, { 'urls': 'stun:stun.l.google.com:19302' } ] };
565 if (typeof RTCPeerConnection !== 'undefined') { webRtcDesktop.webrtc = new RTCPeerConnection(configuration); }
566 else if (typeof webkitRTCPeerConnection !== 'undefined') { webRtcDesktop.webrtc = new webkitRTCPeerConnection(configuration); }
567
568 webRtcDesktop.webchannel = webRtcDesktop.webrtc.createDataChannel("DataChannel", {}); // { ordered: false, maxRetransmits: 2 }
569 webRtcDesktop.webchannel.onopen = function () {
570 // Switch to software KVM
571 //if (urlvars && urlvars['kvmdatatrace']) { console.log('WebRTC Data Channel Open'); }
572 console.log('WebRTC Data Channel Open');
573 Q('deskstatus').textContent = StatusStrs[desktop.State] + ", Soft-KVM";
574 desktop.m.hold(true);
575 webRtcDesktop.webRtcActive = true;
576 webRtcDesktop.softdesktop = CreateKvmDataChannel(webRtcDesktop.webchannel, CreateAgentRemoteDesktop('Desk', Q('id_mainarea')), desktop.m);
577 webRtcDesktop.softdesktop.m.setRotation(desktop.m.rotation);
578 webRtcDesktop.softdesktop.m.onScreenSizeChange = deskAdjust;
579 if (desktopsettings.quality) { webRtcDesktop.softdesktop.m.CompressionLevel = desktopsettings.quality; } // Number from 1 to 100. 50 or less is best.
580 if (desktopsettings.scaling) { webRtcDesktop.softdesktop.m.ScalingLevel = desktopsettings.scaling; }
581 webRtcDesktop.softdesktop.Start();
582
583 // Check if we can get remote file access
584 // ###BEGIN###{DesktopInbandFiles}
585 /*
586 QV('go24', true); // Files
587 downloadFile = null;
588 p24files = webRtcDesktop.softdesktop;
589 p24targetpath = '';
590 webRtcDesktop.softdesktop.onControlMsg = onFilesControlData;
591 webRtcDesktop.softdesktop.sendCtrlMsg(JSON.stringify({ action: 'ls', reqid: 1, path: '' })); // Ask for the root folder
592 */
593 // ###END###{DesktopInbandFiles}
594 }
595 webRtcDesktop.webchannel.onclose = function (event) {
596 //if (urlvars['kvmdatatrace']) { console.log('WebRTC Data Channel Closed'); }
597 console.log('WebRTC Data Channel Closed');
598 webRtcDesktopReset();
599 }
600 webRtcDesktop.webrtc.onicecandidate = function (e) {
601 if (e.candidate == null) {
602 desktop.m.sendKvmData(JSON.stringify({ action: 'offer', ver: 1, sdp: webRtcDesktop.webrtcoffer.sdp }));
603 } else {
604 webRtcDesktop.webrtcoffer.sdp += ('a=' + e.candidate.candidate + '\r\n'); // New candidate, add it to the SDP
605 }
606 }
607 webRtcDesktop.webrtc.oniceconnectionstatechange = function () {
608 if ((webRtcDesktop != null) && (webRtcDesktop.webrtc != null) && ((webRtcDesktop.webrtc.iceConnectionState == 'disconnected') || (webRtcDesktop.webrtc.iceConnectionState == 'failed'))) { /*console.log('WebRTC ICE Failed');*/ webRtcDesktopReset(); }
609 }
610 webRtcDesktop.webrtc.createOffer(function (offer) {
611 // Got the offer
612 webRtcDesktop.webrtcoffer = offer;
613 webRtcDesktop.webrtc.setLocalDescription(offer, function () { }, webRtcDesktopReset);
614 }, webRtcDesktopReset, { mandatory: { OfferToReceiveAudio: false, OfferToReceiveVideo: false } });
615 } else if ((data.action == 'answer') && (webRtcDesktop != null)) {
616 // Complete the WebRTC channel
617 webRtcDesktop.webrtc.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp: data.sdp }), function () { }, webRtcDesktopReset);
618 }
619 }
620 };
621 desktop.Start(null, 16994, '*', '*', 0);
622 desktop.contype = 2;
623 } else if ((contype == null) || (contype == 1) || (contype == 3)) {
624 // Setup the Mesh Agent remote desktop
625 desktop = CreateAgentRedirect(null, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort, authCookie, null, domainUrl);
626 desktop.m.stopInput = (viewOnly == 1);
627 desktop.m.mouseCursorActive(true);
628 desktop.debugmode = debugmode;
629 desktop.m.debugmode = debugmode;
630 desktop.attemptWebRTC = attemptWebRTC;
631 desktop.options = {};
632 if (tsid != null) { desktop.options.tsid = tsid; }
633 if (consent != null) { desktop.options.consent = consent; }
634 desktop.onStateChanged = onDesktopStateChange;
635 desktop.onConsoleMessageChange = function () {
636 if (desktop.consoleMessage) {
637 Q('p11DeskConsoleMsg').innerHTML += formatAgentConsoleMessage(desktop.consoleMessage, desktop.consoleMessageId, desktop.consoleMessageArgs);
638 QV('p11DeskConsoleMsg', true);
639 if (p11DeskConsoleMsgTimer != null) { clearTimeout(p11DeskConsoleMsgTimer); }
640 if (desktop.consoleMessageTimeout) { p11DeskConsoleMsgTimer = setTimeout(p11clearConsoleMsg, desktop.consoleMessageTimeout * 1000); }
641 } else {
642 p11clearConsoleMsg();
643 }
644 }
645 desktop.onMetadataChange = function (metadata) { updateMetadata(desktop, 'deskmetadata'); }
646 desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best.
647 desktop.m.ScalingLevel = desktopsettings.scaling;
648 if (desktopsettings.framerate) { desktop.m.FrameRateTimer = desktopsettings.framerate; }
649 desktop.m.onDisplayinfo = deskDisplayInfo;
650 desktop.m.onScreenSizeChange = deskAdjust;
651 desktop.Start(null);
652 desktop.latency.callback = function (ms) { /* console.log('latency', ms); */ updateSessionTime(); };
653 desktop.contype = 1;
654 }
655 } else {
656 // Disconnect and clean up the remote desktop
657 desktop.Stop();
658 webRtcDesktopReset();
659 desktop = null;
660 //if (pluginHandler != null) { pluginHandler.callHook('onDesktopDisconnect'); }
661 }
662 }
663
664 function updateMetadata(conn, elementid) {
665 var str = '', viewerCount = 0;
666 if (conn && (conn.State == 3)) {
667 if (conn.metadata && conn.metadata.users) { for (var i in conn.metadata.users) { viewerCount += conn.metadata.users[i]; } }
668 if (viewerCount > 1) { str = '<span onclick=showSessionMetadata(1) style=cursor:pointer>' + format(", {0} watching", viewerCount) + '</span>'; }
669 }
670 QH('deskmetadata', str);
671 if ((conn == desktop) && (xxdialogTag == ('sessionMetadata1'))) { showSessionMetadata(1); }
672 }
673
674 function showSessionMetadata(cid) {
675 if (xxdialogMode && (xxdialogTag != ('sessionMetadata' + cid))) return;
676 if (xxdialogMode) { setDialogMode(0); }
677 var conn = null;
678 if (cid == 1) { conn = desktop; }
679 if (conn && conn.metadata) {
680 var x = '';
681 if (conn.metadata.startTime) { x += addHtmlValue4("Start Time", printDateTime(new Date(conn.metadata.startTime))); }
682 if (conn.metadata.users) {
683 for (var i in conn.metadata.users) {
684 var val = (conn.metadata.users[i] == 1) ? "1 connection" : format("{0} connections", conn.metadata.users[i]);
685 var username = i.split('/')[2];
686 if ((users != null) && (users[i] != null)) { username = users[i].name; }
687 x += addHtmlValue4(format("User \"{0}\"", username), val);
688 }
689 }
690 setDialogMode(2, "Session Information", 1, null, x, 'sessionMetadata' + cid);
691 }
692 }
693
694 function p11clearConsoleMsg() { QH('p11DeskConsoleMsg', ''); QV('p11DeskConsoleMsg', false); if (p11DeskConsoleMsgTimer) { clearTimeout(p11DeskConsoleMsgTimer); p11DeskConsoleMsgTimer = null; } }
695 function p12clearConsoleMsg() { QH('p12TermConsoleMsg', ''); QV('p12TermConsoleMsg', false); if (p12TermConsoleMsgTimer) { clearTimeout(p12TermConsoleMsgTimer); p12TermConsoleMsgTimer = null; } }
696 function p13clearConsoleMsg() { QH('p13FilesConsoleMsg', ''); QV('p13FilesConsoleMsg', false); if (p13FilesConsoleMsgTimer) { clearTimeout(p13FilesConsoleMsgTimer); p13FilesConsoleMsgTimer = null; } }
697
698 var webRtcDesktop = null;
699 function webRtcDesktopReset() {
700 if (webRtcDesktop == null) return;
701 if (webRtcDesktop.softdesktop != null) { webRtcDesktop.softdesktop.Stop(); webRtcDesktop.softdesktop = null; }
702 if (webRtcDesktop.webchannel != null) { try { webRtcDesktop.webchannel.close(); } catch (e) { } webRtcDesktop.webchannel = null; }
703 if (webRtcDesktop.webrtc != null) { try { webRtcDesktop.webrtc.close(); } catch (e) { } webRtcDesktop.webrtc = null; }
704 webRtcDesktop = null;
705 // Switch back to hardware KVM
706 if (desktop && desktop.m) {
707 desktop.m.hold(false);
708 Q('deskstatus').textContent = StatusStrs[desktop.State];
709 }
710 // ###BEGIN###{DesktopInbandFiles}
711 /*
712 p24files = null;
713 p24downloadFileCancel() // If any downloads are in process, cancel them.
714 p24uploadFileCancel(); // If any uploads are in process, cancel them.
715 QV('go24', false); // Files
716 if (currentView == 24) { go(14); }
717 */
718 // ###END###{DesktopInbandFiles}
719 }
720
721 function onDesktopStateChange(xdesktop, state) {
722 var xstate = state;
723 if ((xstate == 3) && (xdesktop.contype == 2)) { xstate++; }
724 var str = StatusStrs[xstate];
725 if ((desktop != null) && (desktop.webRtcActive == true)) { str += ", WebRTC"; }
726 //if (desktop.m.stopInput == true) { str += ', Loopback'; }
727 QH('deskstatus', str);
728 switch (state) {
729 case 0:
730 // Stop recording
731 if (desktop.m.recordedData != null) { deskRecordSession(); }
732
733 // Disconnect and clean up the remote desktop
734 desktop.Stop();
735 desktop = null;
736 QV('DeskFocus', false);
737 QV('termdisplays', false);
738 QV('deskRecordIcon', false);
739 if (fullscreen == true) { deskToggleFull(); }
740 webRtcDesktopReset();
741 deskPreferedStickyDisplay = 0;
742 break;
743 case 2:
744 break;
745 case 3:
746 if (desktop && (desktop.serverIsRecording == true)) { QV('deskRecordIcon', true); }
747 desktop.startTime = new Date();
748 if (updateSessionTimer == null) { updateSessionTimer = setInterval(updateSessionTime, 1000); }
749 break;
750 default:
751 //console.log('Unknown onDesktopStateChange state', state);
752 break;
753 }
754 updateDesktopButtons();
755 deskAdjust();
756 setTimeout(deskAdjust, 50);
757 updateMetadata(desktop, 'deskmetadata');
758 }
759
760 function updateSessionTime() {
761 // Desktop
762 var latencyStr = '', seconds = 0;
763 if (desktop && desktop.startTime) {
764 if (desktop.latency && (desktop.latency.current >= 0)) { latencyStr = format('{0} ms', desktop.latency.current); }
765 seconds = Math.floor((new Date() - desktop.startTime) / 1000);
766 QH('DeskTimer', zeroPad(Math.floor(seconds / 3600), 2) + ':' + zeroPad((Math.floor(seconds / 60) % 60), 2) + ':' + zeroPad((seconds % 60), 2));
767 QH('DeskLatency', latencyStr);
768 } else {
769 QH('DeskTimer', '');
770 QH('DeskLatency', '');
771 }
772
773 // Terminal
774 seconds = 0;
775 if (terminal && terminal.startTime) {
776 if (terminal.latency && (terminal.latency.current >= 0)) { latencyStr = format('{0} ms, ', terminal.latency.current); }
777 seconds = Math.floor((new Date() - terminal.startTime) / 1000);
778 QH('TermTimer', latencyStr + zeroPad(Math.floor(seconds / 3600), 2) + ':' + zeroPad((Math.floor(seconds / 60) % 60), 2) + ':' + zeroPad((seconds % 60), 2));
779 } else {
780 QH('TermTimer', '');
781 }
782
783 if ((desktop == null) && (terminal == null)) { clearInterval(updateSessionTimer); updateSessionTimer = null; }
784 }
785
786 function showDesktopSettings() {
787 if (xxdialogMode) return;
788 applyDesktopSettings();
789 updateDesktopButtons();
790 setDialogMode(7, "Remote Desktop Settings", 3, showDesktopSettingsChanged);
791 }
792
793 function showDesktopSettingsChanged() {
794 desktopsettings.encoding = d7desktopmode.value;
795 desktopsettings.showfocus = d7showfocus.checked;
796 desktopsettings.showmouse = d7showcursor.checked;
797 desktopsettings.quality = d7bitmapquality.value;
798 desktopsettings.scaling = d7bitmapscaling.value;
799 desktopsettings.framerate = d7framelimiter.value;
800 desktopsettings.swapmouse = d7deskSwapMouse.checked;
801 desktopsettings.remotekeymap = d7deskRemoteKeyMap.checked;
802 desktopsettings.localkeymap = d7localKeyMap.checked;
803 localStorage.setItem('desktopsettings', JSON.stringify(desktopsettings));
804 applyDesktopSettings();
805 if (desktop) {
806 if (desktop.contype == 1) {
807 desktop.m.SwapMouse = desktopsettings.swapmouse;
808 desktop.m.remoteKeyMap = desktopsettings.remotekeymap;
809 if (desktop.State != 0) {
810 desktop.m.SendCompressionLevel(1, desktopsettings.quality, desktopsettings.scaling, desktopsettings.framerate);
811 }
812 }
813 if (desktop.contype == 2) {
814 if (desktop.State != 0) { desktop.Stop(); setTimeout(function () { connectDesktop(null, 2); }, 50); }
815 }
816 }
817 }
818
819 function applyDesktopSettings() {
820 var r = '', ops = (features2 & 1) ? [100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 5, 1] : [60, 50, 40, 30, 20, 10, 5, 1]
821 for (var i in ops) { r += '<option value=' + ops[i] + '>' + ops[i] + '%</option>'; }
822 QH('d7bitmapquality', r);
823 d7desktopmode.value = desktopsettings.encoding;
824 d7showfocus.checked = desktopsettings.showfocus;
825 d7showcursor.checked = desktopsettings.showmouse;
826 d7bitmapquality.value = 40; // Default value
827 if (ops.indexOf(parseInt(desktopsettings.quality)) >= 0) { d7bitmapquality.value = desktopsettings.quality; }
828 d7bitmapscaling.value = desktopsettings.scaling;
829 if (desktopsettings.framerate) { d7framelimiter.value = desktopsettings.framerate; } else { d7framelimiter.value = 100; }
830 if (desktopsettings.swapmouse != null) { d7deskSwapMouse.checked = desktopsettings.swapmouse; }
831 if (desktopsettings.remotekeymap != null) { d7deskRemoteKeyMap.checked = desktopsettings.remotekeymap; }
832 if (desktopsettings.localkeymap) { d7localKeyMap.checked = desktopsettings.localkeymap; }
833 }
834
835 // Enter browser fullscreen
836 function enterBrowserFullscreen(elem) {
837 if (navigator.keyboard && navigator.keyboard.lock) { navigator.keyboard.lock(); }
838 if (elem.requestFullscreen) { elem.requestFullscreen(); }
839 else if (elem.msRequestFullscreen) { elem.msRequestFullscreen(); }
840 else if (elem.mozRequestFullScreen) { elem.mozRequestFullScreen(); }
841 else if (elem.webkitRequestFullscreen) { elem.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); }
842 }
843
844 // Exit browser fullscreen
845 function exitBrowserFullscreen() {
846 if (document.exitFullscreen) { document.exitFullscreen(); }
847 else if (document.msExitFullscreen) { document.msExitFullscreen(); }
848 else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); }
849 else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); }
850 if (navigator.keyboard && navigator.keyboard.unlock) { navigator.keyboard.unlock(); }
851 }
852
853 // Return true if the browser is fullscreen. This is a delayed method that will return true/false late. Not very useful.
854 function isBrowserFullscreen() {
855 if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) { return false; } else { return true; }
856 }
857
858 var fullscreen = false;
859 var browserfullscreen = false;
860 function deskToggleFull(e) {
861 var xtermActive = !((urlargs.xterm === 0) || ((terminal != null) && (xterm == null)));
862 fullscreen = !fullscreen;
863 if (fullscreen) {
864 QC('body').add('fulldesk');
865 QS('deskarea3x')['height'] = '100%';
866 QS('deskarea3x')['max-height'] = '100%';
867 if (xtermActive) {
868 // XTerm terminal
869 QS('termTable')['position'] = 'absolute';
870 QS('termTable')['top'] = QS('termTable')['bottom'] = QS('termTable')['left'] = QS('termTable')['right'] = '0';
871 } else {
872 // Legacy terminal
873 QS('termTable')['height'] = '100%';
874 QS('termTable')['max-height'] = '100%';
875 }
876
877 // If shift is pressed, enter browser full screen.
878 if (e.shiftKey == true) {
879 enterBrowserFullscreen(Q('container'));
880 browserfullscreen = true;
881 }
882 } else {
883 QC('body').remove('fulldesk');
884 var hide = urlargs.hide;
885 if (footerBar == false) { hide |= 4; }
886 var xh = (((hide & 1) ? 0 : 66) + ((hide & 2) ? 0 : 24) + ((hide & 4) ? 0 : 45) + ((hide & 8) ? 0 : 60)); // 0 to 195
887 QS('deskarea3x')['height'] = 'calc(100vh - ' + (75 + xh) + 'px)';
888 QS('deskarea3x')['max-height'] = 'calc(100vh - ' + (75 + xh) + 'px)';
889 if (xtermActive) {
890 // XTerm terminal
891 QS('termTable')['position'] = null;
892 QS('termTable')['top'] = QS('termTable')['bottom'] = QS('termTable')['left'] = QS('termTable')['right'] = null;
893 } else {
894 // Legacy terminal
895 QS('termTable')['height'] = 'calc(100vh - ' + (75 + xh) + 'px)';
896 QS('termTable')['max-height'] = 'calc(100vh - ' + (75 + xh) + 'px)';
897 }
898 if (browserfullscreen == true) { exitBrowserFullscreen(); browserfullscreen = false; }
899 }
900 deskAdjust();
901 updateDesktopButtons();
902 adjustPanels();
903 //setTimeout(adjustPanels, 10);
904 //setTimeout(function() { xtermfit.fit(); }, 10);
905 if (xterm != null) { if (xxcurrentView == 12) { xtermfit.fit(); xterm.focus(); } }
906 }
907
908 function mdeskAdjust(mod, sw, sh, cv) {
909 if (!mod || !sw || !sh || !cv) return;
910
911 // Check if we are in single desktop mode
912 if (cv.id == 'Desk') { deskAdjust(); return; }
913
914 // Figure out and adjust the size to fill the width of the div
915 var vsize = [{ x: 180, y: 101 }, { x: 302, y: 169 }, { x: 454, y: 255 }][Q('sizeselect').selectedIndex];
916 var realw = vsize.x + 2, tw = Q('xdevices').clientWidth - 30, xw = Math.floor(tw / realw);
917 xw = realw + Math.floor((tw - (xw * realw)) / xw);
918 vsize.y = vsize.y * (xw / vsize.x);
919 vsize.x = xw;
920 var mh = vsize.y, mw = vsize.x;
921 if (mod.State != 0) { mh = vsize.y; mw = (sw / sh) * vsize.y; }
922 QS(cv.id)['max-height'] = mh + 'px';
923 QS(cv.id)['max-width'] = mw + 'px';
924 QS(cv.id)['margin-top'] = '0';
925 QS(cv.id)['margin-bottom'] = '0';
926 }
927
928 // Remote desktop special key combos for Windows
929 function deskSendKeys() {
930 Q('DeskWD').blur();
931 if (xxdialogMode || desktop == null || desktop.State != 3) return;
932 var ks = Q('deskkeys').value;
933 if (ks == 0) { // WIN+Down arrow
934 if (desktop.contype == 2) {
935 desktop.m.sendkey([[0xffe7, 1], [0xff54, 1], [0xff54, 0], [0xffe7, 0]]); // Intel AMT: Meta-left down, Down arrow press, Down arrow release, Meta-left release
936 } else {
937 desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.DOWN, 40], [desktop.m.KeyAction.UP, 40], [desktop.m.KeyAction.EXUP, 0x5B]]); // Agent: L-Winkey press, Down arrow press, Down arrow release, L-Winkey release
938 }
939 } else if (ks == 1) { // WIN+Up arrow
940 if (desktop.contype == 2) {
941 desktop.m.sendkey([[0xffe7, 1], [0xff52, 1], [0xff52, 0], [0xffe7, 0]]); // Intel AMT: Meta-left down, Up arrow press, Up arrow release, Meta-left release
942 } else {
943 desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.DOWN, 38], [desktop.m.KeyAction.UP, 38], [desktop.m.KeyAction.EXUP, 0x5B]]); // MeshAgent: L-Winkey press, Up arrow press, Up arrow release, L-Winkey release
944 }
945 } else if (ks == 2) { // WIN+L arrow
946 if (desktop.contype == 2) {
947 desktop.m.sendkey([[0xffe7, 1], [0x6c, 1], [0x6c, 0], [0xffe7, 0]]); // Intel AMT: Meta-left down, 'l' press, 'l' release, Meta-left release
948 } else {
949 desktop.sendCtrlMsg('{"action":"lock"}');
950 //desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN,0x5B],[desktop.m.KeyAction.DOWN,76],[desktop.m.KeyAction.UP,76],[desktop.m.KeyAction.EXUP,0x5B]]); // MeshAgent: L-Winkey press, 'L' press, 'L' release, L-Winkey release
951 //desktop.m.SendKeyMsgKC(desktop.m.KeyAction.EXDOWN, 0x5B);
952 //desktop.m.SendKeyMsgKC(desktop.m.KeyAction.DOWN, 76);
953 //desktop.m.SendKeyMsgKC(desktop.m.KeyAction.UP, 76);
954 //desktop.m.SendKeyMsgKC(desktop.m.KeyAction.EXUP, 0x5B);
955 }
956 } else if (ks == 3) { // WIN+M arrow
957 if (desktop.contype == 2) {
958 desktop.m.sendkey([[0xffe7, 1], [0x6d, 1], [0x6d, 0], [0xffe7, 0]]); // Intel AMT: Meta-left down, 'm' press, 'm' release, Meta-left release
959 } else {
960 desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.DOWN, 77], [desktop.m.KeyAction.UP, 77], [desktop.m.KeyAction.EXUP, 0x5B]]); // MeshAgent: L-Winkey press, 'M' press, 'M' release, L-Winkey release
961 }
962 } else if (ks == 4) { // Shift+WIN+M arrow
963 if (desktop.contype == 2) {
964 desktop.m.sendkey([[0xffe1, 1], [0xffe7, 1], [0x6d, 1], [0x6d, 0], [0xffe7, 0], [0xffe1, 0]]); // Intel AMT: Shift-left down, Meta-left down, 'm' press, 'm' release, Meta-left release, Shift-left release
965 } else {
966 desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.DOWN, 16], [desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.DOWN, 77], [desktop.m.KeyAction.UP, 77], [desktop.m.KeyAction.EXUP, 0x5B], [desktop.m.KeyAction.UP, 16]]); // MeshAgent: L-shift press, L-Winkey press, 'M' press, 'M' release, L-Winkey release, L-shift release
967 }
968 } else if (ks == 5) { // WIN
969 if (desktop.contype == 2) {
970 desktop.m.sendkey([[0xffe7, 1], [0xffe7, 0]]); // Intel AMT: Meta-left down, Meta-left release
971 } else {
972 desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.EXUP, 0x5B]]); // MeshAgent: L-Winkey press, L-Winkey release
973 }
974 } else if (ks == 6) { // WIN+R
975 if (desktop.contype == 2) {
976 desktop.m.sendkey([[0xffe7, 1], [0x72, 1], [0x72, 0], [0xffe7, 0]]); // Intel AMT: Meta-left down, 'r' press, 'r' release, Meta-left release
977 } else {
978 desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.DOWN, 82], [desktop.m.KeyAction.UP, 82], [desktop.m.KeyAction.EXUP, 0x5B]]); // MeshAgent: L-Winkey press, 'R' press, 'R' release, L-Winkey release
979 }
980 } else if (ks == 7) { // ALT-F4
981 if (desktop.contype == 2) {
982 desktop.m.sendkey([[0xffe9, 1], [0xffc1, 1], [0xffc1, 0], [0xffe9, 0]]); // Intel AMT: Alt down, 'F4' press, 'F4' release, Alt release
983 } else {
984 desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 18], [desktop.m.KeyAction.DOWN, 115], [desktop.m.KeyAction.UP, 115], [desktop.m.KeyAction.EXUP, 18]]); // MeshAgent: Alt press, 'F4' press, 'F4' release, Alt release
985 }
986 } else if (ks == 8) { // CTRL-W
987 if (desktop.contype == 2) {
988 desktop.m.sendkey([[0xffe3, 1], [0x77, 1], [0x77, 0], [0xffe3, 0]]); // Intel AMT: Ctrl down, 'w' press, 'w' release, Ctrl release
989 } else {
990 desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 17], [desktop.m.KeyAction.DOWN, 87], [desktop.m.KeyAction.UP, 87], [desktop.m.KeyAction.EXUP, 17]]); // MeshAgent: Ctrl press, 'W' press, 'W' release, Ctrl release
991 }
992 } else if (ks == 9) { // ALT-TAB
993 if (desktop.contype == 2) {
994 desktop.m.sendkey([[0xffe9, 1], [0xff09, 1], [0xff09, 0], [0xffe9, 0]]); // Intel AMT: Alt down, 'TAB' press, 'TAB' release, Alt release
995 } else {
996 desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 18], [desktop.m.KeyAction.DOWN, 9], [desktop.m.KeyAction.UP, 9], [desktop.m.KeyAction.EXUP, 18]]); // MeshAgent: Alt press, 'TAB' press, 'TAB' release, Alt release
997 }
998 } else if (ks == 10) { // CTRL-ALT-DEL
999 desktop.m.sendcad();
1000 } else if (ks == 11) { // WIN-LEFT
1001 if (desktop.contype == 2) {
1002 desktop.m.sendkey([[0xffe7, 1], [0xff51, 1], [0xff51, 0], [0xffe7, 0]]); // Intel AMT: Meta-left down, Left arrow press, Left arrow release, Meta-left release
1003 } else {
1004 desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.DOWN, 37], [desktop.m.KeyAction.UP, 37], [desktop.m.KeyAction.EXUP, 0x5B]]);
1005 }
1006 } else if (ks == 12) { // WIN-RIGHT
1007 if (desktop.contype == 2) {
1008 desktop.m.sendkey([[0xffe7, 1], [0xff53, 1], [0xff53, 0], [0xffe7, 0]]); // Intel AMT: Meta-left down, Right arrow press, Right arrow release, Meta-left release
1009 } else {
1010 desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.DOWN, 39], [desktop.m.KeyAction.UP, 39], [desktop.m.KeyAction.EXUP, 0x5B]]);
1011 }
1012 }
1013 }
1014
1015 function ondockeypress(e) {
1016 setSessionActivity();
1017 if (!xxdialogMode && desktop && isInputAllowed()) {
1018 // Check what keys we are allows to send
1019 /*
1020 if (currentNode != null) {
1021 var meshrights = GetNodeRights(currentNode);
1022 var inputAllowed = ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
1023 if (inputAllowed == false) return false;
1024 var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
1025 if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
1026 }
1027 */
1028 return desktop.m.handleKeys(e);
1029 }
1030 }
1031
1032 function ondockeydown(e) {
1033 setSessionActivity();
1034 if (!xxdialogMode && desktop && isInputAllowed()) {
1035 // Check what keys we are allows to send
1036 /*
1037 if (currentNode != null) {
1038 var meshrights = GetNodeRights(currentNode);
1039 var inputAllowed = ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
1040 if (inputAllowed == false) return false;
1041 var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
1042 if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
1043 }
1044 */
1045 return desktop.m.handleKeyDown(e);
1046 }
1047 }
1048
1049 function ondockeyup(e) {
1050 setSessionActivity();
1051 if (!xxdialogMode && desktop && isInputAllowed()) {
1052 // Check what keys we are allows to send
1053 /*
1054 if (currentNode != null) {
1055 var meshrights = GetNodeRights(currentNode);
1056 var inputAllowed = ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
1057 if (inputAllowed == false) return false;
1058 var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
1059 if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
1060 }
1061 */
1062 return desktop.m.handleKeyUp(e);
1063 }
1064 }
1065
1066 // Remote desktop typing
1067 function showDeskType() {
1068 if (xxdialogMode || desktop == null || desktop.State != 3) return;
1069 Q('DeskType').blur();
1070 var x = '<div>' + "Enter text and click OK to remotely type it using a US english keyboard. Make sure to place the remote cursor at the correct position before proceeding." + '<div>';
1071 x += '<textarea id=d2typeText style="margin-top:5px;width:100%;height:184px;resize:none" maxlength=2000></textarea>';
1072 setDialogMode(2, "Remote Keyboard Entry", 3, showDeskTypeEx, x);
1073 Q('d2typeText').focus();
1074 }
1075
1076 var AmtDeskTypeTimer = null;
1077 var AmtDeskTypeContent = null;
1078 var DeskTypeTranslate = { 39: 222, 42: 106, 43: 107, 44: 188, 45: 189, 46: 190, 47: 191, 59: 186, 61: 187, 91: 219, 92: 220, 93: 221, 96: 192, 191: 111 };
1079 var DeskTypeShiftTranslate = { 33: 49, 34: 222, 35: 51, 36: 52, 37: 53, 38: 55, 40: 57, 41: 48, 58: 186, 60: 188, 62: 190, 63: 191, 64: 50, 94: 54, 95: 189, 106: 56, 107: 187, 123: 219, 124: 220, 125: 221, 126: 192 };
1080 function showDeskTypeEx() {
1081 var txt = Q('d2typeText').value, ltxt = Q('d2typeText').value.toUpperCase(), x = [], shift = false;
1082 if (desktop.contype == 2) {
1083 // Intel AMT
1084 for (var i in txt) { var a = txt.charCodeAt(i); x.push([a, 1], [a, 0]); }
1085 AmtDeskTypeContent = x;
1086 AmtDeskTypeTimer = setInterval(function () {
1087 var key = AmtDeskTypeContent.shift();
1088 if (desktop) { desktop.m.sendkey(key[0], key[1]); }
1089 if ((desktop == null) || (AmtDeskTypeContent.length == 0)) { clearInterval(AmtDeskTypeTimer); AmtDeskTypeContent = null; }
1090 }, 10);
1091 } else {
1092 // MeshAgent
1093 if (desktopsettings.remotekeymap !== true) {
1094 // New unicode typing
1095 desktop.m.SendStringUnicode(txt);
1096 } else {
1097 // Old scan code typing. This is for non-unicode system.
1098 for (var i in txt) {
1099 var a = txt.charCodeAt(i), b = ltxt.charCodeAt(i);
1100 if (((a >= 65) && (a <= 90)) || ((a >= 97) && (a <= 122))) {
1101 if ((a == b) && (shift == false)) { x.push([desktop.m.KeyAction.DOWN, 16]); shift = true; } // LShift down
1102 if ((a != b) && (shift == true)) { x.push([desktop.m.KeyAction.UP, 16]); shift = false; } // LShift up
1103 } else if ((a >= 48) && (a <= 57)) {
1104 if (shift == true) { x.push([desktop.m.KeyAction.UP, 16]); shift = false; } // Shift up
1105 } else if (DeskTypeTranslate[a]) {
1106 if (shift == true) { x.push([desktop.m.KeyAction.UP, 16]); shift = false; } // Shift up
1107 b = DeskTypeTranslate[a];
1108 } else if (DeskTypeShiftTranslate[a]) {
1109 if (shift == false) { x.push([desktop.m.KeyAction.DOWN, 16]); shift = true; } // LShift down
1110 b = DeskTypeShiftTranslate[a];
1111 }
1112 x.push([desktop.m.KeyAction.DOWN, b], [desktop.m.KeyAction.UP, b]);
1113 }
1114 if (shift == true) { x.push([desktop.m.KeyAction.UP, 16]); shift = false; } // Shift up
1115 desktop.m.SendKeyMsgKC(x);
1116 }
1117 }
1118 }
1119
1120 /*
1121 // Show clipboard dialog
1122 function showDeskClip() {
1123 if (xxdialogMode || desktop == null || desktop.State != 3) return;
1124 Q('DeskClip').blur();
1125 var x = '';
1126 x += '<input id=dlgClipGet type=button value="Get Clipboard" style=width:120px onclick=showDeskClipGet()>';
1127 x += '<input id=dlgClipSet type=button value="Set Clipboard" style=width:120px onclick=showDeskClipSet()>';
1128 x += '<div id=dlgClipStatus style="display:inline-block;margin-left:8px" ></div>';
1129 x += '<textarea id=d2clipText style="width:100%;height:184px;resize:none" maxlength=65535></textarea>';
1130 x += '<input type=button value="Close" style=width:80px;float:right onclick=dialogclose(0)><div style=height:26px;margin-top:3px><span id=linuxClipWarn style=display:none>' + "Remote clipboard is valid for 60 seconds." + '</span>&nbsp;</div><div></div>';
1131 setDialogMode(2, "Remote Clipboard", 8, null, x, 'clipboard');
1132 Q('d2clipText').focus();
1133 }
1134
1135 function showDeskClipGet() {
1136 if (desktop == null || desktop.State != 3) return;
1137 meshserver.send({ action: 'msg', type: 'getclip', nodeid: currentNode._id });
1138 }
1139
1140 function showDeskClipSet() {
1141 if (desktop == null || desktop.State != 3) return;
1142 meshserver.send({ action: 'msg', type: 'setclip', nodeid: currentNode._id, data: Q('d2clipText').value });
1143 //QV('linuxClipWarn', currentNode && currentNode.agent && (currentNode.agent.id > 4) && (currentNode.agent.id != 21) && (currentNode.agent.id != 22));
1144 }
1145 */
1146
1147 // Send CTRL-ALT-DEL
1148 function sendCAD() {
1149 if (xxdialogMode || desktop == null || desktop.State != 3) return;
1150 desktop.m.sendcad();
1151 }
1152
1153 /*
1154 // Show process dialogs
1155 function toggleDeskTools() {
1156 if (xxdialogMode) return;
1157 if (QS('DeskTools').display == 'none') {
1158 QV('DeskTools', true);
1159 Q('DeskTools').nodeid = currentNode._id;
1160 QH('DeskToolsProcesses', '');
1161 QH('DeskToolsServices', '');
1162 QV('deskToolsTopTabService', false);
1163 changeDeskToolTab(0)
1164 refreshDeskTools(0);
1165 refreshDeskTools(1);
1166 } else {
1167 QV('DeskTools', false);
1168 }
1169 }
1170
1171 var deskToolTabSelection = 0;
1172 function changeDeskToolTab(tabnum) {
1173 deskToolTabSelection = tabnum;
1174 QV('DeskToolsProcessTab', tabnum == 0);
1175 QV('DeskToolsServiceTab', tabnum == 1);
1176 QS('deskToolsTopTabProcess')['bottom'] = (tabnum == 0) ? '0px' : '3px';
1177 QS('deskToolsTopTabService')['bottom'] = (tabnum == 1) ? '0px' : '3px';
1178 QS('deskToolsTopTabProcess')['color'] = (tabnum == 0) ? 'black' : 'gray';
1179 QS('deskToolsTopTabService')['color'] = (tabnum == 1) ? 'black' : 'gray';
1180 }
1181
1182 // Refresh all of the desktop tool panels
1183 function refreshDeskTools(x) {
1184 var sel = (x == null) ? deskToolTabSelection : x;
1185 QV('DeskToolsRefreshButton', false);
1186 setTimeout(refreshDeskToolsEx, 500);
1187 if (sel == 0) meshserver.send({ action: 'msg', type: 'ps', nodeid: currentNode._id });
1188 if (sel == 1) meshserver.send({ action: 'msg', type: 'services', nodeid: currentNode._id });
1189 }
1190 function refreshDeskToolsEx() { QV('DeskToolsRefreshButton', true); }
1191 var deskTools = { sort: 1, ssort: 1, msg: null, smsg: null };
1192 function sortProcess(sort) { deskTools.sort = sort; showDeskToolsProcesses(deskTools.msg); }
1193 function sortService(sort) { deskTools.ssort = sort; showDeskToolsServices(deskTools.smsg); }
1194 function sortProcessPid(a, b) { if (a.p > b.p) return 1; if (a.p < b.p) return (-1); return sortProcessName(a, b); }
1195 function sortProcessName(a, b) { if (a.d > b.d) return 1; if (a.d < b.d) return (-1); return 0; }
1196 function showDeskToolsProcesses(message) {
1197 deskTools.msg = message;
1198 if (message == null) { QH('DeskToolsProcesses', ''); return; }
1199 if (Q('DeskTools').nodeid != message.nodeid) return;
1200 var p = [], processes = null;
1201 try { processes = JSON.parse(message.value); } catch (e) { }
1202 if (processes != null) {
1203 for (var pid in processes) { p.push({ p: parseInt(pid), c: processes[pid].cmd, d: processes[pid].cmd.toLowerCase(), u: processes[pid].user }); }
1204 if (deskTools.sort == 0) { p.sort(sortProcessPid); } else if (deskTools.sort == 1) { p.sort(sortProcessName); }
1205 var x = '';
1206 for (var i in p) {
1207 if (p[i].p != 0) {
1208 var c = p[i].c;
1209 if (c.length > 30) { c = '<span title="' + EscapeHtml(c) + '">' + EscapeHtml(c.substring(0, 30)) + '...</span>' } else { c = EscapeHtml(c); }
1210 x += '<div class=deskToolsBar><div style=width:50px;float:left;text-align:right;padding-right:5px>' + EscapeHtml(p[i].p) + '</div><a href=# style=float:right;padding-right:5px;cursor:pointer title="' + "Stop process" + '" onclick=\'return stopProcess(' + EscapeHtml(p[i].p) + ',"' + EscapeHtml(p[i].c) + '")\'><img width=10 height=10 src="images/trash.png"></a><div style=float:right;padding-right:5px>' + (p[i].u ? EscapeHtml(p[i].u) : '') + '</div><div>' + c + '</div></div>';
1211 }
1212 }
1213 QH('DeskToolsProcesses', x);
1214 }
1215 }
1216 function showDeskToolsServices(message) {
1217 deskTools.smsg = message;
1218 if (message == null) { QH('DeskToolsProcesses', ''); return; }
1219 if (Q('DeskTools').nodeid != message.nodeid) return;
1220 QV('deskToolsTopTabService', true);
1221 var s = [], services = null;
1222 try { services = JSON.parse(message.value); } catch (e) { }
1223 deskTools.services = services;
1224 if (services != null) {
1225 for (var i in services) {
1226 if (services[i].status) {
1227 // Windows
1228 s.push({ p: capitalizeFirstLetter(services[i].status.state.toLowerCase()), d: services[i].displayName, i: i });
1229 } else if (services[i].serviceType) {
1230 // Linux (TODO: This the service status is not displayed, not sure start/stop/restart will work).
1231 s.push({ p: services[i].serviceType, d: services[i].name, i: i });
1232 }
1233 }
1234 if (deskTools.ssort == 0) { s.sort(sortProcessPid); } else if (deskTools.ssort == 1) { s.sort(sortProcessName); }
1235 var x = '';
1236 for (var i in s) {
1237 if (s[i].p != 0) {
1238 var c = s[i].d;
1239 if (c.length > 30) { c = '<span title="' + c + '">' + c.substring(0, 30) + '...</span>' } else { c = EscapeHtml(c); }
1240 x += '<div onclick=showServiceDetailsDialog(' + s[i].i + ') class=deskToolsBar><div style=width:70px;float:left;padding-right:5px>' + EscapeHtml(s[i].p) + '</div><div>' + c + '</div></div>';
1241 }
1242 }
1243 QH('DeskToolsServices', x);
1244 }
1245 }
1246
1247 function showServiceDetailsDialog(index) {
1248 if (xxdialogMode) return;
1249 var service = deskTools.services[index];
1250 if (service != null) {
1251 var x = '';
1252 if (service.name) { x += addHtmlValue("Name", service.name); }
1253 if (service.displayName) { x += addHtmlValue("Display name", service.displayName); }
1254 if (service.status) {
1255 if (service.status.state) { x += addHtmlValue("State", capitalizeFirstLetter(service.status.state.toLowerCase())); }
1256 if (service.status.pid) { x += addHtmlValue("PID", service.status.pid); }
1257 var serviceTypes = [];
1258 if (service.status.isFileSystemDriver === true) { serviceTypes.push("FileSystemDriver"); }
1259 if (service.status.isInteractive === true) { serviceTypes.push("Interactive"); }
1260 if (service.status.isKernelDriver === true) { serviceTypes.push("KernelDriver"); }
1261 if (service.status.isOwnProcess === true) { serviceTypes.push("OwnProcess"); }
1262 if (service.status.isSharedProcess === true) { serviceTypes.push("SharedProcess"); }
1263 if (serviceTypes.length > 0) { x += addHtmlValue("Type", serviceTypes.join(', ')); }
1264 }
1265 x += '<br/><div style=float:right;margin-bottom:12px><input type=button value="' + "Close" + '" onclick=showServiceDetailsDialogEx(0,' + index + ')></div><div style=margin-bottom:12px><input type=button value="' + "Start" + '" onclick=showServiceDetailsDialogEx(1,' + index + ')><input type=button value="' + "Stop" + '" onclick=showServiceDetailsDialogEx(2,' + index + ')><input type=button value="' + "Restart" + '" onclick=showServiceDetailsDialogEx(3,' + index + ')></div>';
1266 setDialogMode(2, "Service Details", 8, null, x, name);
1267 }
1268 }
1269
1270 function showServiceDetailsDialogEx(action, index) {
1271 setDialogMode(0);
1272 if (action == 0) return;
1273 var service = deskTools.services[index];
1274 if (service != null) {
1275 if (action == 1) { meshserver.send({ action: 'msg', type: 'serviceStart', nodeid: currentNode._id, serviceName: service.name }); }
1276 if (action == 2) { meshserver.send({ action: 'msg', type: 'serviceStop', nodeid: currentNode._id, serviceName: service.name }); }
1277 if (action == 3) { meshserver.send({ action: 'msg', type: 'serviceRestart', nodeid: currentNode._id, serviceName: service.name }); }
1278 setTimeout(function () { refreshDeskTools(1) }, 1000);
1279 }
1280 }
1281 */
1282
1283 // Toggle mouse and keyboard input
1284 function toggleKvmControl() { QS('DeskControlSpan').color = Q('DeskControl').checked ? null : 'red'; }
1285
1286 // Save the desktop image to file
1287 function deskSaveImage() {
1288 if (xxdialogMode || desktop == null || desktop.State != 3) return;
1289 var d = new Date(), n = "Desktop" + '-' + d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2) + '-' + ('0' + d.getHours()).slice(-2) + '-' + ('0' + d.getMinutes()).slice(-2);
1290 Q('Desk')['toBlob'](function (blob) { saveAs(blob, n + '.png'); });
1291 }
1292
1293 function deskDisplayInfo(sender, displays, selDisplay) {
1294 var displayCount = 0, displaySelector = '';
1295 for (var i in displays) {
1296 displayCount++;
1297 displaySelector += '<option' + ((selDisplay == i) ? ' selected' : '') + ' value=' + i + '>' + displays[i] + '</option>';
1298 if ((deskPreferedStickyDisplay == i) && (selDisplay != deskPreferedStickyDisplay)) { desktop.m.SetDisplay(i); }
1299 deskPreferedStickyDisplay = -1;
1300 }
1301 QH('termdisplays', displaySelector);
1302 QV('termdisplays', displayCount > 1);
1303 }
1304
1305 function deskGetDisplayNumbers(e) { desktop.m.GetDisplayNumbers(); }
1306 var deskPreferedStickyDisplay = -1;
1307 function deskSetDisplay(e) { desktop.m.SetDisplay(deskPreferedStickyDisplay = parseInt(Q('termdisplays').value)); Q('termdisplays').blur(); }
1308
1309 // Double click detection. This is important for macOS.
1310 var dblClickDetectArgs = { t: 0, x: 0, y: 0 };
1311 function dblClickDetect(e) {
1312 if (e.buttons != 1) return;
1313 var t = Date.now();
1314 if (((t - dblClickDetectArgs.t) < 250) && (Math.abs(e.clientX - dblClickDetectArgs.x) < 2) && (Math.abs(e.clientY - dblClickDetectArgs.y) < 2)) {
1315 if (!xxdialogMode && desktop != null && isInputAllowed()) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousedblclick(e); desktop.m.sendKeepAlive(); } else { desktop.m.mousedblclick(e); } }
1316 }
1317 dblClickDetectArgs.t = t;
1318 dblClickDetectArgs.x = e.clientX;
1319 dblClickDetectArgs.y = e.clientY;
1320 }
1321
1322 function dmousedown(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && isInputAllowed()) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousedown(e); desktop.m.sendKeepAlive(); } else { desktop.m.mousedown(e); } } dblClickDetect(e); }
1323 function dmouseup(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && isInputAllowed()) if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mouseup(e); desktop.m.sendKeepAlive(); } else { desktop.m.mouseup(e); } }
1324 function dmousemove(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && isInputAllowed()) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousemove(e); desktop.m.sendKeepAlive(); } else { desktop.m.mousemove(e); } } }
1325 function dmousewheel(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && isInputAllowed()) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousewheel(e); desktop.m.sendKeepAlive(); } else { if (desktop.m.mousewheel) { desktop.m.mousewheel(e); } } haltEvent(e); return true; } return false; }
1326 function drotate(x) { if (!xxdialogMode && desktop != null) { desktop.m.setRotation(desktop.m.rotation + x); deskAdjust(); deskAdjust(); } }
1327
1328
1329 //
1330 // Terminal
1331 //
1332
1333 function setupTerminal() {
1334 // Setup the terminal
1335 if (terminal != null) { terminal.Stop(); terminal = null; }
1336 updateTerminalButtons();
1337
1338 // Terminal special keys
1339 var x = '';
1340 for (var c = 1; c < 27; c++) x += '<option value=\'' + c + '\'>' + "Ctrl" + '-' + String.fromCharCode(64 + c) + ' (' + c + ')</option>';
1341 QH('specialkeylist', x);
1342 }
1343
1344 // Show and enable the right buttons
1345 function updateTerminalButtons() {
1346 var termState = ((terminal != null) && (terminal.state != 0));
1347
1348 // Show the right buttons
1349 QV('disconnectbutton2span', termState == true);
1350 QV('connectbutton2span', termState == false);
1351 //QV('terminalSizeDropDown', termState == false);
1352
1353 // Enable buttons
1354 QE('connectbutton2', true);
1355
1356 // Key buttons
1357 QE('ctrlcbutton', termState);
1358 QE('ctrlxbutton', termState);
1359 QE('escbutton', termState);
1360 QE('bsbutton', termState);
1361 QE('pastebutton', termState);
1362 QE('specialkeylist', termState);
1363 QE('specialkeylistinput', termState);
1364
1365 // Terminal settings
1366 QV('terminalSettingsButtons', (terminal) && (terminal.contype == 2));
1367 if (terminal) {
1368 Q('id_ttypebutton').value = terminalEmulations[terminal.m.terminalEmulation];
1369 Q('id_tfxkeysbutton').value = fxEmulations[terminal.m.fxEmulation];
1370 Q('id_tcrbutton').value = (terminal.m.lineFeed == '\r\n') ? "CR+LF" : "LF";
1371 }
1372
1373 // Display extra buttons on legacy terminal
1374 var xtermActive = true;
1375 QV('termarea3xdiv', xtermActive);
1376 QV('bsbutton', !xtermActive);
1377 QV('pastebutton', !xtermActive);
1378 QV('devListToolbarViewIcons2', xtermActive);
1379 QE('termSizeList', terminal == null);
1380 }
1381
1382 // Called when the terminal state changes
1383 function onTerminalStateChange(xterminal, state) {
1384 var xstate = state;
1385 if ((xstate == 3) && (xterminal.contype == 2)) { xstate++; }
1386 var str = StatusStrs[xstate];
1387 if (terminal.webRtcActive == true) { str += ", WebRTC"; }
1388 QH('termstatus', str);
1389 switch (state) {
1390 case 0:
1391 // Disconnected, clear the terminal
1392 QH('termtitle', '');
1393 QV('termRecordIcon', false);
1394 if (xterm == null) {
1395 xterminal.m.TermResetScreen();
1396 xterminal.m.TermDraw();
1397 } else {
1398 xterm.dispose();
1399 xterm = xtermfit = xtermimage = null;
1400 }
1401 if (terminal != null) { terminal.Stop(); terminal = null; }
1402 break;
1403 case 3:
1404 if (xterminal && (xterminal.serverIsRecording == true)) { QV('termRecordIcon', true); }
1405 terminal.startTime = new Date();
1406 if (updateSessionTimer == null) { updateSessionTimer = setInterval(updateSessionTime, 1000); }
1407 if (xterm != null) { xterm.focus(); }
1408 break;
1409 default:
1410 //console.log('Unhandled onTerminalStateChange state', state);
1411 break;
1412 }
1413 updateTerminalButtons();
1414 }
1415
1416 // DEBUG
1417 var autoConnectTerminalTimer = null;
1418 function autoConnectTerminal(e) { if (autoConnectTerminalTimer == null) { autoConnectTerminalTimer = setInterval(connectTerminal, 100); } else { clearInterval(autoConnectTerminalTimer); autoConnectTerminalTimer = null; } }
1419
1420 // Handles a tunnel to a remote shell
1421 function CreateRemoteTunnel(onTunnelUpdate, options) {
1422 var obj = { protocol: 1 };
1423 if ((options != null) && (typeof options.protocol == 'number')) { obj.protocol = options.protocol; }
1424 obj.onTunnelUpdate = onTunnelUpdate;
1425 obj.xxStateChange = function (state) { }
1426 obj.ProcessBinaryData = function (data) { obj.onTunnelUpdate(data); }
1427 obj.ProcessData = function (data) { obj.onTunnelUpdate(data); }
1428 obj.terminalEmulation = 1;
1429 obj.fxEmulation = 0;
1430 obj.lineFeed = '\r\n';
1431 return obj;
1432 }
1433
1434 function tunnelUpdate(data) {
1435 if (xterm.writeUtf8) {
1436 if (typeof data == 'string') { xterm.writeUtf8(data); } else { xterm.writeUtf8(new Uint8Array(data)); }
1437 } else {
1438 if (typeof data == 'string') { xterm.write(data); } else { xterm.write(new Uint8Array(data)); }
1439 }
1440 }
1441
1442 // Send the new terminal size to the agent
1443 function xTermSendResize() {
1444 xtermResizeTimer = null;
1445 if ((xterm != null) && (terminal != null) && (terminal.sendCtrlMsg != null)) { terminal.sendCtrlMsg(JSON.stringify({ ctrlChannel: '102938', type: 'termsize', cols: xterm.cols, rows: xterm.rows })); }
1446 }
1447
1448 // Used to translate incoming agent console messages
1449 var agentConsoleMessages = ['', "Waiting for user to grant access...", "Denied", "Failed to start remote terminal session, {0} ({1})", "Timeout", "Received invalid network data"];
1450 function formatAgentConsoleMessage(msg, msgid, msgargs) {
1451 var r;
1452 if (msgargs == null) { msgargs = []; }
1453 while (msgargs.length < 3) { msgargs.push(''); } // We need to call the format function in a way that works with older browsers and minifier, can't use apply() or ...
1454 if (msgid && (msgid < agentConsoleMessages.length)) { r = EscapeHtml(format(agentConsoleMessages[msgid], (msgargs[0]), (msgargs[1]), (msgargs[2]))); } else { r = EscapeHtml(msg); }
1455 return r.split('\n').join('<br />') + '<br /><br />';
1456 }
1457
1458 function connectTerminal(e, contype, options) {
1459 p12clearConsoleMsg();
1460 if (!terminal) {
1461 // Terminal setup
1462 var termoptions = { protocol: ((options != null) && (typeof options.protocol == 'number')) ? options.protocol : 1 };
1463 if (options && options.requireLogin) { termoptions.requireLogin = true; }
1464 /*
1465 if ([1, 2, 3, 4, 21, 22].indexOf(currentNode.agent.id) == -1) {
1466 if (Q('termSizeList').value == 1) { termoptions.cols = 80; termoptions.rows = 25; termoptions.xterm = true; }
1467 else if (Q('termSizeList').value == 2) { termoptions.cols = 100; termoptions.rows = 30; termoptions.xterm = true; }
1468 else if (Q('termSizeList').value == 3) {
1469 // TODO: Try to improve terminal auto-size.
1470 termoptions.cols = Math.floor((Q('column_l').clientWidth - 60) / 10);
1471 termoptions.rows = Math.floor((Q('column_l').clientHeight - 120) / 20);
1472 termoptions.xterm = true;
1473 }
1474 }
1475 */
1476
1477 /*
1478 // If shift is pressed
1479 if ((e && (e.shiftKey == true))) {
1480 if (currentNode.agent.id > 4) {
1481 if (termoptions.protocol == 1) { termoptions.protocol = 7; } // Switch to user shell
1482 } else {
1483 if (termoptions.protocol == 1) { termoptions.protocol = 6; } // Switch to Powershell
1484 }
1485 }
1486 */
1487
1488 // Setup a mesh agent xterm terminal
1489 QV('termarea3xdiv', true);
1490
1491 // Setup the terminal with auto-fit
1492 if (xterm != null) { xterm.dispose(); }
1493 xtermfit = new FitAddon.FitAddon();
1494 xtermimage = new ImageAddon.ImageAddon();
1495 xterm = new Terminal({ allowProposedApi: true });
1496 if (xtermfit) { xterm.loadAddon(xtermfit); }
1497 xterm.loadAddon(xtermimage);
1498 xterm.open(Q('termarea3xdiv')); // termarea3x
1499 xterm.onData(function (data) { if (terminal != null) { terminal.sendText(data); } })
1500 if (xtermfit) { xtermfit.fit(); }
1501 xterm.onTitleChange(function (title) { QH('termtitle', ' - ' + EscapeHtml(title)); });
1502 xterm.onResize(function (size) {
1503 // Despam resize
1504 if (xtermResizeTimer) clearTimeout(xtermResizeTimer);
1505 xtermResizeTimer = setTimeout(xTermSendResize, 200);
1506 });
1507
1508 // Setup a terminal tunnel to the agent
1509 terminal = CreateAgentRedirect(null, CreateRemoteTunnel(tunnelUpdate, options), serverPublicNamePort, authCookie, null, domainUrl);
1510 terminal.debugmode = debugmode;
1511 terminal.m.debugmode = debugmode;
1512 terminal.options = { cols: xterm.cols, rows: xterm.rows };
1513 if (options && options.requireLogin) { terminal.options.requireLogin = true; }
1514 terminal.Start(null);
1515 terminal.onStateChanged = onTerminalStateChange;
1516 terminal.contype = 1;
1517 terminal.attemptWebRTC = false; // Never do WebRTC on terminal, because of a race condition we can't do it.
1518 terminal.onConsoleMessageChange = function (server, msg) {
1519 if (terminal.consoleMessage) {
1520 Q('p12TermConsoleMsg').innerHTML += formatAgentConsoleMessage(terminal.consoleMessage, terminal.consoleMessageId, terminal.consoleMessageArgs);
1521 QV('p12TermConsoleMsg', true);
1522 if (p12TermConsoleMsgTimer != null) { clearTimeout(p12TermConsoleMsgTimer); }
1523 if (terminal.consoleMessageTimeout) { p12TermConsoleMsgTimer = setTimeout(p12clearConsoleMsg, terminal.consoleMessageTimeout * 1000); }
1524 } else {
1525 p12clearConsoleMsg();
1526 }
1527 };
1528 } else {
1529 terminal.Stop();
1530 terminal = null;
1531 }
1532 Q('connectbutton2').blur(); // Deselect the connect button so the button does not get key presses.
1533 }
1534
1535 var terminalEmulations = ["UTF8 Terminal", "Extended ASCII", "Intel ASCII"];
1536 function termToggleType() {
1537 if (!terminal || xxdialogMode) return;
1538 terminal.m.terminalEmulation = (terminal.m.terminalEmulation + 1) % 3;
1539 Q('id_ttypebutton').value = terminalEmulations[terminal.m.terminalEmulation];
1540 Q('id_ttypebutton').blur(); // Deselect the connect button so the button does not get key presses.
1541 }
1542
1543 var fxEmulations = ["Intel (F10 = ESC+[OM)", "Alternate (F10 = ESC+0)", "VT100+ (F10 = ESC+[OY)"];
1544 function termToggleFx() {
1545 if (!terminal || xxdialogMode) return;
1546 terminal.m.fxEmulation = (terminal.m.fxEmulation + 1) % 3;
1547 Q('id_tfxkeysbutton').value = fxEmulations[terminal.m.fxEmulation];
1548 Q('id_tfxkeysbutton').blur(); // Deselect the connect button so the button does not get key presses.
1549 }
1550
1551 function termToggleCr() {
1552 if (!terminal || xxdialogMode) return;
1553 if (terminal.m.lineFeed == '\n') { terminal.m.lineFeed = '\r\n'; } else { terminal.m.lineFeed = '\n'; }
1554 Q('id_tcrbutton').value = (terminal.m.lineFeed == '\r\n') ? "CR+LF" : "LF";
1555 }
1556
1557 function termSendKey(key, id) {
1558 if (!terminal || xxdialogMode) return;
1559 if (xterm != null) {
1560 if (terminal.sendText) {
1561 // MeshAgent
1562 terminal.sendText(String.fromCharCode(key));
1563 } else {
1564 // CIRA
1565 terminal.send(String.fromCharCode(key));
1566 }
1567 xterm.focus();
1568 } else if (terminal != null) {
1569 terminal.m.TermSendKey(key);
1570 Q(id).blur(); // Deselect the connect button so the button does not get key presses.
1571 }
1572 }
1573
1574 function showTermPasteDialog() {
1575 if (!terminal || xxdialogMode) return;
1576 Q('pastebutton').blur();
1577 setDialogMode(2, "Paste", 3, showTermPasteDialogEx, '<textarea id=d2pasteText style="width:100%;height:184px;resize:none"></textarea>');
1578 Q('d2pasteText').focus();
1579 }
1580
1581 function showTermPasteDialogEx() {
1582 if (!terminal) return;
1583 terminal.m.TermSendKeys(Q('d2pasteText').value);
1584 }
1585
1586 // Send special key
1587 function sendSpecialKey() {
1588 if (xterm != null) {
1589 terminal.sendText(String.fromCharCode(Q('specialkeylist').value));
1590 xterm.focus();
1591 } else if (terminal != null) {
1592 terminal.m.TermSendKey(Q('specialkeylist').value);
1593 Q('specialkeylist').blur();
1594 Q('specialkeylistinput').blur();
1595 }
1596 }
1597
1598 function p12clearConsoleMsg() { QH('p12TermConsoleMsg', ''); QV('p12TermConsoleMsg', false); if (p12TermConsoleMsgTimer) { clearTimeout(p12TermConsoleMsgTimer); p12TermConsoleMsgTimer = null; } }
1599
1600
1601
1602 //
1603 // Files
1604 //
1605
1606 function setupFiles() {
1607 // Setup the files tab
1608 if (files != null) { files.Stop(); files = null; }
1609 }
1610
1611 function onFilesStateChange(xfiles, state) {
1612 p13Connect.value = (state == 0) ? "Connect" : "Disconnect";
1613 var str = StatusStrs[state];
1614 if (files.webRtcActive == true) { str += ", WebRTC"; }
1615 Q('p13Status').textContent = str;
1616 switch (state) {
1617 case 0:
1618 // Disconnected, clear the files
1619 QH('p13files', '');
1620 p13filetree = null;
1621 p13filetreelocation = [];
1622 QH('p13currentpath', '');
1623 QE('p13FolderUp', false);
1624 QV('filesRecordIcon', false);
1625 p13setActions();
1626 if (files != null) { files.Stop(); files = null; }
1627 if (xxdialogTag == 'fileMsgDialog') { setDialogMode(0); }
1628 break;
1629 case 3:
1630 p13targetpath = '';
1631 if (files) {
1632 files.sendText({ action: 'ls', reqid: 1, path: '' });
1633 if (files.serverIsRecording == true) { QV('filesRecordIcon', true); }
1634 }
1635 break;
1636 default:
1637 //console.log('Unknown onFilesStateChange state', state);
1638 break;
1639 }
1640 }
1641
1642 function CreateRemoteFiles(onFileUpdate) {
1643 var obj = { protocol: 5 };
1644 obj.onFileUpdate = onFileUpdate;
1645 obj.xxStateChange = function (state) { }
1646 obj.ProcessData = function (data) { obj.onFileUpdate(data); }
1647 return obj;
1648 }
1649
1650 function connectFiles(e) {
1651 p13clearConsoleMsg();
1652 if (!files) {
1653 // Setup a mesh agent files
1654 files = CreateAgentRedirect(null, CreateRemoteFiles(p13gotFiles), serverPublicNamePort, authCookie, null, domainUrl);
1655 files.attemptWebRTC = attemptWebRTC;
1656 files.onStateChanged = onFilesStateChange;
1657 files.onConsoleMessageChange = function () {
1658 if (files.consoleMessage) {
1659 Q('p13FilesConsoleMsg').innerHTML += formatAgentConsoleMessage(files.consoleMessage, files.consoleMessageId, files.consoleMessageArgs);
1660 QV('p13FilesConsoleMsg', true);
1661 if (p13FilesConsoleMsgTimer != null) { clearTimeout(p13FilesConsoleMsgTimer); }
1662 if (files.consoleMessageTimeout) { p13FilesConsoleMsgTimer = setTimeout(p13clearConsoleMsg, files.consoleMessageTimeout * 1000); }
1663 } else {
1664 p13clearConsoleMsg();
1665 }
1666 }
1667 files.Start(null);
1668 } else {
1669 //QH('Term', '');
1670 files.Stop();
1671 files = null;
1672 }
1673 p13clipboard = p13clipboardFolder = null;
1674 p13clipboardCut = 0;
1675 p13updateClipview();
1676 }
1677
1678 var p13filetree = null;
1679 var p13targetpath = null;
1680 var p13filetreelocation = [];
1681
1682 function p13fileOperationDialogEx(b) { if ((b == 0) && (files != null)) { files.sendText({ action: 'cancel' }); } }
1683
1684 function p13gotFiles(data) {
1685 if ((data.length > 0) && (data.charCodeAt(0) != 123)) { p13gotDownloadBinaryData(data); return; } // This is ok because 4 first bytes is a control value.
1686 //console.log('p13gotFiles', data);
1687 try { data = JSON.parse(decode_utf8(data)); } catch (ex) { data = JSON.parse(data); }
1688 if (data.action == 'download') { p13gotDownloadCommand(data); return; }
1689
1690 // Find file result
1691 if (data.action == 'findfile') {
1692 if (xxdialogTag == data.reqid) {
1693 if (data.r == null) {
1694 QE('d2findFilter', true);
1695 QE('filefind_dlgOkButton', true);
1696 xxdialogTag = null;
1697 if (Q('d2findResults').innerHTML == '') { QH('d2findResults', '<div style=text-align:center;margin:10px><i>' + "No files found" + '</i></div>'); }
1698 } else {
1699 QA('d2findResults', '<div style=white-space:nowrap>' + EscapeHtml(data.r) + '</div>');
1700 }
1701 }
1702 return;
1703 }
1704
1705 // Process file upload commands
1706 if ((data.action != null) && (data.action.startsWith('upload'))) { p13gotUploadData(data); return; }
1707
1708 // Display a dialog message
1709 if (data.action == 'dialogmessage') {
1710 if ((data.msg == null) && (xxdialogTag == 'fileMsgDialog')) {
1711 setDialogMode(0); // Close the dialog box
1712 } else if ((data.msg == 'zipping') && ((!xxdialogMode) || (xxdialogTag == 'fileMsgDialog'))) {
1713 // Show the dialog box message
1714 setDialogMode(2, "File Operation", 10, p13fileOperationDialogEx, '<div style=margin:10px>' + "Compressing files..." + '<div>', 'fileMsgDialog');
1715 } else if ((data.msg == 'zippingFile') && ((!xxdialogMode) || (xxdialogTag == 'fileMsgDialog'))) {
1716 // Show the dialog box message
1717 setDialogMode(2, "File Operation", 10, p13fileOperationDialogEx, '<div style=margin:10px>' + EscapeHtml(data.file) + '<div><br /><progress value=' + EscapeHtml(data.progress) + ' style=width:100% max=100 />', 'fileMsgDialog');
1718 }
1719 return;
1720 }
1721
1722 // Refresh file folder
1723 if (data.action == 'refresh') { p13folderup(9999); return; }
1724
1725 if (data.path != null) {
1726 data.path = data.path.replace(/\//g, '\\');
1727 if ((p13filetree != null) && (data.path == p13filetree.path)) {
1728 // This is an update to the same folder
1729 var checkedNames = p13getCheckedNames();
1730 p13filetree = data;
1731 p13updateFiles(checkedNames);
1732 } else {
1733 // Make both paths use the same seperator not start with /
1734 var x1 = data.path.replace(/\//g, '\\'), x2 = p13targetpath.replace(/\//g, '\\');
1735 while ((x1.length > 0) && (x1[0] == '\\')) { x1 = x1.substring(1); }
1736 while ((x2.length > 0) && (x2[0] == '\\')) { x2 = x2.substring(1); }
1737 if ((x1 == x2) || ((data.path == '\\') && (p13targetpath == ''))) {
1738 // This is a different folder
1739 p13filetree = data;
1740 p13updateFiles();
1741 }
1742 }
1743 }
1744 }
1745
1746 function p13getCheckedNames() {
1747 // Save all existing checked boxes
1748 var checkedNames = [], checkboxes = document.getElementsByName('fd');
1749 for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { checkedNames.push(p13filetree.dir[checkboxes[i].value].n) }; }
1750 return checkedNames;
1751 }
1752
1753 function p13updateFiles(checkedNames) {
1754 var html1 = '', html2 = '', displayPath = '<a href=# style=cursor:pointer onclick="return p13folderup(0)">' + "Root" + '</a>', fullPath = 'Root';
1755
1756 // Work on parsing the file path
1757 var x = p13filetree.path.split('\\');
1758 p13filetreelocation = [];
1759 for (var i in x) { if (x[i] != '') { p13filetreelocation.push(x[i]); } } // Remove empty spaces
1760 for (var i in p13filetreelocation) { displayPath += ' / <a href=# style=cursor:pointer onclick="return p13folderup(' + (parseInt(i) + 1) + ')">' + EscapeHtml(p13filetreelocation[i]) + '</a>' } // Setup the path we display
1761 var newlinkpath = p13filetreelocation.join('/');
1762
1763 // Sort the files
1764 var filetreexx = p13sort_files(p13filetree.dir);
1765
1766 // Display all files and folders at this location
1767 for (var i in filetreexx) {
1768 // Figure out the name and shortname
1769 var f = filetreexx[i], name = f.n, shortname;
1770 // shortname = name;
1771 // if (name.length > 70) { shortname = '<span title="' + EscapeHtml(name) + '">' + EscapeHtml(name.substring(0, 70)) + ("..." + '</span>'); } else { shortname = EscapeHtml(name); }
1772 // Removed redundant filename length check because we handle it in the CSS
1773 shortname = EscapeHtml(name);
1774
1775 // Figure out the date
1776 var fdatestr = '';
1777 if (f.d != null) {
1778 var fdate = new Date(f.d);
1779 if (typeof f.d == 'number') { fdate = new Date(f.d * 1000); }
1780 var fdatestr = printDateTime(fdate) + '&nbsp;';
1781 }
1782
1783 // Figure out the size
1784 var fsize = '';
1785 if (f.s != null) { fsize = getFileSizeStr(f.s); }
1786
1787 var h = '';
1788 if (f.t < 3) {
1789 var right = '', title = '';
1790 h = '<div class=filelist file=999><input file=999 style=float:left name=fd class=fcb type=checkbox onchange=p13setActions() value=\'' + f.nx + '\'>&nbsp;<span style=float:right title="' + title + '">' + right + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + ' onclick=p13folderset("' + encodeURIComponentEx(f.nx) + '")></div><a href=# style=cursor:pointer onclick=\'return p13folderset("' + encodeURIComponentEx(f.nx) + '")\'>' + shortname + '</a></span></div>';
1791 } else {
1792 var link = shortname;
1793 if (f.s > 0) {
1794 // Local link
1795 //link = '<a href=# style=cursor:pointer onclick="return p13downloadfile(\'' + encodeURIComponentEx(newlinkpath + '/' + name) + '\',\'' + encodeURIComponentEx(name) + '\',' + f.s + ')">' + shortname + '</a>';
1796 // Server link
1797 //link = '<a href="devicefile.ashx?c=' + authCookie + '&m=' + currentNode.meshid.split('/')[2] + '&n=' + currentNode._id.split('/')[2] + '&f=' + encodeURIComponentEx(newlinkpath + '/' + name) + '" download="' + name + '" style=cursor:pointer>' + shortname + '</a>';
1798 // Server link
1799 //link = '<a onclick=downloadFile("devicefile.ashx?c=' + authCookie + '&m=' + currentNode.meshid.split('/')[2] + '&n=' + currentNode._id.split('/')[2] + '&f=' + encodeURIComponentEx(newlinkpath + '/' + name) + '","' + encodeURIComponentEx(name) + '") style=cursor:pointer>' + shortname + '</a>';
1800 link = '<a onclick=downloadFile("devicefile.ashx?c=' + authCookie + '&f=' + encodeURIComponentEx(newlinkpath + '/' + name) + '","' + encodeURIComponentEx(name) + '") style=cursor:pointer>' + shortname + '</a>';
1801 }
1802 h = '<div id=fileEntry cmenu=filesContextMenu fileIndex=' + i + ' class=filelist file=3><input file=3 style=float:left name=fd class=fcb type=checkbox onchange=p13setActions() value=\'' + f.nx + '\'>&nbsp;<span class=fsize>' + fdatestr + '</span><span style=float:right>' + EscapeHtml(fsize) + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + '></div>' + link + '</span></div>';
1803 }
1804
1805 if (f.t < 3) { html1 += h; } else { html2 += h; }
1806 }
1807
1808 // Display the files and path
1809 QH('p13files', html1 + html2);
1810 QH('p13currentpath', displayPath);
1811 QE('p13FolderUp', p13filetreelocation.length != 0);
1812
1813 // Re-check all boxes if needed using names
1814 if (checkedNames != null) { var checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { if (checkedNames.indexOf(p13filetree.dir[checkboxes[i].value].n) >= 0) { checkboxes[i].checked = true; } } }
1815
1816 // Update the actions buttons
1817 p13setActions();
1818 }
1819
1820 function p13folderset(x) {
1821 p13targetpath = joinPaths(p13filetree.path, p13filetree.dir[x].n).split('\\').join('/');
1822 files.sendText({ action: 'ls', reqid: 1, path: p13targetpath });
1823 }
1824
1825 function p13folderup(x) {
1826 if (x == null) { p13filetreelocation.pop(); } else { while (p13filetreelocation.length > x) { p13filetreelocation.pop(); } }
1827 p13targetpath = p13filetreelocation.join('/');
1828 files.sendText({ action: 'ls', reqid: 1, path: p13targetpath });
1829 return false;
1830 }
1831
1832 function p13findfile() {
1833 if (xxdialogMode) return;
1834 var x = addHtmlValue("Filter", '<input id=d2findFilter style="width:230px" onkeyup=p13findfileValidate() onkeydown=p13findfileDown(event) placeholder="*.txt"></input>');
1835 x += '<div id=d2findResults style="width:100%;height:184px;background-color:white;border:1px solid black;padding:3px;overflow:scroll;margin-top:8px"></div>';
1836 x += '<div style=margin-top:8px><input id=filefind_dlgCancelButton type=button value=' + "Close" + ' style=float:right;width:80px;margin-left:5px onclick=p13findfileCancel()>';
1837 x += '<input id=filefind_dlgOkButton type=submit value=' + "Search" + ' style=float:right;width:80px onclick=p13findfileEx()><br /><br /></div>';
1838 setDialogMode(2, "Find Files", 0, null, x);
1839 p13findfileValidate();
1840 Q('d2findFilter').focus();
1841 }
1842
1843 function p13findfileDown(e) { if (e.code == "Enter") { p13findfileEx(); } }
1844 function p13findfileValidate() { QE('filefind_dlgOkButton', Q('d2findFilter').value.length > 0); }
1845 function p13findfileCancel() { if (xxdialogTag != null) { files.sendText({ action: 'cancelfindfile', reqid: xxdialogTag }); } dialogclose(0) }
1846
1847 function p13findfileEx(b, t) {
1848 if (Q('d2findFilter').value.length == 0) return;
1849 var winagent = true;//((currentNode.agent.id > 0) && (currentNode.agent.id < 5));
1850 var slash = winagent ? '\\' : '/';
1851 var path = p13filetreelocation.join(slash) + slash;
1852 if (!winagent) { path = slash + path; }
1853 xxdialogTag = 'find:' + Math.random();
1854 files.sendText({ action: 'findfile', reqid: xxdialogTag, path: path, filter: Q('d2findFilter').value });
1855 QH('d2findResults', '');
1856 QE('d2findFilter', false);
1857 QE('filefind_dlgOkButton', false);
1858 }
1859
1860 var p13sortorder;
1861 function p13sort_filename(a, b) { if (a.ln > b.ln) return (1 * p13sortorder); if (a.ln < b.ln) return (-1 * p13sortorder); return 0; }
1862 function p13sort_timestamp(a, b) { if (a.d > b.d) return (1 * p13sortorder); if (a.d < b.d) return (-1 * p13sortorder); return 0; }
1863 function p13sort_bysize(a, b) { if (a.s == b.s) return p13sort_filename(a, b); return (((a.s - b.s)) * p13sortorder); }
1864
1865 function p13sort_files(files) {
1866 var r = [], sortselection = Q('p13sortdropdown').value;
1867 for (var i in files) { files[i].nx = i; if (files[i].s == null) { files[i].s = 0; } if (files[i].n == null) { files[i].n = i; } files[i].ln = files[i].n.toLowerCase(); r.push(files[i]); }
1868 p13sortorder = 1;
1869 if (sortselection > 3) { p13sortorder = -1; sortselection -= 3; }
1870 if (sortselection == 1) { r.sort(p13sort_filename); }
1871 else if (sortselection == 2) { r.sort(p13sort_bysize); }
1872 else if (sortselection == 3) { r.sort(p13sort_timestamp); }
1873 return r;
1874 }
1875
1876 function p13setActions() {
1877 var advancedFeatures = true; //(currentNode.agent.id != 14); // Reduct file feature on some devices.
1878 if (p13filetree == null) {
1879 QE('p13DeleteFileButton', false);
1880 QE('p13NewFolderButton', false);
1881 QE('p13UploadButton', false);
1882 QE('p13RenameFileButton', false);
1883 QE('p13ViewFileButton', false);
1884 QE('p13SelectAllButton', false);
1885 Q('p13SelectAllButton').value = "Select All";
1886 QE('p13RefreshButton', false);
1887 QE('p13FindButton', false);
1888 QE('p13CutButton', false);
1889 QE('p13CopyButton', false);
1890 QE('p13ZipButton', false);
1891 QE('p13PasteButton', false);
1892 } else {
1893 var cc = p13getFileSelCount(), tc = p13getFileCount(), sfc = p13getFileSelCount(false); // In order: number of entires selected, number of total entries, number of selected entires that are files (not folders)
1894 var winAgent = true; //((currentNode.agent.id > 0) && (currentNode.agent.id < 5)) || (currentNode.agent.id == 14);
1895 QE('p13DeleteFileButton', (cc > 0) && ((p13filetreelocation.length > 0) || (winAgent == false)));
1896 QE('p13NewFolderButton', advancedFeatures && ((p13filetreelocation.length > 0) || (winAgent == false)));
1897 QE('p13UploadButton', ((p13filetreelocation.length > 0) || (winAgent == false) /*|| (currentNode.agent.id == 14)*/));
1898 QE('p13RenameFileButton', advancedFeatures && (cc == 1) && ((p13filetreelocation.length > 0) || (winAgent == false)));
1899 QE('p13ViewFileButton', advancedFeatures && (cc == 1) && (sfc == 1) && ((p13filetreelocation.length > 0) || (winAgent == false)));
1900 QE('p13SelectAllButton', tc > 0);
1901 Q('p13SelectAllButton').value = (cc > 0 ? "Select None" : "Select All");
1902 QE('p13RefreshButton', true);
1903 QE('p13FindButton', advancedFeatures && ((p13filetreelocation.length > 0) || (winAgent == false)));
1904 QE('p13CutButton', advancedFeatures && (cc > 0) && (cc == sfc) && ((p13filetreelocation.length > 0) || (winAgent == false)));
1905 QE('p13CopyButton', advancedFeatures && (cc > 0) && (cc == sfc) && ((p13filetreelocation.length > 0) || (winAgent == false)));
1906 QE('p13ZipButton', advancedFeatures && (cc > 0) && ((p13filetreelocation.length > 0) || (winAgent == false)));
1907 QE('p13PasteButton', advancedFeatures && ((p13filetreelocation.length > 0) || (winAgent == false)) && ((p13clipboard != null) && (p13clipboard.length > 0)));
1908 }
1909 }
1910
1911 function p13getFileSelCount(includeDirs) { var cc = 0; var checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && ((includeDirs != false) || (checkboxes[i].attributes.file.value == '3'))) cc++; } return cc; }
1912 function p13getFileSelDirCount() { var cc = 0, checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '999')) cc++; } return cc; }
1913 function p13getFileCount() { var cc = 0; var checkboxes = document.getElementsByName('fd'); return checkboxes.length; }
1914 function p13selectallfile() { var nv = (p13getFileSelCount() == 0), checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { checkboxes[i].checked = nv; } p13setActions(); }
1915 function p13createfolder() { setDialogMode(2, "New Folder", 3, p13createfolderEx, '<input type=text id=p13renameinput maxlength=64 onkeyup=p13fileNameCheck(event) style=width:100% />'); focusTextBox('p13renameinput'); p13fileNameCheck(); }
1916 function p13createfolderEx() { files.sendText({ action: 'mkdir', reqid: 1, path: p13filetreelocation.join('/') + '/' + Q('p13renameinput').value }); p13folderup(999); }
1917 function p13deletefile() { var cc = p13getFileSelCount(), rec = (p13getFileSelDirCount() > 0) ? '<br /><br /><label><input type=checkbox id=p13recdeleteinput>' + "Recursive delete" + '</label><br>' : '<input type=checkbox id=p13recdeleteinput style=\'display:none\'>'; setDialogMode(2, "Delete", 3, p13deletefileEx, (cc > 1) ? (format("Delete {0} selected items?", cc) + rec) : ("Delete selected item?" + rec)); }
1918 function p13deletefileEx() { var delfiles = [], checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { delfiles.push(p13filetree.dir[checkboxes[i].value].n); } } files.sendText({ action: 'rm', reqid: 1, path: p13filetreelocation.join('/'), delfiles: delfiles, rec: Q('p13recdeleteinput').checked }); p13folderup(999); }
1919 function p13renamefile() { var renamefile, checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { renamefile = p13filetree.dir[checkboxes[i].value].n; } } setDialogMode(2, "Rename", 3, p13renamefileEx, '<input type=text id=p13renameinput maxlength=64 onkeyup=p13fileNameCheck(event) style=width:100% value="' + renamefile + '" />', { action: 'rename', path: p13filetreelocation.join('/'), oldname: renamefile }); focusTextBox('p13renameinput'); p13fileNameCheck(); }
1920 function p13renamefileEx(b, t) { t.newname = Q('p13renameinput').value; files.sendText(t); p13folderup(999); }
1921 function p13fileNameCheck(e) { var x = isFilenameValid(Q('p13renameinput').value); QE('idx_dlgOkButton', x); if ((x == true) && (e != null) && (e.keyCode == 13)) { dialogclose(1); } }
1922 function p13uploadFile() { setDialogMode(2, "Upload File", 3, p13uploadFileEx, '<input type=file name=files id=p13uploadinput style=width:100% multiple=multiple onchange="updateUploadDialogOk(\'p13uploadinput\')" />'); updateUploadDialogOk('p13uploadinput'); }
1923 function updateUploadDialogOk(x) { QE('idx_dlgOkButton', Q('p13uploadinput').files.length > 0); }
1924 function p13uploadFileEx() { p13doUploadFiles(Q('p13uploadinput').files); }
1925 function p13viewfile() {
1926 var checkboxes = document.getElementsByName('fd');
1927 for (var i = 0; i < checkboxes.length; i++) {
1928 if (checkboxes[i].checked) {
1929 if (p13filetree.dir[checkboxes[i].value].s <= 204800) {
1930 p13downloadfile(encodeURIComponentEx(p13filetreelocation.join('/') + '/' + p13filetree.dir[checkboxes[i].value].n), encodeURIComponentEx(p13filetree.dir[checkboxes[i].value].n), p13filetree.dir[checkboxes[i].value].s, 'viewer');
1931 } else { messagebox("File Editor", "Only files less than 200k can be edited."); }
1932 break;
1933 }
1934 }
1935 }
1936
1937 function p13zipFiles() {
1938 var inputFiles = [], checkboxes = document.getElementsByName('fd');
1939 for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { inputFiles.push(p13filetree.dir[checkboxes[i].value].n); } }
1940 setDialogMode(2, "Zip Filename", 3, p13zipFilesEx, '<input type=text id=p13renameinput maxlength=64 onkeyup=p13fileNameCheck(event) style=width:100% value="" />', { action: 'zip', path: p13filetreelocation.join('/'), files: inputFiles });
1941 focusTextBox('p13renameinput');
1942 p13fileNameCheck();
1943 }
1944
1945 function p13zipFilesEx(b, tag) {
1946 tag.output = Q('p13renameinput').value;
1947 if (!tag.output.toLowerCase().endsWith('.zip')) { tag.output += '.zip'; }
1948 files.sendText(tag);
1949 }
1950
1951 var p13clipboard = null, p13clipboardFolder = null, p13clipboardCut = 0;
1952 function p13copyFile(cut) { var checkboxes = document.getElementsByName('fd'); p13clipboard = []; p13clipboardCut = cut, p13clipboardFolder = p13targetpath; for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '3')) { p13clipboard.push(p13filetree.dir[checkboxes[i].value].n); } } p13updateClipview(); }
1953 function p13pasteFile() {
1954 var x = '';
1955 if ((p13clipboard != null) && (p13clipboard.length > 0)) {
1956 if (p13clipboardCut == 0) {
1957 if (p13clipboard.length > 1) { x = format("Confirm copy of {0} entries to this location?", p13clipboard.length); } else { x = format("Confirm copy of 1 entrie to this location?"); }
1958 } else {
1959 if (p13clipboard.length > 1) { x = format("Confirm move of {0} entries to this location?", p13clipboard.length); } else { x = format("Confirm move of 1 entrie to this location?"); }
1960 }
1961 }
1962 setDialogMode(2, "Paste", 3, p13pasteFileEx, x);
1963 }
1964 function p13pasteFileEx() { files.sendText({ action: (p13clipboardCut == 0 ? 'copy' : 'move'), reqid: 1, scpath: p13clipboardFolder, dspath: p13targetpath, names: p13clipboard }); p13folderup(999); if (p13clipboardCut == 1) { p13clipboard = null, p13clipboardFolder = null, p13clipboardCut = 0; p13updateClipview(); } }
1965 function p13updateClipview() {
1966 var x = '';
1967 if ((p13clipboard != null) && (p13clipboard.length > 0)) {
1968 if (p13clipboardCut == 0) {
1969 if (p13clipboard.length > 1) {
1970 x = format("Holding {0} entries for copy" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.', p13clipboard.length);
1971 } else {
1972 x = format("Holding 1 entrie for copy" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.');
1973 }
1974 } else {
1975 if (p13clipboard.length > 1) {
1976 x = format("Holding {0} entries for move" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.', p13clipboard.length);
1977 } else {
1978 x = format("Holding 1 entrie for move" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.');
1979 }
1980 }
1981 }
1982 QH('p13bottomstatus', x);
1983 p13setActions();
1984 }
1985 function p13clearClip() { p13clipboard = null; p13clipboardFolder = null; p13clipboardCut = 0; p13updateClipview(); return false; }
1986
1987 function p13fileDragDrop(e) {
1988 haltEvent(e);
1989 QV('p13bigfail', false);
1990 QV('p13bigok', false);
1991 if ((e.dataTransfer == null) || (e.dataTransfer.files.length == 0) || (p13filetree == null)) return;
1992
1993 // Check if these are files we can upload, remove all folders.
1994 var files = [];
1995 for (var i in e.dataTransfer.files) { if ((e.dataTransfer.files[i].size != null) && (e.dataTransfer.files[i].size != 0)) { files.push(e.dataTransfer.files[i]); } }
1996 if (files.length == 0) return;
1997
1998 p13doUploadFiles(files);
1999 }
2000
2001 var p13dragtimer = null;
2002 function p13fileDragOver(e) {
2003 haltEvent(e);
2004 if (p13dragtimer != null) { clearTimeout(p13dragtimer); p13dragtimer = null; }
2005 var ac = (p13filetree != null); // Set to true if we can accept the file
2006 QV('p13bigok', ac);
2007 QV('p13bigfail', !ac);
2008 }
2009
2010 function p13fileDragLeave(e) {
2011 haltEvent(e);
2012 if (e.target.id != 'p13filetable') {
2013 QV('p13bigfail', false);
2014 QV('p13bigok', false);
2015 } else {
2016 p13dragtimer = setTimeout(function () { QV('p13bigfail', false); QV('p13bigok', false); p13dragtimer = null; }, 10);
2017 }
2018 }
2019
2020 //
2021 // FILES DOWNLOAD
2022 //
2023
2024 var gdownloadFile; // Global state for file download
2025
2026 // Called by the html page to start a download, arguments are: path, file name and file size.
2027 function p13downloadfile(x, y, z, tag) {
2028 if (xxdialogMode || !files) return;
2029 gdownloadFile = { path: decodeURIComponent(x), file: decodeURIComponent(y), size: z, tsize: 0, data: '', state: 0, id: Math.random(), tag: tag }
2030 //console.log('p13downloadFileCancel', gdownloadFile);
2031 files.sendText({ action: 'download', sub: 'start', id: gdownloadFile.id, path: gdownloadFile.path });
2032 setDialogMode(2, "Download File", 10, p13downloadFileCancel, '<div>' + gdownloadFile.file + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=' + z + ' />');
2033 }
2034
2035 // Called by the html page to cancel the download
2036 function p13downloadFileCancel() { setDialogMode(0); files.sendText({ action: 'download', sub: 'cancel', id: gdownloadFile.id }); gdownloadFile = null; }
2037
2038 // Called by the transport when download control command is received
2039 function p13gotDownloadCommand(cmd) {
2040 //console.log('p13gotDownloadCommand', cmd);
2041 if ((gdownloadFile == null) || (cmd.id != gdownloadFile.id)) return;
2042 if (cmd.sub == 'start') { gdownloadFile.state = 1; files.sendText({ action: 'download', sub: 'startack', id: gdownloadFile.id }); }
2043 else if (cmd.sub == 'cancel') { gdownloadFile = null; setDialogMode(0); }
2044 }
2045
2046 // Called by the transport when binary data is received
2047 function p13gotDownloadBinaryData(data) {
2048 if (!gdownloadFile || gdownloadFile.state == 0) return;
2049 if (data.length > 4) {
2050 gdownloadFile.tsize += (data.length - 4); // Add to the total bytes received
2051 gdownloadFile.data += data.substring(4); // Append the data
2052 Q('d2progressBar').value = gdownloadFile.tsize; // Change the progress bar
2053 }
2054 if ((ReadInt(data, 0) & 1) != 0) { // Check end flag
2055 if (gdownloadFile.tag == 'viewer') {
2056 // View the file in the dialog box
2057 setDialogMode(4, EscapeHtml(gdownloadFile.file), 3, p13editSaveBack, null, gdownloadFile.file);
2058 QV('d4EncodingButton', true);
2059 QV('d4LineBreakButton', true);
2060 QS('dialog').width = 'auto';
2061 QS('dialog').bottom = '80px';
2062 QS('dialog').top = QS('dialog').left = QS('dialog').right = '100px';
2063 Q('d4editorarea').value = gdownloadFile.data;
2064 gdownloadFile = null;
2065 } else {
2066 // Save the file to disk
2067 saveAs(data2blob(gdownloadFile.data), gdownloadFile.file); gdownloadFile = null; setDialogMode(0); // Save the file
2068 }
2069 } else {
2070 files.sendText({ action: 'download', sub: 'ack', id: gdownloadFile.id }); // Send the ACK
2071 }
2072 }
2073
2074 var d4EditWrapVal = 0;
2075 var d4EditSizeVal = 0;
2076 var d4EditEncodingVal = 0;
2077 var d4EditLineBreakVal = 0;
2078
2079 function d4ToggleWrap(update) {
2080 if (!update) { d4EditWrapVal = ++d4EditWrapVal % 2; }
2081 Q('d4WrapButton').value = ["Wrap: ON", "Wrap: OFF"][d4EditWrapVal];
2082 QS('d4editorarea').overflow = (d4EditWrapVal == 0) ? 'auto' : 'scroll';
2083 QS('d4editorarea')['white-space'] = (d4EditWrapVal == 0) ? null : 'pre';
2084 putstore('editorWrap', d4EditWrapVal);
2085 }
2086
2087 function d4ToggleSize(update) {
2088 if (!update) { d4EditSizeVal = ++d4EditSizeVal % 4; }
2089 QS('d4editorarea')['font-size'] = ['100%', '125%', '150%', '200%'][d4EditSizeVal];
2090 Q('d4SizeButton').value = ["Size: 100%", "Size: 125%", "Size: 150%", "Size: 200%"][d4EditSizeVal];
2091 putstore('editorSize', d4EditSizeVal);
2092 }
2093
2094 function d4ToggleEncoding(update) {
2095 if (!update) {
2096 d4EditEncodingVal = ++d4EditEncodingVal % 2;
2097 if (d4EditEncodingVal == 1) {
2098 Q('d4editorarea').value = decode_utf8(Q('d4editorarea').value);
2099 } else {
2100 Q('d4editorarea').value = encode_utf8(Q('d4editorarea').value);
2101 }
2102 }
2103 Q('d4EncodingButton').value = ["Encoding: RAW","Encoding: UTF8"][d4EditEncodingVal];
2104 putstore('editorEncoding', d4EditEncodingVal);
2105 }
2106
2107 function d4ToggleLineBreak(update) {
2108 if (!update) {
2109 d4EditLineBreakVal = ++d4EditLineBreakVal % 3;
2110 }
2111 Q('d4LineBreakButton').value = ["Line Break: Windows (CR LF)","Line Break: Linux (LF)","Line Break: Mac (CR)"][d4EditLineBreakVal];
2112 putstore('editorLineBreak', d4EditLineBreakVal);
2113 }
2114
2115 function p13editSaveBack(b, tag) {
2116 var data;
2117 var value = Q('d4editorarea').value;
2118 value = d4EditLineBreakVal === 0 ? value.replace(/\r?\n|\r/g, '\r\n') : // Windows
2119 d4EditLineBreakVal === 2 ? value.replace(/\r\n|\n/g, '\r') : // Mac
2120 value.replace(/\r\n|\r/g, '\n'); // Linux
2121 if (d4EditEncodingVal == 1) {
2122 data = new TextEncoder().encode(value); // UTF8 encoding
2123 } else {
2124 data = new TextEncoder().encode(decode_utf8(value)); // RAW encoding
2125 }
2126 p13uploadFileContinue(1, [{ name: tag, size: data.byteLength, type: 'text/plain', xdata: data }]);
2127 }
2128
2129 //
2130 // FILES UPLOAD
2131 //
2132
2133 var uploadFile;
2134 function p13doUploadFiles(files) {
2135 if (xxdialogMode) return;
2136
2137 // Check if we are going to overwrite any files
2138 var winAgent = true;//((currentNode.agent.id > 0) && (currentNode.agent.id < 5));
2139 var targetFiles = [], overWriteCount = 0;
2140 for (var i in p13filetree.dir) { if (winAgent) { targetFiles.push(p13filetree.dir[i].n.toLowerCase()); } else { targetFiles.push(p13filetree.dir[i].n); } }
2141 for (var i = 0; i < files.length; i++) {
2142 if (winAgent) {
2143 if (targetFiles.indexOf(files[i].name.toLowerCase()) >= 0) { overWriteCount++; }
2144 } else {
2145 if (targetFiles.indexOf(files[i].name) >= 0) { overWriteCount++; }
2146 }
2147 }
2148
2149 if (overWriteCount == 0) {
2150 // If no overwrite, go ahead with upload
2151 p13uploadFileContinue(1, files);
2152 } else {
2153 // Otherwise, prompt for confirmation
2154 setDialogMode(2, "Upload File", 3, p13uploadFileContinue, format((overWriteCount == 1) ? "Upload will overwrite 1 file. Continue?" : "Upload will overwrite {0} files. Continue?", overWriteCount), files);
2155 }
2156 }
2157
2158 function p13uploadFileContinue(b, files) {
2159 uploadFile = {};
2160 uploadFile.xpath = p13filetreelocation.join('/');
2161 uploadFile.xfiles = files;
2162 uploadFile.xfilePtr = -1;
2163 setDialogMode(2, "Upload File", 10, p13uploadFileCancel, '<div id=p13dfileName>' + "Connecting..." + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=0 />');
2164 p13uploadNextFile();
2165 }
2166
2167 // Perform SHA-384 hashing
2168 const byteToHex = [];
2169 for (var n = 0; n <= 0xff; ++n) { var hexOctet = n.toString(16).padStart(2, '0'); byteToHex.push(hexOctet); }
2170 function arrayBufferToHex(arrayBuffer) { return Array.prototype.map.call(new Uint8Array(arrayBuffer), n => byteToHex[n]).join(''); }
2171 function performHash(data, f) { window.crypto.subtle.digest('SHA-384', data).then(function (v) { f(arrayBufferToHex(v)); }, function () { f(null); }); }
2172
2173 // Push the next file
2174 function p13uploadNextFile() {
2175 uploadFile.xfilePtr++;
2176 if (uploadFile.xfiles.length > uploadFile.xfilePtr) {
2177 uploadFile.xptr = 0;
2178 var file = uploadFile.xfiles[uploadFile.xfilePtr];
2179 QH('p13dfileName', file.name);
2180 Q('d2progressBar').max = file.size;
2181 Q('d2progressBar').value = 0;
2182 if (file.xdata == null) {
2183 // Load the data
2184 uploadFile.xreader = new FileReader();
2185 uploadFile.xreader.onload = function () {
2186 uploadFile.xdata = uploadFile.xreader.result;
2187
2188 // If the remote file already exists and is smaller then our file, see if we can resume the trasfer
2189 var f = null;
2190 for (var i in p13filetree.dir) { if (p13filetree.dir[i].n == file.name) { f = p13filetree.dir[i]; } }
2191 if ((f != null) && (f.s <= uploadFile.xreader.result.byteLength)) {
2192 performHash(uploadFile.xreader.result.slice(0, f.s), function (hash) {
2193 files.sendText(JSON.stringify({ action: 'uploadhash', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, tag: { h: hash.toUpperCase(), s: f.s, skip: f.s == uploadFile.xreader.result.byteLength } }));
2194 });
2195 } else {
2196 files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength }));
2197 }
2198 };
2199 uploadFile.xreader.readAsArrayBuffer(file);
2200 } else {
2201 // Data already loaded
2202 uploadFile.xdata = file.xdata;
2203 files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength }));
2204 }
2205 } else {
2206 p13uploadFileTransferDone();
2207 }
2208 }
2209
2210 // Used to cancel the entire transfer.
2211 function p13uploadFileCancel(button, tag) {
2212 if (uploadFile != null) { files.sendText(JSON.stringify({ action: 'uploadcancel', reqid: uploadFile.xfilePtr })); uploadFile = null; }
2213 p13uploadFileTransferDone();
2214 }
2215
2216 // Used to cancel the entire transfer.
2217 function p13uploadFileTransferDone() {
2218 uploadFile = null; // No more files to upload, clean up.
2219 setDialogMode(0); // Close the dialog box
2220 p13folderup(9999); // Refresh the current folder
2221 }
2222
2223 // Receive upload ack from the mesh agent, use this to keep sending more data
2224 function p13gotUploadData(cmd) {
2225 if ((uploadFile == null) || (parseInt(uploadFile.xfilePtr) != parseInt(cmd.reqid))) { return; }
2226 switch (cmd.action) {
2227 case 'uploadstart': { p13uploadNextPart(false); for (var i = 0; i < 8; i++) { p13uploadNextPart(true); } break; } // Send 8 more blocks of 16k to fill the websocket.
2228 case 'uploadack': { p13uploadNextPart(false); break; }
2229 case 'uploaddone': { if (uploadFile.xfiles.length > uploadFile.xfilePtr + 1) { p13uploadNextFile(); } else { p13uploadFileTransferDone(); } break; }
2230 case 'uploaderror': { p13uploadFileCancel(); break; }
2231 case 'uploadhash': {
2232 var file = uploadFile.xfiles[uploadFile.xfilePtr];
2233 if (file) {
2234 if (cmd.tag.h === cmd.hash) {
2235 if (cmd.tag.skip) {
2236 p13uploadNextFile();
2237 } else {
2238 uploadFile.xptr = cmd.tag.s;
2239 files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength, append: true }));
2240 }
2241 } else {
2242 files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength, append: false }));
2243 }
2244 }
2245 break;
2246 }
2247 }
2248 }
2249
2250 // Push the next part of the file into the websocket. If dataPriming is true, push more data only if it's not the last block of the file.
2251 function p13uploadNextPart(dataPriming) {
2252 var data = uploadFile.xdata, start = uploadFile.xptr;
2253 if (start >= data.byteLength) {
2254 files.sendText(JSON.stringify({ action: 'uploaddone', reqid: uploadFile.xfilePtr }));
2255 } else {
2256 var end = uploadFile.xptr + 65536;
2257 if (end > data.byteLength) { if (dataPriming == true) { return; } end = data.byteLength; }
2258 var dataslice = new Uint8Array(data.slice(start, end))
2259 if ((dataslice[0] == 123) || (dataslice[0] == 0)) {
2260 var datapart = new Uint8Array(end - start + 1);
2261 datapart.set(dataslice, 1); // Add a zero char at the start of the send, this will indicate that it's not a JSON command.
2262 files.send(datapart);
2263 } else {
2264 files.send(dataslice); // The data does not start with 0 or 123 "{" so it can't be confused for JSON.
2265 }
2266 uploadFile.xptr = end;
2267 Q('d2progressBar').value = end;
2268 }
2269 }
2270
2271
2272
2273 //
2274 // POPUP DIALOG
2275 //
2276
2277 // null = Hidden, 1 = Generic Message
2278 var xxdialogMode;
2279 var xxdialogFunc;
2280 var xxdialogButtons;
2281 var xxdialogTag;
2282 var xxcurrentView = -1;
2283
2284 // Display a dialog box
2285 // Parameters: Dialog Mode (0 = none), Dialog Title, Buttons (1 = OK, 2 = Cancel, 3 = OK & Cancel), Call back function(0 = Cancel, 1 = OK), Dialog Content (Mode 2 only)
2286 function setDialogMode(x, y, b, f, c, tag) {
2287 xxdialogMode = x;
2288 xxdialogFunc = f;
2289 xxdialogButtons = b;
2290 xxdialogTag = tag;
2291 QE('idx_dlgOkButton', true);
2292 QV('idx_dlgOkButton', b & 1);
2293 QV('idx_dlgCancelButton', b & 2);
2294 QV('id_dialogclose', (b & 2) || (b & 8));
2295 QV('idx_dlgDeleteButton', b & 4);
2296 QV('idx_dlgButtonBar', b & 7);
2297 if (y) QH('id_dialogtitle', y);
2298 for (var i = 1; i < 8; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
2299 QV('dialog', x);
2300 if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
2301 }
2302
2303 function dialogclose(x) {
2304 var f = xxdialogFunc, b = xxdialogButtons, t = xxdialogTag;
2305 setDialogMode();
2306 if (((b & 8) || x) && f) f(x, t);
2307 }
2308
2309 function go(x) {
2310 if (xxdialogMode) return;
2311 QV('p11', x == 11);
2312 QV('p12', x == 12);
2313 QV('p13', x == 13);
2314
2315 var leftBarItems = ['LeftMenuDesktop', 'LeftMenuTerminal', 'LeftMenuFiles'];
2316 for (var i in leftBarItems) { Q(leftBarItems[i]).classList.remove('slbbuttonsel'); Q(leftBarItems[i]).classList.remove('slbbuttonsel2'); }
2317 if (x == 11) { Q('LeftMenuDesktop').classList.add('slbbuttonsel2'); }
2318 if (x == 12) { Q('LeftMenuTerminal').classList.add('slbbuttonsel2'); }
2319 if (x == 13) { Q('LeftMenuFiles').classList.add('slbbuttonsel2'); }
2320 xxcurrentView = x;
2321 }
2322
2323 function putstore(name, val) {
2324 try {
2325 if ((typeof (localStorage) === 'undefined') || (localStorage.getItem(name) == val)) return;
2326 if (val == null) { localStorage.removeItem(name); } else { localStorage.setItem(name, val); }
2327 } catch (ex) { }
2328 if (name[0] != '_') {
2329 var s = {};
2330 try {
2331 for (var i = 0, len = localStorage.length; i < len; ++i) {
2332 var k = localStorage.key(i);
2333 if (k[0] != '_') {
2334 s[k] = localStorage.getItem(k);
2335 if ((k != 'desktopsettings') && (k != 'stars') && (k != 'deskKeyShortcuts') && (k != 'deskStrings') && (k != 'cmdopt') && (typeof s[k] == 'string') && (s[k].length > 64)) { delete s[k]; }
2336 }
2337 }
2338 } catch (ex) {}
2339 // meshserver.send({ action: 'userWebState', state: JSON.stringify(s) });
2340 }
2341 }
2342
2343 function getstore(name, val) { try { if (typeof (localStorage) === 'undefined') return val; var v = localStorage.getItem(name); if ((v == null) || (v == null)) return val; return v; } catch (e) { return val; } }
2344 function messagebox(t, m) { setSessionActivity(); QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
2345 function statusbox(t, m) { setSessionActivity(); QH('id_dialogMessage', m); setDialogMode(1, t); }
2346 function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
2347 function pad2(num) { var s = '00' + num; return s.substr(s.length - 2); }
2348 function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };
2349 function setSessionActivity() { sessionActivity = Date.now(); /*QH('idleTimeoutNotify', '');*/ }
2350 function printDate(d) { return d.toLocaleDateString(urlargs.locale); }
2351 function printTime(d) { return d.toLocaleTimeString(urlargs.locale); }
2352 function printDateTime(d) { return d.toLocaleString(urlargs.locale); }
2353 function printFlexDateTime(d) { if (printDate(new Date()) == printDate(d)) { return format("Expires at {0}", printTime(d)); } else { return format("Expires {0}", printDateTime(d)); } }
2354 function getFileSizeStr(size) { if (typeof size != 'number') { size = 0; } if (size == 1) return "1 byte"; return format("{0} bytes", size); }
2355 function encodeURIComponentEx(txt) { return encodeURIComponent(txt).replace(/'/g, '%27'); };
2356 function focusTextBox(x) { setTimeout(function () { Q(x).selectionStart = Q(x).selectionEnd = 65535; Q(x).focus(); }, 0); }
2357 var isFilenameValid = (function () { var x1 = /^[^\\/:\*\?"<>\|]+$/, x2 = /^\./, x3 = /^(nul|prn|con|lpt[0-9]|com[0-9])(\.|$)/i; return function isFilenameValid(fname) { return x1.test(fname) && !x2.test(fname) && !x3.test(fname) && (fname[0] != '.'); } })();
2358
2359 function downloadFile(link, name, closeDialog) {
2360 var element = document.createElement('iframe');
2361 element.style.cssText = 'display:none;width:0;height:0;border:0;visibility:hidden;';
2362 element.src = link;
2363 document.body.appendChild(element);
2364 setTimeout(function() { try { if (element.parentNode) { document.body.removeChild(element); } } catch(e) { } }, 10000);
2365 if (closeDialog) { setDialogMode(0); }
2366 }
2367
2368 start();
2369 </script>
2370 </body>
2371 </html>