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