Started work on main mobile web page

Ylian Saint-Hilaire committed May 25, 2018 at 13:44 UTC ad6d774997f4f6b2c9da94c2ce9409a5316807ea
3 files changed +869 -1
MeshCentralServer.njsproj
+1
@@ -198,6 +198,7 @@
198 <Content Include="readme.txt" />
199 <Content Include="sample-config.json" />
200 <Content Include="SourceFileList.txt" />
201 + <Content Include="views\default-mobile.handlebars" />
202 <Content Include="views\default.handlebars" />
203 <Content Include="views\download.handlebars" />
204 <Content Include="views\login-mobile.handlebars" />
views/default-mobile.handlebars new
+867
@@ -0,0 +1,867 @@
1 +<!DOCTYPE html>
2 +<html 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 + <script type="text/javascript" src="scripts/common-0.0.1.js"></script>
10 + <script type="text/javascript" src="scripts/meshcentral.js"></script>
11 + <title>MeshCentral - Login</title>
12 + <style>
13 + a {
14 + color: #036;
15 + text-decoration: underline;
16 + }
17 +
18 + #footer a {
19 + color: #fff;
20 + text-decoration: underline;
21 + }
22 +
23 + #footer a:hover {
24 + color: #fff;
25 + text-decoration: none;
26 + }
27 +
28 + .i1 {
29 + background: url(../images/icons50.png) 0px 0px;
30 + height: 50px;
31 + width: 50px;
32 + border: none;
33 + }
34 +
35 + .i2 {
36 + background: url(../images/icons50.png) -50px 0px;
37 + height: 50px;
38 + width: 50px;
39 + border: none;
40 + }
41 +
42 + .i3 {
43 + background: url(../images/icons50.png) -100px 0px;
44 + height: 50px;
45 + width: 50px;
46 + border: none;
47 + }
48 +
49 + .i4 {
50 + background: url(../images/icons50.png) -150px 0px;
51 + height: 50px;
52 + width: 50px;
53 + border: none;
54 + }
55 +
56 + .i5 {
57 + background: url(../images/icons50.png) -200px 0px;
58 + height: 50px;
59 + width: 50px;
60 + border: none;
61 + }
62 +
63 + .i6 {
64 + background: url(../images/icons50.png) -250px 0px;
65 + height: 50px;
66 + width: 50px;
67 + border: none;
68 + }
69 +
70 + .gray {
71 + /*filter: url("data:image/svg+xml;utf8,&lt;svg xmlns=\'http://www.w3.org/2000/svg\'&gt;&lt;filter id=\'grayscale\'&gt;&lt;feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/&gt;&lt;/filter&gt;&lt;/svg&gt;#grayscale");*/ /* Firefox 10+, Firefox on Android */
72 + filter: gray; /* IE6-9 */
73 + -webkit-filter: grayscale(100%) opacity(60%); /* Chrome 19+, Safari 6+, Safari 6+ iOS */
74 + }
75 + </style>
76 +</head>
77 +<body onload="if (typeof(startup) !== 'undefined') startup();" style="overflow-y:hidden;margin:0;padding:0;border:0;color:black;font-size:13px;font-family:\'Trebuchet MS\', Arial, Helvetica, sans-serif">
78 + <div id=container>
79 + <div id=mastheadx></div>
80 + <div id=masthead style="background-color:#036;background-repeat:no-repeat;height:50px;width:100%;overflow:hidden">
81 + <div style="float:left;height:66px;color:#c8c8c8;padding-left:10px;padding-top:6px">
82 + <strong><font style="font-size:36px;font-family:Arial,Helvetica,sans-serif">{{{title}}}</font></strong>
83 + </div>
84 + <div style="float:left;height:66px;color:#c8c8c8;padding-left:5px;padding-top:10px">
85 + <strong><font style="font-size:12px;font-family:Arial,Helvetica,sans-serif">{{{title2}}}</font></strong>
86 + </div>
87 + </div>
88 + <div id=page_content style="overflow-y:scroll;position:absolute;bottom:32px;top:50px;width:100%">
89 + <div id=column_l style="width:100%;height:calc(100hv - 82px)">
90 + <div id=p0 style=display:none;width:100%;height:100%>
91 + <div style="display:flex;align-items:center;width:100%;height:100%">
92 + <div id=p0message style=text-align:center;width:100%>Server disconnected, <href onclick=reload() style=cursor:pointer><u>click to reconnect</u></href>.</div>
93 + </div>
94 + </div>
95 + <div id=p1 style=display:none;width:100%;height:100%>
96 + <div style="display:flex;align-items:center;width:100%;height:100%">
97 + <div id=p1message style=text-align:center;width:100%></div>
98 + </div>
99 + </div>
100 + <div id=p2 style=display:none>
101 + <div id=xdevices></div>
102 + </div>
103 + </div>
104 + </div>
105 + <div id=footer style="height:32px;width:100%;text-align:center;background-color:#113962;position:absolute;bottom:0px">
106 + <table cellpadding=0 cellspacing=6 style=width:100%>
107 + <tr>
108 + <td style=text-align:left;color:white>{{{footer}}}</td>
109 + <td style=text-align:right>{{{rootCertLink}}}&nbsp;<a href=terms>Terms &amp; Privacy</a></td>
110 + </tr>
111 + </table>
112 + </div>
113 + </div>
114 + <div id=dialog style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:5px;position:fixed;top:180px;width:400px;display:none">
115 + <div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
116 + <div id=id_dialogclose style=float:right;padding:5px;cursor:pointer onclick=setDialogMode()><b>X</b></div>
117 + <div id=id_dialogtitle style=padding:5px></div>
118 + <div style=width:100%;margin:6px></div>
119 + </div>
120 + <div style="margin-right:16px;margin-left:8px">
121 + <div id=dialog1 style="margin:auto;text-align:center;margin:3px">
122 + <div id=id_dialogMessage style="padding:10px"></div>
123 + </div>
124 + <div id=dialog2 style="margin:auto;margin:3px">
125 + <div id=id_dialogOptions></div>
126 + </div>
127 + </div>
128 + <div id="idx_dlgButtonBar" style="padding:10px;margin-bottom:20px">
129 + <input id="idx_dlgCancelButton" type="button" value="Cancel" style="float:right;width:80px;margin-left:5px" onclick="dialogclose(0)">
130 + <input id="idx_dlgOkButton" type="button" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">
131 + </div>
132 + </div>
133 + <script>
134 + var debugLevel = {{{debuglevel}}};
135 + var features = {{{features}}};
136 + var meshserver = null;
137 + var xdr = null;
138 + var serverinfo = null;
139 + var nodes = [];
140 + var filetree = {};
141 + var userinfo = null;
142 + var serverinfo = null;
143 + var users = null;
144 + var nodeShortIdent = 0;
145 +
146 + function startup() {
147 + if ((features & 32) == 0) {
148 + // Guard against other site's top frames (web bugs).
149 + var loc = null;
150 + try { loc = top.location.toString().toLowerCase(); } catch (e) { }
151 + if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
152 + }
153 +
154 + window.onresize = center;
155 + center();
156 + QH('p1message', 'Connecting...');
157 + go(1);
158 +
159 + // Connect to the mesh server
160 + meshserver = MeshServerCreateControl("{{{domainurl}}}");
161 + meshserver.onStateChanged = onStateChanged;
162 + meshserver.onMessage = onMessage;
163 + meshserver.Start();
164 + }
165 +
166 + function onStateChanged(server, state) {
167 + if (state == 0) {
168 + // Control web socket disconnected
169 + setDialogMode(0); // Close any dialog boxes if present
170 + go(0); // Go to disconnection panel
171 + setTimeout(serverPoll, 5000); // Start polling for the server
172 +
173 + // Clean up here
174 +
175 + } else if (state == 2) {
176 + // Fetch list of meshes, nodes, files
177 + meshserver.send({ action: 'meshes' });
178 + meshserver.send({ action: 'nodes' });
179 + //meshserver.send({ action: 'files' });
180 + if (xxcurrentView < 2) { go(2); }
181 + }
182 + }
183 +
184 + // Poll the server, if it responds, refresh the page.
185 + function serverPoll() {
186 + xdr = null;
187 + try { xdr = new XDomainRequest(); } catch (e) { }
188 + if (!xdr) xdr = new XMLHttpRequest();
189 + xdr.open("HEAD", window.location.href);
190 + xdr.timeout = 15000;
191 + xdr.onload = function () { reload(); };
192 + xdr.onerror = xdr.ontimeout = function () { setTimeout(serverPoll, 10000); };
193 + xdr.send();
194 + }
195 +
196 + function onMessage(server, message) {
197 + switch (message.action) {
198 + case 'serverinfo': {
199 + serverinfo = message.serverinfo;
200 + break;
201 + }
202 + case 'userinfo': {
203 + userinfo = message.userinfo;
204 + //updateSiteAdmin();
205 + //QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
206 + //QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
207 + break;
208 + }
209 + case 'users': {
210 + users = {};
211 + for (var m in message.users) { users[message.users[m]._id] = message.users[m]; }
212 + updateUsers();
213 + break;
214 + }
215 + case 'wssessioncount': {
216 + wssessions = message.wssessions;
217 + updateUsers();
218 + break;
219 + }
220 + case 'meshes': {
221 + meshes = {};
222 + for (var m in message.meshes) { meshes[message.meshes[m]._id] = message.meshes[m]; }
223 + updateMeshes();
224 + updateDevices();
225 + break;
226 + }
227 + case 'files': {
228 + filetree = setupBackPointers(message.filetree);
229 + updateFiles();
230 + d3updatefiles();
231 + break;
232 + }
233 + case 'nodes': {
234 + nodes = [];
235 + for (var m in message.nodes) {
236 + for (var n in message.nodes[m]) {
237 + if (!meshes[m]) { console.log('Invalid mesh (1): ' + m); continue; }
238 + message.nodes[m][n].namel = message.nodes[m][n].name.toLowerCase();
239 + if (message.nodes[m][n].rname) { message.nodes[m][n].rnamel = message.nodes[m][n].rname.toLowerCase(); } else { message.nodes[m][n].rnamel = message.nodes[m][n].namel; }
240 + message.nodes[m][n].meshnamel = meshes[m].name.toLowerCase();
241 + message.nodes[m][n].meshid = m;
242 + message.nodes[m][n].state = (message.nodes[m][n].state)?(message.nodes[m][n].state):0;
243 + message.nodes[m][n].desc = message.nodes[m][n].desc;
244 + if (!message.nodes[m][n].icon) message.nodes[m][n].icon = 1;
245 + message.nodes[m][n].ident = ++nodeShortIdent;
246 + nodes.push(message.nodes[m][n]);
247 + }
248 + }
249 + //onSortSelectChange();
250 + //onSearchInputChanged();
251 + updateDevices();
252 + //refreshMap(false, true);
253 + if (xxcurrentView == 0) { if ('{{viewmode}}' != '') { go({{viewmode}}); } else { setDialogMode(0); go(1); } }
254 + if ('{{currentNode}}' != '') { gotoDevice('{{currentNode}}',{{viewmode}});}
255 + break;
256 + }
257 + case 'powertimeline': {
258 + if (message.nodeid != powerTimelineReq) break;
259 + powerTimelineNode = message.nodeid;
260 + powerTimeline = message.timeline;
261 + powerTimelineUpdate = Date.now() + 300000; // Update every 5 minutes
262 + if (currentNode._id == message.nodeid) { drawDeviceTimeline(); }
263 + break;
264 + }
265 + case 'event': {
266 + /*
267 + if (!message.event.nolog) {
268 + events.unshift(message.event);
269 + var eventLimit = parseInt(p3limitdropdown.value);
270 + while (events.length > eventLimit) { events.pop(); } // Remove element(s) at the end
271 + events_update();
272 + }
273 + */
274 + switch (message.event.action) {
275 + case 'createmesh': {
276 + // A new mesh was created
277 + if (message.event.links['user/{{{domain}}}/' + userinfo.name.toLowerCase()] != null) { // Check if this is a mesh create for a mesh we own. If site administrator, we get all messages so need to ignore some.
278 + meshes[message.event.meshid] = { _id: message.event.meshid, name: message.event.name, mtype: message.event.mtype, desc: message.event.desc, links: message.event.links };
279 + updateMeshes();
280 + updateDevices();
281 + meshserver.send({ action: 'files' });
282 + }
283 + break;
284 + }
285 + case 'meshchange': {
286 + // Update mesh information
287 + if (meshes[message.event.meshid] == null) {
288 + // This is a new mesh for us
289 + meshes[message.event.meshid] = { _id: message.event.meshid, name: message.event.name, mtype: message.event.mtype, desc: message.event.desc, links: message.event.links };
290 + meshserver.send({ action: 'nodes' }); // Request a refresh of all nodes (TODO: We could optimize this to only request nodes for the new mesh).
291 + } else {
292 + // This is an existing mesh
293 + meshes[message.event.meshid].name = message.event.name;
294 + meshes[message.event.meshid].desc = message.event.desc;
295 + meshes[message.event.meshid].links = message.event.links;
296 +
297 + // Check if we lost rights to this mesh in this change.
298 + if (meshes[message.event.meshid].links['user/{{{domain}}}/' + userinfo.name.toLowerCase()] == null) {
299 + if ((xxcurrentView == 20) && (currentMesh == meshes[message.event.meshid])) go(2);
300 + delete meshes[message.event.meshid];
301 +
302 + // Delete all nodes in that mesh
303 + var newnodes = [];
304 + for (var i in nodes) { if (nodes[i].meshid != message.event.meshid) { newnodes.push(nodes[i]); } }
305 + nodes = newnodes;
306 +
307 + // If we are looking at a node in the deleted mesh, move back to "My Devices"
308 + if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && currentNode.meshid == message.event.meshid) { setDialogMode(0); go(1); }
309 + }
310 + }
311 + updateMeshes();
312 + updateDevices();
313 + meshserver.send({ action: 'files' });
314 +
315 + // If we are looking at a mesh that is now deleted, move back to "My Account"
316 + if (xxcurrentView == 20 && currentMesh._id == message.event.meshid) { p20updateMesh(); }
317 + break;
318 + }
319 + case 'deletemesh': {
320 + // Delete the mesh
321 + if (meshes[message.event.meshid]) {
322 + delete meshes[message.event.meshid];
323 + updateMeshes();
324 + meshserver.send({ action: 'files' });
325 + }
326 +
327 + // Delete all nodes in that mesh
328 + var newnodes = [];
329 + for (var i in nodes) { if (nodes[i].meshid != message.event.meshid) { newnodes.push(nodes[i]); } }
330 + nodes = newnodes;
331 + updateDevices();
332 +
333 + // If we are looking at a mesh that is now deleted, move back to "My Account"
334 + if (xxcurrentView >= 20 && xxcurrentView < 30 && currentMesh._id == message.event.meshid) { setDialogMode(0); go(2); }
335 + // If we are looking at a node in the deleted mesh, move back to "My Devices"
336 + if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && currentNode.meshid == message.event.meshid) { setDialogMode(0); go(1); }
337 +
338 + break;
339 + }
340 + case 'addnode': {
341 + var node = message.event.node;
342 + if (!meshes[node.meshid]) break; // This is a node for a mesh we don't know. Happens when we are site administrator, we get all messages.
343 + node.namel = node.name.toLowerCase();
344 + if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
345 + node.meshnamel = meshes[node.meshid].name.toLowerCase();
346 + node.state = 0;
347 + if (!node.icon) node.icon = 1;
348 + node.ident = ++nodeShortIdent;
349 + nodes.push(node);
350 + //onSortSelectChange();
351 + //onSearchInputChanged();
352 + updateDevices();
353 + //updateMapMarkers();
354 + break;
355 + }
356 + case 'removenode': {
357 + var index = -1;
358 + for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
359 + if (index != -1) {
360 + var node = nodes[index];
361 + if (currentNode == node) {
362 + if (xxcurrentView >= 10 && xxcurrentView < 20) { setDialogMode(0); go(1); }
363 + delete currentNode; // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
364 + }
365 + nodes.splice(index, 1);
366 + updateDevices();
367 + updateMapMarkers();
368 + }
369 + break;
370 + }
371 + case 'changenode': {
372 + var index = -1;
373 + for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
374 + if (index != -1) {
375 + var node = nodes[index];
376 +
377 + // Change the node
378 + node.name = message.event.node.name;
379 + node.host = message.event.node.host;
380 + node.desc = message.event.node.desc;
381 + node.publicip = message.event.node.publicip;
382 + node.iploc = message.event.node.iploc;
383 + node.wifiloc = message.event.node.wifiloc;
384 + node.gpsloc = message.event.node.gpsloc;
385 + node.tags = message.event.node.tags;
386 + node.userloc = message.event.node.userloc;
387 + if (message.event.node.agent != null) {
388 + if (node.agent == null) node.agent = {};
389 + if (message.event.node.agent.ver != null) { node.agent.ver = message.event.node.agent.ver; }
390 + if (message.event.node.agent.id != null) { node.agent.id = message.event.node.agent.id; }
391 + if (message.event.node.agent.caps != null) { node.agent.caps = message.event.node.agent.caps; }
392 + if (message.event.node.agent.core != null) { node.agent.core = message.event.node.agent.core; } else { if (node.agent.core) { delete node.agent.core; } }
393 + node.agent.tag = message.event.node.agent.tag;
394 + }
395 + if (message.event.node.intelamt != null) {
396 + if (node.intelamt == null) node.intelamt = {};
397 + if (message.event.node.intelamt.host != null) { node.intelamt.user = message.event.node.intelamt.host; }
398 + if (message.event.node.intelamt.user != null) { node.intelamt.user = message.event.node.intelamt.user; }
399 + if (message.event.node.intelamt.tls != null) { node.intelamt.tls = message.event.node.intelamt.tls; }
400 + if (message.event.node.intelamt.ver != null) { node.intelamt.ver = message.event.node.intelamt.ver; }
401 + if (message.event.node.intelamt.state != null) { node.intelamt.state = message.event.node.intelamt.state; }
402 + }
403 + node.namel = node.name.toLowerCase();
404 + if (node.host) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
405 + if (message.event.node.icon) { node.icon = message.event.node.icon; }
406 +
407 + //onSortSelectChange(true);
408 + //drawNotifications();
409 + //refreshDevice(node._id);
410 + //updateMapMarkers();
411 + updateDevices();
412 +
413 + //if ((currentNode == node) && (xxdialogMode != null) && (xxdialogTag == '@xxmap')) { p10showNodeLocationDialog(); }
414 + }
415 + break;
416 + }
417 + case 'nodeconnect': {
418 + // Indicated a node has changed connectivity state
419 + var index = -1;
420 + for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
421 + if (index != -1) {
422 + var node = nodes[index];
423 +
424 + // Change the node connection state
425 + //node.conn = message.event.conn;
426 + //node.pwr = message.event.pwr;
427 + updateDevices();
428 + //updateMapMarkers();
429 + //refreshDevice(node._id);
430 + }
431 + break;
432 + }
433 + case 'clearevents': {
434 + //events = [];
435 + //events_update();
436 + break;
437 + }
438 + case 'login': {
439 + // Update the last login time
440 + if (users != null && users['user/{{{domain}}}/' + message.event.username.toLowerCase()]) { users['user/{{{domain}}}/' + message.event.username.toLowerCase()].login = message.event.time; }
441 + break;
442 + }
443 + case 'notify': {
444 + //var n = { text: message.event.value };
445 + //if (message.event.tag != null) { n.tag = message.event.tag; }
446 + //addNotification(n);
447 + break;
448 + }
449 + }
450 + break;
451 + }
452 + }
453 + }
454 +
455 + //
456 + // MY ACCOUNT
457 + //
458 +
459 + function updateMeshes() { }
460 +
461 + //
462 + // MY DEVICES
463 + //
464 +
465 + var sort = 0;
466 + var deviceHeaderId = 0;
467 + var deviceHeaderCount;
468 + var deviceHeaders = {};
469 + function updateDevices() {
470 + var r = '', c = 0, current = null, count = 0, displayedMeshes = {}, groups = {}, groupCount = {};
471 +
472 + // 3 wide, list view or desktop view
473 + deviceHeaderId = 0;
474 + deviceHeaderCount = {};
475 + var deviceHeaderTotal = 0;
476 + var deviceHeaders = {};
477 + var deviceHeadersTitles = {};
478 + var current;
479 +
480 + // Go thru the list of nodes and display them
481 + for (var i in nodes) {
482 + if (nodes[i].v == false) continue;
483 + var mesh2 = meshes[nodes[i].meshid], meshlinks = mesh2.links['user/{{{domain}}}/' + userinfo.name.toLowerCase()];
484 + if (meshlinks == null) continue;
485 + var meshrights = meshlinks.rights;
486 +
487 + /*
488 + if (sort == 0) {
489 + // Mesh header
490 + if (nodes[i].meshid != current) {
491 + //deviceHeaderSet();
492 + var extra = '';
493 + if (meshes[nodes[i].meshid].mtype == 1) { extra = '<span class=devHeaderx>, Intel&reg; AMT only</span>'; }
494 + if (current != null) { if (c == 2) { r += '<td><div style=width:301px></div></td>'; } if (r != '') { r += '</tr></table>'; } }
495 + r += '<div class=DevSt style=width:100%;padding-top:4px><span style=float:right>';
496 + //r += getMeshActions(mesh2, meshrights);
497 + r += '</span><span id=MxMESH style=cursor:pointer onclick=gotoMesh("' + nodes[i].meshid + '")>' + EscapeHtml(meshes[nodes[i].meshid].name) + '</span>' + extra + '<span id=DevxHeader' + deviceHeaderId + ' class=devHeaderx></span></div>';
498 + current = nodes[i].meshid;
499 + displayedMeshes[current] = 1;
500 + c = 0;
501 + }
502 + } else if (sort == 1) {
503 + // Power header
504 + if (nodes[i].pwr !== current) {
505 + //deviceHeaderSet();
506 + if (current !== null) { if (c == 2) { r += '<td><div style=width:301px></div></td>'; } if (r != '') { r += '</tr></table>'; } }
507 + r += '<div class=DevSt style=width:100%;padding-top:4px><span>' + PowerStateStr2(nodes[i].pwr) + '</span><span id=DevxHeader' + deviceHeaderId + ' class="devHeaderx"></span></div>';
508 + current = nodes[i].pwr;
509 + c = 0;
510 + }
511 + } else if (sort == 2) {
512 + // Device header
513 + if (current == null) { current = '1'; }
514 + }
515 + */
516 +
517 +
518 + count++;
519 + var title = EscapeHtml(nodes[i].name);
520 + if (title.length == 0) { title = '<i>None</i>'; }
521 + if ((nodes[i].rname != null) && (nodes[i].rname.length > 0)) { title += " / " + EscapeHtml(nodes[i].rname); }
522 + var name = EscapeHtml(nodes[i].name);
523 + //if (showRealNames == true && nodes[i].rname != null) name = EscapeHtml(nodes[i].rname);
524 + if (name.length == 0) { name = '<i>None</i>'; }
525 +
526 + // Node
527 + var icon = nodes[i].icon;
528 + var nodestate = NodeStateStr(nodes[i]);
529 + if ((!nodes[i].conn) || (nodes[i].conn == 0)) { icon += ' gray'; }
530 + //var title = '';
531 + //r += '<div id=devs style=display:inline-block;width:301px;height:50px;padding-top:1px;padding-bottom:1px><div style=width:22px;height:50%;float:left;padding-top:12px><input class="' + nodes[i].meshid + ' DeviceCheckbox" onclick=p1updateInfo() value=devid_' + nodes[i]._id + ' type=checkbox></div><div style=height:100%;cursor:pointer onclick=gotoDevice(\'' + nodes[i]._id + '\')><div class="i' + icon + '" style=width:50px;float:left></div><div style=height:100%><div class=g1></div><div class=e2><div class=e1 title="' + title + '">' + name + '</div><div>' + nodestate + '</div></div><div class=g2></div></div></div></div>';
532 + r += '<div>';
533 + r += '<div class="i' + icon + '" style="float:left;margin-left:4px"></div>';
534 + r += '<div style="width:auto;height:40px;background-color:lightgray;margin-top:5px;margin-bottom:5px;margin-left:60px;padding-top:5px;padding-bottom:5px;cursor:pointer;border-radius:8px 0px 0px 8px">';
535 + r += '<div><div style=padding-left:12px;padding-top:2px><b>' + name + '</b></div><div style=padding-left:12px;padding-top:3px;color:gray>' + nodestate + '</div></div>';
536 + r += '</div>';
537 + r += '</div>';
538 +
539 + // If we are displaying devices by group, put the device in the right group.
540 + /*
541 + if ((sort == 3) && (r != '')) {
542 + if (nodes[i].tags) {
543 + for (var j in nodes[i].tags) {
544 + var tag = nodes[i].tags[j];
545 + if (groups[tag] == null) { groups[tag] = r; groupCount[tag] = 1; } else { groups[tag] += r; groupCount[tag] += 1; }
546 + if (view == 3) break;
547 + }
548 + }
549 + r = '';
550 + }
551 + */
552 +
553 + deviceHeaderTotal++;
554 + if (typeof deviceHeaderCount[nodes[i].state] == 'undefined') { deviceHeaderCount[nodes[i].state] = 1; } else { deviceHeaderCount[nodes[i].state]++; }
555 + }
556 +
557 + QH('xdevices', r);
558 + }
559 +
560 + var powerStateStrings = ['', '<span title="Device is powered on.">Powered</span>', '<span title="Device is in sleep state (S1).">Sleeping</span>', '<span title="Device is in sleep state (S2).">Sleeping</span>', '<span title="Device is in deep sleep state (S3).">Deep Sleep</span>', '<span title="Device is in hibernating state (S4).">Hibernating</span>', '<span title="Device is in powered off state (S5).">Soft-Off</span>', '<span title="Device is detected but power state could not be obtained.">Present</span>'];
561 + var powerStateStrings2 = ['', 'Device is powered', 'Device is in sleep state (S1)', 'Device is in sleep state (S2)', 'Device is in deep sleep state (S3)', 'Device is hibernating (S4)', 'Device is in soft-off state (S5)', 'Device is present, but power state cannot be determined'];
562 + var powerColorTable = ['#00000000', 'black', 'blue', 'blue', 'lightblue', 'blueviolet', 'darkgreen', 'lightseagreen', 'lightseagreen'];
563 + function NodeStateStr(node) {
564 + var states = [];
565 + if (node.state > 0 && node.state < powerStatetable.length) state.push(powerStatetable[node.state]);
566 + if (node.conn) {
567 + if ((node.conn & 1) != 0) states.push('<span title="Mesh agent is connected and ready for use.">Agent</span>');
568 + if ((node.conn & 2) != 0) states.push('<span title="Intel&reg; AMT CIRA is connected and ready for use.">CIRA</span>');
569 + if ((node.conn & 4) != 0) states.push('<span title="Intel&reg; AMT is routable.">Intel&reg; AMT</span>');
570 + if ((node.conn & 8) != 0) states.push('<span title="Mesh agent is reachable using another agent as relay.">Relay</span>');
571 + }
572 + if ((node.pwr != null) && (node.pwr != 0)) { states.push(powerStateStrings[node.pwr]); }
573 + return states.join(', ');
574 + }
575 +
576 + function PowerStateStr(x) {
577 + if (x < powerStatetable.length) return powerStatetable[x];
578 + return '';
579 + }
580 +
581 + function PowerStateStr2(x) {
582 + if ((x != 0) && (x < powerStatetable.length)) return powerStatetable[x];
583 + return 'Unknown';
584 + }
585 +
586 + //
587 + // MY DEVICE
588 + //
589 +
590 + function refreshDevice(nodeid) {
591 + if (!currentNode || currentNode._id != nodeid) return;
592 + gotoDevice(nodeid, xxcurrentView, true);
593 + }
594 +
595 + function getNodeRights(nodeid) {
596 + var node = getNodeFromId(nodeid), mesh = meshes[node.meshid];
597 + return mesh.links['user/{{{domain}}}/' + userinfo.name.toLowerCase()].rights;
598 + }
599 +
600 + var currentNode;
601 + var powerTimelineNode = null;
602 + var powerTimelineReq = null;
603 + var powerTimelineUpdate = null;
604 + var powerTimeline = null;
605 + function getCurrentNode() { return currentNode; };
606 + function gotoDevice(nodeid, panel, refresh) {
607 + //disconnectAllKvmFunction();
608 + var node = getNodeFromId(nodeid);
609 + var mesh = meshes[node.meshid];
610 + var meshrights = mesh.links['user/{{{domain}}}/' + userinfo.name.toLowerCase()].rights;
611 + if (!currentNode || currentNode._id != node._id || refresh == true) {
612 + currentNode = node;
613 +
614 + // Add node name
615 + var nname = EscapeHtml(node.name);
616 + if (nname.length == 0) { nname = '<i>None</i>'; }
617 + if ((meshrights & 4) != 0) { nname = '<span onclick=showEditNodeValueDialog(0) style=cursor:pointer>' + nname + '</span>'; }
618 + QH('p10deviceName', nname);
619 + QH('p11deviceName', nname);
620 + QH('p12deviceName', nname);
621 + QH('p13deviceName', nname);
622 + QH('p14deviceName', nname);
623 + QH('p15deviceName', nname);
624 + QH('p16deviceName', nname);
625 +
626 + // Node attributes
627 + var x = '<table style=width:100%>';
628 +
629 + // Attribute: Mesh
630 + x += addDeviceAttribute('<span title="The name of the administrative group this computer belong to">Mesh</span>', '<a title="The name of the group this computer belong to" onclick=gotoMesh("' + node.meshid + '") style=cursor:pointer>' + EscapeHtml(meshes[node.meshid].name) + '</a>');
631 +
632 + // Attribute: Name
633 + if (node.rname != null) { x += addDeviceAttribute('<span title="The name of this computer as set in the operating system">Name</span>', '<span title="The name of this computer as set in the operating system">' + EscapeHtml(node.rname) + '</span>'); }
634 +
635 + // Attribute: Host
636 + if ((mesh.mtype == 1) || (node.name != node.host)) {
637 + if ((meshrights & 4) != 0) {
638 + if (node.host) {
639 + x += addDeviceAttribute('Hostname', '<span onclick=showEditNodeValueDialog(1) style=cursor:pointer>' + EscapeHtml(node.host) + '</span>');
640 + } else {
641 + x += addDeviceAttribute('Hostname', '<span onclick=showEditNodeValueDialog(1) style=cursor:pointer><i>None</i></span>');
642 + }
643 + } else {
644 + x += addDeviceAttribute('Hostname', EscapeHtml(node.host));
645 + }
646 + }
647 +
648 + // Attribute: Description
649 + var description = node.desc?EscapeHtml(node.desc):"<i>None</i>";
650 + if ((meshrights & 4) != 0) {
651 + x += addDeviceAttribute('Description', '<span onclick=showEditNodeValueDialog(2) style=cursor:pointer>' + description + '</span>');
652 + } else {
653 + x += addDeviceAttribute('Description', description);
654 + }
655 +
656 + // Attribute: Mesh Agent
657 + var agentsStr = ['Unknown', 'Windows 32bit console', 'Windows 64bit console', 'Windows 32bit service', 'Windows 64bit service', 'Linux 32bit', 'Linux 64bit', 'MIPS', 'XENx86', 'Android ARM', 'Linux ARM', 'OSX 32bit', 'Android x86', 'PogoPlug ARM', 'Android APK', 'Linux Poky x86-32bit', 'OSX 64bit', 'ChromeOS', 'Linux Poky x86-64bit', 'Linux NoKVM x86-32bit', 'Linux NoKVM x86-64bit', 'Windows MinCore console', 'Windows MinCore service', 'NodeJS', 'ARM-Linaro', 'ARMv6l / ARMv7l' ];
658 + if ((node.agent != null) && (node.agent.id != null) && (node.agent.ver != null)) {
659 + var str = '';
660 + if (node.agent.id <= agentsStr.length) { str = agentsStr[node.agent.id]; } else { str = agentsStr[0]; }
661 + if (node.agent.ver != 0) { str += ' v' + node.agent.ver; }
662 + x += addDeviceAttribute('Mesh Agent', str);
663 + }
664 +
665 + // Attribute: Intel AMT
666 + if (node.intelamt != null) {
667 + var str = '';
668 + var provisioningStates = { 0: 'Not Activated (Pre)', 1: 'Not Activated (In)', 2: 'Activated' };
669 + if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>Unknown State</i>, v' + node.intelamt.ver; } else
670 + if (node.intelamt.ver == null || node.intelamt.state == null) { str += '<i>Unknown Version & State</i>'; } else {
671 + str += provisioningStates[node.intelamt.state];
672 + if (node.intelamt.flags) { if (node.intelamt.flags & 2) { str += ' <span title="Intel AMT is activated in Client Control Mode">CCM</span>'; } else if (node.intelamt.flags & 4) { str += ' <span title="Intel AMT is activated in Admin Control Mode">ACM</span>'; } }
673 + str += (', v' + node.intelamt.ver);
674 + }
675 + if (node.intelamt.tls == 1) { str += ', <span title="Intel AMT is setup with TLS network security">TLS</span>'; }
676 + if (node.intelamt.state == 2) {
677 + if (node.intelamt.user == null || node.intelamt.user == '') {
678 + if ((meshrights & 4) != 0) {
679 + str += ', <i style=color:#FF0000;cursor:pointer title="Edit Intel&reg; AMT credentials" onclick=editDeviceAmtSettings("' + node._id + '")>No Credentials</i>';
680 + } else {
681 + str += ', <i style=color:#FF0000>No Credentials</i>';
682 + }
683 + }
684 + str += ' ';
685 + if ((meshrights & 4) != 0) {
686 + str += '<img src=images/link4.png height=10 width=10 title="Edit Intel&reg; AMT credentials" style=cursor:pointer onclick=editDeviceAmtSettings("' + node._id + '")>';
687 + }
688 + }
689 + x += addDeviceAttribute('Intel&reg; AMT', str);
690 + }
691 +
692 + // Attribute: Mesh Agent Tag
693 + if ((node.agent != null) && (node.agent.tag != null) && (node.agent.tag != 'mailto:')) {
694 + var tag = EscapeHtml(node.agent.tag);
695 + if (tag.startsWith('mailto:')) { tag = '<a href="' + tag + '">' + tag.substring(7) + '</a>'; }
696 + x += addDeviceAttribute('Agent Tag', tag);
697 + }
698 +
699 + // Attribute: Intel AMT
700 + //if (node.intelamt && node.intelamt.user) { x += addDeviceAttribute('Intel&reg; AMT', node.intelamt.user); }
701 +
702 + // Attribute: Connectivity (Only show this if more than just the agent is connected).
703 + var connectivity = node.conn;
704 + if (connectivity && connectivity > 1) {
705 + var cstate = [];
706 + if ((node.conn & 1) != 0) cstate.push('<span title="Mesh agent is connected and ready for use.">Mesh Agent</span>');
707 + if ((node.conn & 2) != 0) cstate.push('<span title="Intel&reg; AMT CIRA is connected and ready for use.">Intel&reg; AMT CIRA</span>');
708 + if ((node.conn & 4) != 0) cstate.push('<span title="Intel&reg; AMT is routable and ready for use.">Intel&reg; AMT</span>');
709 + if ((node.conn & 8) != 0) cstate.push('<span title="Mesh agent is reachable using another agent as relay.">Mesh Relay</span>');
710 + x += addDeviceAttribute('Connectivity', cstate.join(', '));
711 + }
712 +
713 + // Node grouping tags
714 + var groupingTags = '<i>None</i>';
715 + if (node.tags != null) { groupingTags = ''; for (var i in node.tags) { groupingTags += '<span style="background-color:lightgray;padding:3px;margin-right:4px;border-radius:5px">' + node.tags[i] + '</span>'; } }
716 + x += addDeviceAttribute('Groups', '<span onclick=showEditNodeValueDialog(3) style=cursor:pointer>' + groupingTags + '</span>');
717 +
718 + x += '</table><br />';
719 + // Show action button, only show if we have permissions 4, 8, 64
720 + if ((meshrights & 76) != 0) { x += '<input type=button value=Actions title="Perform power actions on the device" onclick=deviceActionFunction() />'; }
721 + x += '<input type=button value=Notes title="View notes about this device" onclick=showNotes(' + ((meshrights & 128) == 0) + ',"' + encodeURIComponent(node._id) + '") />';
722 + //if ((connectivity & 1) && (meshrights & 8) && (node.agent.id < 5)) { x += '<input type=button value=Toast title="Display a text message of the remote device" onclick=deviceToastFunction() />'; }
723 + QH('p10html', x);
724 +
725 + // Show node last 7 days timeline
726 + drawDeviceTimeline();
727 +
728 + // Show bottom buttons
729 + x = '<div style=float:right;font-size:x-small>';
730 + if ((meshrights & 4) != 0) x += '<a style=cursor:pointer onclick=p10showDeleteNodeDialog("' + node._id + '") title="Remove this device">Delete Device</a>';
731 + x += '</div><div style=font-size:x-small>';
732 + if (mesh.mtype == 2) x += '<a style=cursor:pointer onclick=p10showNodeNetInfoDialog("' + node._id + '") title="Show device network interface information">Interfaces</a>&nbsp;';
733 + if (xxmap != null) x += '<a style=cursor:pointer onclick=p10showNodeLocationDialog("' + node._id + '") title="Show device locations information">Location</a>&nbsp;';
734 +
735 + if (((meshrights & 8) != 0) && (mesh.mtype == 2)) x += '<a style=cursor:pointer onclick=p10showMeshCmdDialog(1,"' + node._id + '") title="Traffic router used to connect to a device thru this server.">Router</a>&nbsp;';
736 +
737 + // RDP link, show this link only of the remote machine is Windows.
738 + if (((connectivity & 1) != 0) && (clickOnce == true) && (mesh.mtype == 2) && ((meshrights & 8) != 0)) {
739 + if ((node.agent.id > 0) && (node.agent.id < 5)) { x += '<a style=cursor:pointer onclick=p10clickOnce("' + node._id + '","RDP2",3389) title="Requires Microsoft ClickOnce support in your browser.">RDP</a>&nbsp;'; }
740 + if (node.agent.id > 4) {
741 + x += '<a style=cursor:pointer onclick=p10clickOnce("' + node._id + '","PSSH",22) title="Requires Microsoft ClickOnce support in your browser.">Putty</a>&nbsp;';
742 + x += '<a style=cursor:pointer onclick=p10clickOnce("' + node._id + '","WSCP",22) title="Requires Microsoft ClickOnce support in your browser.">WinSCP</a>&nbsp;';
743 + }
744 + }
745 + x += '</div><br>'
746 +
747 + QH('p10html3', x);
748 +
749 + // Set the node power state
750 + powerstate = PowerStateStr(node.state);
751 + //if (node.state == 0) { powerstate = 'Unknown State'; }
752 + if ((connectivity & 1) != 0) { if (powerstate.length > 0) { powerstate += '<br/>'; } powerstate += '<span style=font-size:12px title="Agent connected">Agent connected</span>'; }
753 + if ((connectivity & 2) != 0) { if (powerstate.length > 0) { powerstate += '<br/>'; } powerstate += '<span style=font-size:12px title="Intel&reg; AMT connected">Intel&reg; AMT connected</span>'; }
754 + if ((connectivity & 4) != 0) { if (powerstate.length > 0) { powerstate += '<br/>'; } powerstate += '<span style=font-size:12px title="Intel&reg; AMT detected">Intel&reg; AMT detected</span>'; }
755 + QH('MainComputerState', powerstate);
756 +
757 + // Set the node icon
758 + Q('MainComputerImage').setAttribute("src", "images/icons200-" + node.icon + "-1.png");
759 + Q('MainComputerImage').className = ((!node.conn) || (node.conn == 0)?'gray':'');
760 +
761 + // Setup/Refresh the desktop tab
762 + setupTerminal();
763 + setupFiles();
764 + var consoleRights = ((meshrights & 16) != 0);
765 + if (consoleRights) { setupConsole(); } else { if (panel == 15) { panel = 10; } }
766 +
767 + // Show or hide the tabs
768 + // mesh.mtype: 1 = Intel AMT only, 2 = Mesh Agent
769 + // node.agent.caps (bitmask): 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console
770 + QV('MainDevDesktop', ((mesh.mtype == 1) || (node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 1) != 0)) && (meshrights & 8));
771 + QV('MainDevTerminal', ((mesh.mtype == 1) || (node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 2) != 0)) && (meshrights & 8));
772 + QV('MainDevFiles', ((mesh.mtype == 2) && ((node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 4) != 0))) && (meshrights & 8));
773 + QV('MainDevAmt', (node.intelamt != null) && (node.intelamt.state == 2) && (meshrights & 8));
774 + QV('MainDevConsole', (consoleRights && (mesh.mtype == 2) && ((node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 8) != 0))) && (meshrights & 8));
775 + QV('p15uploadCore', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 16) != 0) && (userinfo.siteadmin == 0xFFFFFFFF));
776 + QH('p15coreName', ((node.agent != null) && (node.agent.core != null))?node.agent.core:'');
777 +
778 + // Setup/Refresh Intel AMT tab
779 + var amtFrameNode = Q('p14iframe').contentWindow.getCurrentMeshNode();
780 + if ((amtFrameNode != null) && (amtFrameNode._id != currentNode._id)) { Q('p14iframe').contentWindow.disconnect(); }
781 + var online = ((node.conn & 6) != 0)?true:false; // If CIRA (2) or AMT (4) connected, enable Commander
782 + Q('p14iframe').contentWindow.setConnectionState(online);
783 + Q('p14iframe').contentWindow.setFrameHeight('650px');
784 + Q('p14iframe').contentWindow.setAuthCallback(updateAmtCredentials);
785 +
786 + // Display "action" button on desktop/terminal/files
787 + QV('deskActionsBtn', (meshrights & 72) != 0); // 72 = Wake-up + Remote Control permissions
788 + QV('termActionsBtn', (meshrights & 72) != 0);
789 + QV('filesActionsBtn', (meshrights & 72) != 0);
790 +
791 + // Request the power timeline
792 + if ((powerTimelineNode != currentNode._id) && (powerTimelineReq != currentNode._id)) { powerTimelineReq = currentNode._id; meshserver.send({ action: 'powertimeline', nodeid: currentNode._id }); }
793 +
794 + // Reset the desktop tools
795 + QV('DeskTools', false);
796 + showDeskToolsProcesses();
797 +
798 + // Ask for device events
799 + //refreshDeviceEvents();
800 + }
801 + setupDesktop(); // Always refresh the desktop, even if we are on the same device, we need to do some canvas switching.
802 + if (!panel) panel = 10;
803 + go(panel);
804 + }
805 +
806 + //
807 + // PANELS
808 + //
809 +
810 + var xxcurrentView = -1;
811 + function go(x) {
812 + if (xxdialogMode || xxcurrentView == x) return;
813 + setDialogMode(0);
814 + // Edit this line when adding a new screen
815 + for (var i = 0; i < 32; i++) { QV('p' + i, i == x); }
816 + xxcurrentView = x;
817 + }
818 +
819 + //
820 + // POPUP DIALOG
821 + //
822 +
823 + // undefined = Hidden, 1 = Generic Message
824 + var xxdialogMode;
825 + var xxdialogFunc;
826 + var xxdialogButtons;
827 + var xxdialogTag;
828 +
829 + // Display a dialog box
830 + // 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)
831 + function setDialogMode(x, y, b, f, c, tag) {
832 + xxdialogMode = x;
833 + xxdialogFunc = f;
834 + xxdialogButtons = b;
835 + xxdialogTag = tag;
836 + QE('idx_dlgOkButton', true);
837 + QV('idx_dlgOkButton', b & 1);
838 + QV('idx_dlgCancelButton', b & 2);
839 + QV('id_dialogclose', (b & 2) || (b & 8));
840 + QV('idx_dlgButtonBar', b & 7);
841 + if (y) QH('id_dialogtitle', y);
842 + for (var i = 1; i < 24; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
843 + QV('dialog', x);
844 + if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
845 + }
846 +
847 + function dialogclose(x) {
848 + var f = xxdialogFunc;
849 + var b = xxdialogButtons;
850 + var t = xxdialogTag;
851 + setDialogMode();
852 + if (((b & 8) || x) && f) f(x, t);
853 + }
854 +
855 + function center() { QS('dialog').left = ((((getDocWidth() - 400) / 2)) + "px"); }
856 + function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
857 + function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
858 + function getDocWidth() { if (window.innerWidth) return window.innerWidth; if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientWidth != 0) return document.documentElement.clientWidth; return document.getElementsByTagName('body')[0].clientWidth; }
859 + function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
860 + function haltReturn(e) { if (e.keyCode == 13) { haltEvent(e); } }
861 + function validateEmail(v) { var emailReg = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; return emailReg.test(v); }
862 + function reload() { window.location.href = window.location.href; }
863 + function getNodeFromId(id) { for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; } return null; }
864 +
865 + </script>
866 +</body>
867 +</html>
\ No newline at end of file
webserver.js
+1 -1
@@ -712,7 +712,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
712 // Send the master web application
713 if ((!obj.args.user) && (obj.args.nousers != true) && (nologout == false)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button
714 var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
715 - res.render(obj.path.join(__dirname, 'views/default'), { viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, mpspass: args.mpspass, webcerthash: obj.webCertificateHashBase64, footer: (domain.footer == null) ? '' : domain.footer });
715 + res.render(obj.path.join(__dirname, isModuleBrowser(req) ? 'views/default-mobile' : 'views/default'), { viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, mpspass: args.mpspass, webcerthash: obj.webCertificateHashBase64, footer: (domain.footer == null) ? '' : domain.footer });
716 } else {
717 // Send back the login application
718 var loginmode = req.session.loginmode, features = 0;