MeshCentral/views/default-mobile.handlebars

6949 lines
426 KiB
Handlebars

<!DOCTYPE html>
<html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />
<meta name="format-detection" content="telephone=no" />
<meta name="robots" content="noindex,nofollow">
<link rel="manifest" href="{{{domainurl}}}manifest.json">
<link rel="shortcut icon" href="{{{domainurl}}}favicon.ico" />
<link rel="icon" type="image/png" sizes="16x16" href="{{{domainurl}}}favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{{domainurl}}}favicon-32x32.png">
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
<link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="#ffffff">
<meta name="apple-mobile-web-app-title" content="{{{title}}}">
<script type="text/javascript" src="scripts/common-0.0.1{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/meshcentral{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/agent-redir-ws-0.1.1{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/agent-desktop-0.0.2{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/amt-0.2.0{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/amt-redir-ws-0.1.0{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/amt-desktop-0.0.2{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/xterm{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/xterm-addon-fit{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/zlib{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/zlib-inflate{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/zlib-adler32{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/zlib-crc32{{{min}}}.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/filesaver.min.js"></script>
<meta name="msapplication-TileColor" content="#00aba9">
<meta name="theme-color" content="#ffffff">
<title>{{{title}}}</title>
<style>
body {
background-color: white;
}
.night body {
background-color: black;
}
#MxMESH {
color: black;
}
.night #MxMESH {
color: lightgray;
}
.textOverGray { color: black; }
#dialog {
z-index:1000;
background-color:#EEE;
box-shadow:0px 0px 15px #666;
font-family:Arial,Helvetica,sans-serif;
border-radius:5px;
position:fixed;
top:90px;
width:300px;
}
.night #dialog {
color: black;
background-color:#AAA;
}
:focus {
outline: 0;
}
a {
color: #036;
text-decoration: underline;
}
.night a {
color: #99F;
}
#footer a {
color: #fff;
text-decoration: underline;
}
#footer a:hover {
text-decoration: none;
}
.night #footer {
color: gray;
}
.i1 {
background: url(../images/icons50.png) 0px 0px;
background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
height: 50px;
width: 50px;
border: none;
}
.i2 {
background: url(../images/icons50.png) -50px 0px;
background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
height: 50px;
width: 50px;
border: none;
}
.i3 {
background: url(../images/icons50.png) -100px 0px;
background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
height: 50px;
width: 50px;
border: none;
}
.i4 {
background: url(../images/icons50.png) -150px 0px;
background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
height: 50px;
width: 50px;
border: none;
}
.i5 {
background: url(../images/icons50.png) -200px 0px;
background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
height: 50px;
width: 50px;
border: none;
}
.i6 {
background: url(../images/icons50.png) -250px 0px;
background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
height: 50px;
width: 50px;
border: none;
}
.i7 {
background: url(../images/icons50.png) -300px 0px;
background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
height: 50px;
width: 50px;
border: none;
}
.i8 {
background: url(../images/icons50.png) -350px 0px;
background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
height: 50px;
width: 50px;
border: none;
}
.m0 {
background: url(../images/images16.png) -32px 0px;
height: 16px;
width: 16px;
border: none;
float: left;
}
.m1 {
background: url(../images/images16.png) -16px 0px;
height: 16px;
width: 16px;
border: none;
float: left;
}
.m2 {
background: url(../images/images16.png) -96px 0px;
height: 16px;
width: 16px;
border: none;
float: left;
}
.m3 {
background: url(../images/images16.png) -112px 0px;
height: 16px;
width: 16px;
border: none;
float: left;
}
.m4 {
background: url(../images/images16.png) -128px 0px;
height: 16px;
width: 16px;
border: none;
float: left;
}
.NotifyIconSmall1 { width:24px; height:24px; background: url(../images/notify24.png) 0px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
.NotifyIconSmall2 { width:24px; height:24px; background: url(../images/notify24.png) -24px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
.NotifyIconSmall3 { width:24px; height:24px; background: url(../images/notify24.png) -48px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
.NotifyIconSmall4 { width:24px; height:24px; background: url(../images/notify24.png) -72px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
.NotifyIconSmall5 { width:24px; height:24px; background: url(../images/notify24.png) -96px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
.NotifyIconSmall6 { width:24px; height:24px; background: url(../images/notify24.png) -120px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
.NotifyIconSmall7 { width:24px; height:24px; background: url(../images/notify24.png) -144px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
.NotifyIconSmall8 { width:24px; height:24px; background: url(../images/notify24.png) -168px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
.NotifyIconSmall9 { width:24px; height:24px; background: url(../images/notify24.png) -192px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
.gray {
/*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 */
filter: gray; /* IE6-9 */
-webkit-filter: grayscale(100%) opacity(60%); /* Chrome 19+, Safari 6+, Safari 6+ iOS */
}
.DevSt {
padding-left: 5px;
border-bottom-style: solid;
border-bottom-width: 1px;
border-bottom-color: #DDDDDD;
}
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.fileIcon1 {
background: url(data:image/gif;base64,R0lGODlhEAAQAJEDAPb49Y2Sj9LT2f///yH5BAEAAAMALAAAAAAQABAAAAImnI+py+1vhJwyUYAzHTL4D3qdlJWaIFJqmKod607sDKIiDUP63hQAOw==);
height: 16px;
width: 16px;
cursor: pointer;
border: none;
float: left;
margin-top: 1px;
}
.fileIcon2 {
background: url(data:image/gif;base64,R0lGODlhEAAQAJEDAM2xV/Xur+XPgP///yH5BAEAAAMALAAAAAAQABAAAAJD3ISZIGHWUGihznesYDYATFVM+D2hJ4lgN1olxALAtAlmPCJvuMmJd6PJckDYwicrHhTD5o7plJmg0Uc0asNMkphHAQA7);
height: 16px;
width: 16px;
cursor: pointer;
border: none;
float: left;
margin-top: 1px;
}
.fileIcon3 {
background: url(data:image/gif;base64,R0lGODlhEAAQAJEDAPb19IGBgbq6uv///yH5BAEAAAMALAAAAAAQABAAAAIy3ISpxgcPH2ouQgFEw1YmxnUXKEaaEZZnVWZk66JwzKpvuwZzwOgwb/C1gIOA8Yg8DgoAOw==);
height: 16px;
width: 16px;
cursor: pointer;
border: none;
float: left;
margin-top: 1px;
}
.fileIcon4 {
background: url(../images/meshicon16.png);
height: 16px;
width: 16px;
cursor: pointer;
border: none;
float: left;
margin-top: 1px;
}
.filelist {
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
cursor: default;
-khtml-user-drag: element;
clear: both;
}
.deviceNotifyDot {
position:absolute;
right:10px;
top:0px;
height:16px;
}
.deviceNotifyDotSub {
text-align:center;
color:#FFF;
width:16px;
background-color:#00F;
padding:2px;
border-radius:10px;
box-shadow: 2px 2px 10px black;
cursor:pointer;
margin-left:3px;
float:left;
}
.deviceNotifyDotSub:hover {
background-color:#44F;
}
.deviceNotifySmallDot {
position:absolute;
right:10px;
top:0px;
height:10px;
}
.deviceNotifySmallDotSub {
text-align:center;
color:#FFF;
width:10px;
padding:2px;
background-color:#00F;
border-radius:10px;
box-shadow: 2px 2px 10px black;
cursor:pointer;
margin-left:2px;
float:left;
}
.deviceNotifySmallDotSub:hover {
background-color:#44F;
}
.deviceNotifyLargeDot {
position:absolute;
right:10px;
top:10px;
height:40px;
}
.deviceNotifyLargeDotSub {
text-align:center;
width:35px;
height:35px;
color:#FFF;
padding:2px;
background-color:#00F;
border-radius:20px;
box-shadow: 2px 2px 10px black;
cursor:pointer;
margin-left:4px;
font-size:30px;
float:left;
}
.deviceNotifyLargeDotSub:hover {
background-color:#44F;
}
.style10 {
background-color: #C9C9C9;
color: #000;
}
.night .style10 {
background-color: #888;
}
.deviceBatteryLarge {
position:absolute;
right:10px;
top:0px;
width:28px;
height:48px;
border:none;
box-shadow:none;
}
.deviceBatteryLarge1 { background: url(../images/batteries48.png) 0px 0px; }
.deviceBatteryLarge2 { background: url(../images/batteries48.png) -28px 0px; }
.deviceBatteryLarge3 { background: url(../images/batteries48.png) -56px 0px; }
.deviceBatteryLarge4 { background: url(../images/batteries48.png) -84px 0px; }
.deviceBatteryLarge5 { background: url(../images/batteries48.png) -112px 0px; }
.deviceBatteryLarge6 { background: url(../images/batteries48.png) -140px 0px; }
.deviceBatteryLarge7 { background: url(../images/batteries48.png) -168px 0px; }
.deviceBatteryLarge8 { background: url(../images/batteries48.png) -196px 0px; }
.deviceBatteryLarge9 { background: url(../images/batteries48.png) -224px 0px; }
.deviceBatteryLarge10 { background: url(../images/batteries48.png) -252px 0px; }
.deviceBatteryLarge11 { background: url(../images/batteries48.png) -280px 0px; }
.deviceBatterySmall {
position:absolute;
left:6px;
top:22px;
width:14px;
height:24px;
border:none;
box-shadow:none;
}
.deviceBatterySmall1 { background: url(../images/batteries24.png) 0px 0px; }
.deviceBatterySmall2 { background: url(../images/batteries24.png) -14px 0px; }
.deviceBatterySmall3 { background: url(../images/batteries24.png) -28px 0px; }
.deviceBatterySmall4 { background: url(../images/batteries24.png) -42px 0px; }
.deviceBatterySmall5 { background: url(../images/batteries24.png) -56px 0px; }
.deviceBatterySmall6 { background: url(../images/batteries24.png) -70px 0px; }
.deviceBatterySmall7 { background: url(../images/batteries24.png) -84px 0px; }
.deviceBatterySmall8 { background: url(../images/batteries24.png) -98px 0px; }
.deviceBatterySmall9 { background: url(../images/batteries24.png) -112px 0px; }
.deviceBatterySmall10 { background: url(../images/batteries24.png) -126px 0px; }
.deviceBatterySmall11 { background: url(../images/batteries24.png) -140px 0px; }
.meshList {
width:auto;
height:40px;
background-color:lightgray;
margin-top:5px;
margin-bottom:5px;
margin-left:60px;
padding-top:5px;
padding-bottom:5px;
border-radius:8px 0px 0px 8px;
}
.night .meshList {
background-color: gray;
}
.devList1 {
height: 50px;
cursor: pointer;
position: relative;
margin-top: 5px;
margin-bottom: 5px;
}
.devList2 {
float: left;
margin-left: 4px
}
.devList3 {
width: auto;
height: 40px;
background-color: lightgray;
margin-left: 60px;
padding-top: 5px;
padding-bottom: 5px;
border-radius: 8px 0px 0px 8px;
}
.night .devList3 {
background-color: gray;
}
.devList4 {
padding-left: 12px;
padding-top: 2px;
color: black;
}
.devList5 {
padding-left: 12px;
padding-top: 3px;
color: #444
}
.night .devList5 {
color: black;
}
.deskButton {
box-shadow: 0px 0px 10px #000;
border-radius:20px;
position:absolute;
right:10px;
top:10px;
cursor:pointer;
background-color:#AAA;
z-index:1000;
}
.menuButton{
box-shadow: 0px 0px 10px #000;
border-radius:10px;
display:inline-block;
width:120px;
background-color:#AAA;
text-align:center;
padding:8px;
cursor:pointer;
margin:10px;
z-index:1000;
}
#notificationCount {
min-width: 28px;
font-size: 20px;
background-color: orange;
text-align: center;
cursor: pointer;
color: black;
}
.notifiyBox {
font-size: 16px;
position: absolute;
z-index: 1000;
top: 60px;
right: 76px;
width: 300px;
text-align: left;
background-color: #F0ECCD;
border: 4px solid #666;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 2px 2px 4px #888;
-moz-box-shadow: 2px 2px 4px #888;
box-shadow: 2px 2px 4px #888;
max-height: 200px;
}
.night .notifiyBox {
color: black;
}
.notifiyBox:before {
content: ' ';
position: absolute;
width: 0;
height: 0;
right: 5px;
top: -30px;
border: 15px solid;
border-color: transparent #666 #666 transparent;
}
.notifiyBox:after {
content: ' ';
position: absolute;
width: 0;
height: 0;
right: 7px;
top: -24px;
border: 12px solid;
border-color: transparent #F0ECCD #F0ECCD transparent;
}
#p15statetext {
padding: 4px;
height: 15px;
}
#p15agentConsole {
background: black;
margin: 0;
padding: 0;
color: lightgray;
width: 100%;
position: relative;
}
#p15coreName {
padding: 4px;
display: inline-block;
}
#p15agentConsoleText {
position:absolute;
margin: 0;
padding: 0;
top: 0;
bottom: 0;
left:0;
right: 0;
overflow-y: scroll;
overflow-x: auto;
}
.areaHead {
padding-top: 2px;
padding-bottom: 2px;
background: #C0C0C0;
}
.night .areaHead {
color: #CCC;
background: #333;
}
.areaFoot {
padding-top: 2px;
padding-bottom: 2px;
background: #C0C0C0;
}
.night .areaFoot {
color: #CCC;
background: #333;
}
.toright2 {
float: right;
text-align: right;
}
#consoleTable {
width: 100%;
height: 100%;
padding: 0px;
margin-top: 0px;
}
.night #consoleTable {
color: black;
}
.menucurve {
background-color:white;
width:10px;
height:10px;
border-radius:10px 0 0 0;
border-right:1px solid white;
border-bottom:1px solid white;
}
.night .menucurve {
background-color:black;
border-right:1px solid black;
border-bottom:1px solid black;
}
#termTable {
width: 100%;
padding: 0px;
margin-top: 0px;
}
.fulldesk #termTable {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
#termarea3x {
background: black;
text-align: center;
height: 400px;
position: relative;
}
.viewSelector10 {
margin-left: 2px;
margin-top: 2px;
background: url(../images/views.png) -476px 0px;
height: 28px;
width: 28px;
}
.viewSelector11 {
margin-left: 2px;
margin-top: 2px;
background: url(../images/views.png) -504px 0px;
height: 28px;
width: 28px;
}
</style>
</head>
<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" style="overflow-y:hidden;margin:0;padding:0;border:0;font-size:13px;font-family:\'Trebuchet MS\', Arial, Helvetica, sans-serif">
<div id=container>
<div id="notifiyBox" class="notifiyBox" style="display:none"></div>
<div id=mastheadx></div>
<div id=masthead style="background:url(logo.png) 0px 0px;background-size:341px 50px;background-color:#036;background-repeat:no-repeat;height:50px;width:100%;overflow:hidden">
<div style="width:calc(100% - 50px);overflow:hidden">
<div style="float:left;height:66px;color:#c8c8c8;padding-left:10px;padding-top:6px">
<strong><font style="font-size:36px;font-family:Arial,Helvetica,sans-serif;text-shadow: 1px 1px 2px #000;">{{{title1}}}</font></strong>
</div>
<div style="float:left;height:66px;color:#c8c8c8;padding-left:5px;padding-top:10px">
<strong><font style="font-size:12px;font-family:Arial,Helvetica,sans-serif;text-shadow: 1px 1px 2px #000;">{{{title2}}}</font></strong>
</div>
</div>
<div id="devViewPageState" class=noselect style="position:absolute;right:160px;top:10px;height:30px;line-height:30px;color:#c8c8c8;font-size:16px;display:none"></div>
<img id="devViewPageButton2" class=noselect style="position:absolute;right:130px;top:10px;cursor:pointer;display:none" onclick=onDeviceViewPageChange(2) src="/images/left-30.png" width=20 height=30 />
<img id="devViewPageButton3" class=noselect style="position:absolute;right:100px;top:10px;cursor:pointer;display:none" onclick=onDeviceViewPageChange(3) src="/images/right-30.png" width=20 height=30 />
<div id=notificationCount onclick="clickNotificationIcon()" class="unselectable" style="position:absolute;right:50px;top:0px;font-size:28px;width:50px;height:50px;cursor:pointer;display:none" title="Click to view current notifications"><div id="notificationCount2" style="padding-top:8px">0</div></div>
<img id="topMenuIcon" class=noselect style="position:absolute;right:0;top:10px;color:#c8c8c8;font-size:44px;margin-right:8px;cursor:pointer;display:none" onclick=topMenu() src="/images/3bars-30.png" width=30 height=30 />
</div>
<div id=page_content style="position:absolute;bottom:32px;top:50px;width:100%">
<div id=column_l style="width:100%;padding:0;position:absolute;bottom:0px;top:0px">
<div id=p0 style=display:none;width:100%;height:100%>
<div style="display:flex;align-items:center;width:100%;height:100%">
<div id=p0message style=text-align:center;width:100%><span id="p0span">Server disconnected</span>, <href onclick=reload() style=cursor:pointer><u>click to reconnect</u></href>.</div>
</div>
</div>
<div id=p1 style=display:none;width:100%;height:100%>
<div style="display:flex;align-items:center;width:100%;height:100%">
<div id=p1message style=text-align:center;width:100%></div>
</div>
</div>
<div id=p2 style="display:none;position:absolute;top:0;left:0;right:0;bottom:0">
<div id=xdevices style="position:absolute;overflow-y:auto;top:0;left:0;right:0;bottom:34px" onscroll="onDevicesScroll()" ontouchstart="onDeviceTouch(true)" ontouchend="onDeviceTouch(false)"></div>
<div id=xdevicesBar style="position:absolute;overflow-y:auto;height:34px;left:0;right:0;bottom:0px;background-color:#aaa;color:black">
<div style="margin:4px">
<span style="width:20px;display:inline-block;text-align:center;cursor:pointer;font-size:16px" onclick=clearSearchInput()><b>X</b></span>
<input id=SearchInput autocomplete=off type=search placeholder=Filter onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) style="padding:2px;margin:0;height:20px;background-color:#FFF" />&nbsp;
<label class=noselect><input type=checkbox id=RealNameCheckBox onclick=onRealNameCheckBox() />OS Name</label>
<label class=noselect><input type=checkbox id=OnlineCheckBox onclick=onOnlineCheckBox(event) />Online</label>
</div>
</div>
</div>
<div id=p3 style=display:none;position:absolute;bottom:0;top:0;width:100%>
<table cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0">
<tr style=padding:0>
<td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
<div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
<div class="menucurve"></div>
</div>
<div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
</td>
<td>
<div style=margin-left:5px>
<strong style="font-size:large"><span id=p3userName></span></strong><br />
</div>
</td>
</tr>
</table>
<div id=p3info style="overflow-y:auto;position:absolute;top:55px;bottom:0px;width:100%">
<img id="p2AccountImage" alt="" loading="lazy" width="128" height="128" onclick="account_manageImage(0)" src="images/user-256.png" style="position:absolute;right:8px;top:7px;border-radius:8px;box-shadow:0px 0px 7px #000" />
<div style="margin-left:8px">
<div id="p3AccountActions">
<div id="p2AccountSecurity" style="display:none">
<p><strong>Account Security</strong></p>
<div style="margin-left:9px;margin-bottom:8px">
<div id="managePhoneNumber1" style="margin-top:5px;display:none"><a onclick="account_managePhone()" style="cursor:pointer">Manage phone number</a> <span id="authPhoneNumberCheck"><strong>&#x2713;</strong></span></div>
<div id="manageEmail2FA" style="margin-top:5px;display:none"><a onclick="account_manageAuthEmail()" style="cursor:pointer">Manage email authentication</a> <span id="authEmailSetupCheck"><strong>&#x2713;</strong></span></div>
<div id="manageAuthApp" style="margin-top:5px;display:none"><a onclick="account_manageAuthApp()" style="cursor:pointer">Manage authenticator app</a> <span id="authAppSetupCheck"><strong>&#x2713;</strong></span></div>
<div id="manageOtp" style="margin-top:5px;display:none"><a onclick="account_manageOtp(0)" style="cursor:pointer">Manage backup codes</a> <span id="authCodesSetupCheck"><strong>&#x2713;</strong></span></div>
<div style="margin-top:5px"><a href=# onclick="return account_viewPreviousLogins()">View previous logins</a></div>
</div>
</div>
<div id="p2AccountActions" style="display:none">
<p><strong>Account Actions</strong></p>
<div style="margin-left:9px;margin-bottom:8px">
<div style="margin-top:5px"><span id="managePhoneNumber2" style="display:none"><a onclick="account_managePhone()" style="cursor:pointer">Manage phone number</a></span></div>
<div style="margin-top:5px"><span id="verifyEmailId" style="display:none"><a onclick="account_showVerifyEmail()" style="cursor:pointer">Verify email</a></span></div>
<div style="margin-top:5px"><span id="changeEmailId" style="display:none"><a onclick="account_showChangeEmail()" style="cursor:pointer">Change email address</a></span></div>
<div style="margin-top:5px"><a onclick="account_showChangePassword()" style="cursor:pointer">Change password</a><span id="p2nextPasswordUpdateTime"></span></div>
<div style="margin-top:5px"><a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a></div>
<div style="margin-top:5px" id="setDarkModeLink"><a onclick="toggleNightMode()" style="cursor:pointer">Set dark mode</a></div>
<div style="margin-top:5px"><a onclick="showNotes(false)" style="cursor:pointer">Personal notes</a></div>
</div>
<br style=clear:both />
</div>
</div>
<strong>Device Groups</strong>
<span id="p3createMeshLink1">( <a onclick=account_createMesh() style=cursor:pointer><img src="images/icon-addnew.png" width=12 height=12 border=0 /> New</a> )</span>
<br /><br />
<div id=p3meshes></div>
<div id=p3noMeshFound style=margin-left:9px;display:none>No device groups.<span id="p3createMeshLink2"> <a onclick=account_createMesh() style=cursor:pointer><strong>Get started here!</strong></a></span></div>
<br style=clear:both />
</div>
</div>
</div>
<div id=p5 style=display:none>
<table cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0;">
<tr style=padding:0>
<td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
<div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
<div class="menucurve"></div>
</div>
<div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
</td>
<td>
<img src="/images/user-50.png" width=50 height=50 />
</td>
<td>
<div style=margin-left:5px>
<strong style="font-size:large">My Files</strong><br />
</div>
</td>
</tr>
</table>
<div id=p5myfiles style="position:absolute;top:55px;bottom:0px;width:100%">
<table id="p5toolbar" style="width:100%;height:78px" cellpadding="0" cellspacing="0">
<tr>
<td style="width:100%;background-color:#d3d9d6;text-align:left;padding:4px" valign=bottom>
<div style="width:100%;text-align:center">
<input type=button style="width:calc(100%/5 - 5px)" id=p5FolderUp disabled="disabled" onclick="p5folderup()" value="Up" />
<input type=button style="width:calc(100%/5 - 5px)" id=p5SelectAllButton disabled="disabled" onclick="p5selectallfile()" value="SelectAll" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p5RenameFileButton disabled="disabled" value="Rename" onclick="p5renamefile()" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p5DeleteFileButton disabled="disabled" value="Delete" onclick="p5deletefile()" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p5NewFolderButton disabled="disabled" value="Folder" onclick="p5createfolder()" onkeypress="return false" onkeydown="return false" />
</div>
<div style="width:100%;text-align:center">
<input type=button style="width:calc(100%/5 - 5px)" id=p5UploadButton disabled="disabled" value="Upload" onclick="p5uploadFile()" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p5CutButton disabled="disabled" value="Cut" onclick="p5copyFile(1)" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p5CopyButton disabled="disabled" value="Copy" onclick="p5copyFile(0)" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p5PasteButton disabled="disabled" value="Paste" onclick="p5pasteFile()" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p5RefreshButton value="Refresh" onclick="p5refreshFiles()" onkeypress="return false" onkeydown="return false" />
</div>
</td>
</tr>
<tr>
<td style="background-color:#E4E9E7;height:28px">
<table style="width:100%">
<tr>
<td id=p5currentpath style="overflow:hidden;padding-left:4px;padding-top:2px;color:black"></td>
<td style="text-align:right;padding-right:4px">
<select id=p5sortdropdown onchange=updateFiles()>
<option value=1 selected="selected">Sort by name</option>
<option value=2>Sort by size</option>
<option value=3>Sort by date</option>
<option value=4>Descend by name</option>
<option value=5>Descend by size</option>
<option value=6>Descend by date</option>
</select>
</td>
</tr>
</table>
</td>
</tr>
</table>
<div id="p5filetable" style="width:100%;height:calc(100% - 102px);overflow:auto;-webkit-user-select:none">
<!--
<div id="p5bigok" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&checkmark;</b></div>
<div id="p5bigfail" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&#10007;</b></div>
-->
<span id="p5files"></span>
</div>
<table id="p5toolbarBottom" style="width:100%;height:22px;position:absolute;bottom:0px;background-color:#D3D9D6" cellpadding=0 cellspacing=0>
<tr>
<td style="text-align:left;padding:3px">&nbsp;<span id="p5bottomstatus"></span></td>
<td id="p5rightOfButtons" style="text-align:right;padding:3px"></td>
</tr>
</table>
</div>
</div>
<div id=p10 style=display:none;position:absolute;bottom:0;top:0;width:100%;overflow:hidden>
<table id=p10deskTopTable cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0;position:absolute;top:0">
<tr style=padding:0>
<td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
<div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
<div class="menucurve"></div>
</div>
<div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
</td>
<td>
<a id=MainComputerImage style=cursor:pointer onclick=p10showiconselector()></a>
</td>
<td>
<div style=margin-left:5px>
<strong><span id=p10deviceName></span></strong><br />
<span id=MainComputerState></span>
</div>
</td>
</tr>
</table>
<div id=p10dialog 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:30px;width:300px;left:30px;display:none">
<div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
<div style=padding:5px>Keyboard Shortcuts Customization</div>
<div style=width:100%;margin:6px></div>
</div>
<div style="margin-right:16px;margin-left:8px"><div id=p10dialog2 style="margin:auto;margin:3px"></div></div>
<div style="padding:10px;margin-bottom:20px"><input type="button" value="OK" style="float:right;width:80px" onclick="deskCustomizeKeysEx()"></div>
</div>
<div id=p10general style="overflow-y:scroll;position:absolute;top:55px;bottom:0px;width:100%">
<div class="deviceNotifyLargeDot">
<img id="p10deviceStar" class=deviceNotifyLargeDotSub src=images/icon-star-notify-40.png width=35 height=35>
<div id="p10deviceMsg" onclick=showDeviceMessages(null,null,event) class=deviceNotifyLargeDotSub></div>
<img id="p10deviceNotify" onclick=showDeviceSessions() class=deviceNotifyLargeDotSub src=images/icon-relay-notify-40.png width=35 height=35>
<img id="p10deviceHelp" onclick=showDeviceHelpRequests(null,null,event) class=deviceNotifyLargeDotSub src=images/icon-help-notify-40.png width=35 height=35>
</div>
<div id="p10deviceBattery" class="deviceBatteryLarge deviceBatteryLarge1"></div>
<div id=p10html style="margin-left:8px;margin-right:8px"></div>
<div id=p10html2></div>
<div id=p10html3 style="margin-left:8px"></div>
</div>
<img id="deskkeybutton1" src="images/mobile-desk-exit.png" class="deskButton" style="top:10px;display:none" onclick="exitButton()" />
<img id="deskkeybutton3a" src="images/mobile-desk-menu-open.png" class="deskButton" style="top:60px;display:none" onclick="toggleMenu(false)" />
<img id="deskkeybutton3b" src="images/mobile-desk-menu-close.png" class="deskButton" style="top:60px;display:none" onclick="toggleMenu(true)" />
<img id="deskkeybutton4a" src="images/mobile-desk-mouse-left.png" class="deskButton" style="top:110px;display:none" onclick="deskChangeMouseButton(0)" />
<img id="deskkeybutton4b" src="images/mobile-desk-mouse-right.png" class="deskButton" style="top:110px;display:none" onclick="deskChangeMouseButton(1)" />
<img id="deskkeybutton5a" src="images/mobile-desk-scale-out.png" class="deskButton" style="top:160px;display:none" onclick="deskChangeFullscreenZoom()" />
<img id="deskkeybutton5b" src="images/mobile-desk-scale-in.png" class="deskButton" style="top:160px;display:none" onclick="deskChangeFullscreenZoom()" />
<img id="deskkeybutton2a" src="images/mobile-desk-keyboard-open.png" class="deskButton" style="top:210px;display:none" onclick="toggleKeyboard()" />
<img id="deskkeybutton2b" src="images/mobile-desk-keyboard-close.png" class="deskButton" style="top:210px;display:none" onclick="toggleKeyboard()" />
<div style="position:absolute;top:0;left:0;z-index:200;opacity:0;width:1px;height:1px">
<input id="softKeyboard" autocapitalize="off" autocomplete="off" type="text" spellcheck="false" style="z-index:200;opacity:0;width:1px;height:1px" onfocus="keyboardFocusChange()" onblur="keyboardFocusChange()" />
</div>
<div id="deskButtonMenu" style="display:none;position:absolute;top:10px;left:10px;right:55px;bottom:10px;z-index:1000"></div>
<div id=p10desktop style="overflow:hidden;position:absolute;top:55px;bottom:0px;width:100%;display:none">
<div id=deskarea1 style="position:absolute;top:0px;width:100%;height:32px">
<div style="padding-top:2px;padding-bottom:2px;background:#C0C0C0;height:32px">
<div style="float:right;text-align:right">
<span id="p14power"></span>&nbsp;
<input type=button id=deskFullScreen value="Full Screen" onclick=deskToggleFull(event) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px;margin-right:3px;">
</div>
<div style="margin-left:3px">
<input type=button id=connectbutton1 value="Connect" onclick=connectDesktop(event,3) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px">
<input type=button id=connectbutton1h value="HW Connect" onclick=connectDesktop(event,2) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px">
<input type=button id=disconnectbutton1 value="Disconnect" onclick=connectDesktop(event,0) onkeypress="return false" onkeydown="return false" style="height:28px">
<span id="deskstatus" style="color:black">Disconnected</span>
</div>
</div>
</div>
<div id=deskarea3 style="position:absolute;top:32px;width:100%;height:calc(100% - 64px);background-color:#000;text-align:center">
<div id=DeskParent style="height:100%">
<canvas id=Desk width=640 height=200 style="width:100%;-ms-touch-action:none;margin-left:0px" oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event) onmousewheel=dmousewheel(event)></canvas>
</div>
<div id=p11DeskConsoleMsg style="display:none;cursor:pointer;position:absolute;left:30px;top:17px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px;text-align:left" onclick=p11clearConsoleMsg()></div>
<div id=p11DeskSessionSelector style="display:none;position:absolute;left:30px;top:17px;right:30px;bottom:17px;overflow-y:auto"></div>
</div>
<div id=deskarea4 style="position:absolute;bottom:0px;width:100%;height:32px">
<div style=padding-top:2px;padding-bottom:2px;background:#C0C0C0>
<div style=float:right;text-align:right>
<span id=DeskChatButton><img src='images/icon-chat.png' onclick=deviceChat(event) height=16 width=16 style=padding-top:5px;cursor:pointer /></span>&nbsp;
<span id=DeskToastButton><img src='images/icon-notify.png' onclick=deviceToastFunction() height=16 width=16 style=padding-top:5px;cursor:pointer /></span>&nbsp;
<!--<input id=DeskToolsButton type=button value=Tools onkeypress="return false" onkeydown="return false" onclick="toggleDeskTools()">&nbsp;-->
</div>
<div>
<input id="deskActionsBtn" type=button style="margin-left:3px;height:28px" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() />
<input type="button" value="Settings" onkeypress="return false" onkeydown="return false" onclick="showDesktopSettings()" style="height:28px">
<input type="button" onkeypress="return false" onkeydown="return false" value="Power Actions..." onclick="showPowerActionDlg()" style="display:none;height:28px">
<!--<input type="button" id="DeskSpecialKeys" value="Keys" onkeypress="return false" onkeydown="return false" onclick="sendSpecialKeys()" style="height:28px">-->
<input type="button" id="DeskScreens" value="Screens" onkeypress="return false" onkeydown="return false" onclick="deskSelectScreens()" style="display:none;height:28px">
<label><span id="DeskControlSpan" style="display:none"><input id="DeskControl" type="checkbox" onkeypress="return false" onkeydown="return false">Input</span></label>
</div>
</div>
</div>
</div>
<div id="termButtonMenu" style="display:none;position:absolute;top:10px;left:10px;right:55px;bottom:10px;z-index:1000"></div>
<div id=p10terminal style="overflow:hidden;position:absolute;top:55px;bottom:0px;width:100%;display:none;background-color:#333">
<div id=termTable style="position:absolute;top:0;bottom:0;left:0;right:0">
<div id="termarea1">
<div class="areaHead" style="line-height:24px">
<div class="toright2">
<input type=button id=termFullScreen value="Full Screen" onclick=deskToggleFull(event) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px;margin-right:3px;">
<div id="terminalCustomUpperRight" style="float:left;margin-right:6px"></div>
</div>
<div>
<span id="connectbutton2span" style="margin-left:3px"><input type="button" id="connectbutton2" value="Connect" style="height:28px" onclick=connectTerminal(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
<span id="connectbutton2sspan" style="margin-right:4px"><input type="button" id="connectbutton2s" value="SSH Connect" style="height:28px" onclick=connectTerminal(event,3) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
<span id="disconnectbutton2span" style="margin-left:3px"><input type="button" id="disconnectbutton2" value="Disconnect" style="height:28px" onclick=connectTerminal(event,0) onkeypress="return false" onkeydown="return false" /></span>
<span id="termstatus" style="line-height:22px">Disconnected</span><span id="termtitle"></span>
</div>
</div>
</div>
<div id="termarea3" style="width:100%;height: calc(100% - 60px);" cellpadding=0 cellspacing=0>
<div id="termarea3x" style="width:100%;height:100%">
<div style="width:100%;height:100%;text-align:left" id="termarea3xdiv"></div>
</div>
</div>
<div id="termarea4" style="position:relative;height:32px;">
<div class="areaFoot">
<div class="toright2"></div>
<div style="height:28px">
<input id="termActionsBtn" style="margin-left:3px;height:28px" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() />
</div>
</div>
</div>
<div id=p12TermConsoleMsg style="display:none;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>
</div>
</div>
<div id=p10files style="position:absolute;top:55px;bottom:0px;width:100%;display:none">
<table id="p13toolbar" style="width:100%;height:111px" cellpadding="0" cellspacing="0">
<tr>
<td style="background-color:#C0C0C0;border-bottom:2px solid black;padding:2px;line-height:24px">
<div style="float:right;text-align:right">
<input id="filesActionsBtn" type=button onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() style=margin-right:2px />
<div id="filesCustomUpperRight" style="float:left;margin-right:6px"></div>
</div>
<div style="margin-left:2px">
<input id=p13AutoConnect value="AutoConnect" onclick=autoConnectFiles(event) onkeypress="return false" onkeydown="return false" type="button" style="display:none">
<input id=p13Connect value="Connect" onclick=connectFiles(event,1) onkeypress="return false" onkeydown="return false" type="button" />
<input id=p13Connects value="SFTP Connect" onclick=connectFiles(event,2) onkeypress="return false" onkeydown="return false" type="button" />
<input id=p13Disconnect value="Disconnect" onclick=connectFiles(event) onkeypress="return false" onkeydown="return false" type="button" />
<span class=textOverGray id=p13Status>Disconnected</span>
</div>
</td>
</tr>
<tr>
<td style="width:100%;background-color:#d3d9d6;text-align:left;padding:4px" valign=bottom>
<div style="width:100%;text-align:center">
<input type=button style="width:calc(100%/5 - 5px)" id=p13FolderUp disabled="disabled" onclick="p13folderup()" value="Up" />
<input type=button style="width:calc(100%/5 - 5px)" id=p13SelectAllButton disabled="disabled" onclick="p13selectallfile()" value="SelectAll" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p13RenameFileButton disabled="disabled" value="Rename" onclick="p13renamefile()" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p13DeleteFileButton disabled="disabled" value="Delete" onclick="p13deletefile()" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p13NewFolderButton disabled="disabled" value="Folder" onclick="p13createfolder()" onkeypress="return false" onkeydown="return false" />
</div>
<div style="width:100%;text-align:center">
<input type=button style="width:calc(100%/5 - 5px)" id=p13UploadButton disabled="disabled" value="Upload" onclick="p13uploadFile()" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p13CutButton disabled="disabled" value="Cut" onclick="p13copyFile(1)" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p13CopyButton disabled="disabled" value="Copy" onclick="p13copyFile(0)" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p13PasteButton disabled="disabled" value="Paste" onclick="p13pasteFile()" onkeypress="return false" onkeydown="return false" />
<input type=button style="width:calc(100%/5 - 5px)" id=p13RefreshButton disabled="disabled" value="Refresh" onclick="p13folderup(9999)" onkeypress="return false" onkeydown="return false" />
</div>
</td>
</tr>
<tr>
<td style="background-color:#E4E9E7;height:28px">
<table style="width:100%">
<tr>
<td id=p13currentpath style="overflow:hidden;padding-left:4px;padding-top:2px;color:black"></td>
<td style="text-align:right;padding-right:4px">
<select id=p13sortdropdown onchange=p13updateFiles()>
<option value=1 selected="selected">Sort by name</option>
<option value=2>Sort by size</option>
<option value=3>Sort by date</option>
<option value=4>Descend by name</option>
<option value=5>Descend by size</option>
<option value=6>Descend by date</option>
</select>
</td>
</tr>
</table>
</td>
</tr>
</table>
<div id=p13FilesConsoleMsg style="display:none;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>
<div id="p13filetable" style="width:100%;height:calc(100% - 133px);overflow:auto;-webkit-user-select:none">
<!--
<div id="p13bigok" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&checkmark;</b></div>
<div id="p13bigfail" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&#10007;</b></div>
-->
<span id="p13files"></span>
</div>
<table id="p13toolbarBottom" style="width:100%;height:22px;position:absolute;bottom:0px" cellpadding=0 cellspacing=0>
<tr><td style="text-align:left;padding:3px;text-align:center;background-color:#D3D9D6;color:black">&nbsp;<span id="p13bottomstatus"></span></td></tr>
</table>
</div>
<div id=p10details style="overflow-y:scroll;position:absolute;top:55px;bottom:0px;width:100%">
<div id=p10detailshtml style="margin-left:-3px"></div>
</div>
<div id=p10console style="overflow:hidden;position:absolute;top:55px;bottom:0px;width:100%">
<table id="consoleTable" cellpadding=0 cellspacing=0>
<tr style="height:28px">
<td class="areaHead">
<div class="toright2">
<div id=p15coreName></div>
<input type=button id=p15uploadCore value="Agent Action" onclick=p15uploadCore(event) />
</div>
<div id="p15statetext"></div>
</td>
</tr>
<tr>
<td id=p15agentConsole style="position:relative">
<pre id=p15agentConsoleText></pre>
</td>
</tr>
<tr style="height:28px">
<td class="areaFoot">
<table style="width:100%">
<tr>
<td style="width:99%">
<input id=p15consoleText style=width:100%;box-sizing:border-box onkeyup=p15consoleSend(event) />
</td>
<td id="p15outputselecttd">
<select id=p15outputselect onchange="setupConsole()">
<option id="p15outputselect1" value=1>Agent</option>
<option id="p15outputselect3" value=3>Push</option>
<option id="p15outputselect2" value=2>MQTT</option>
</select>
</td>
<td style="width:1%"><input id="id_p15consoleClear" type="button" class="bottombutton" value="Clear" onclick="p15consoleClear()" /></td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>
<div id=p20 style="display:none;position:absolute;bottom:0;top:0;width:100%">
<table cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0;position:absolute;top:0">
<tr style=padding:0>
<td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
<div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
<div class="menucurve"></div>
</div>
<div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
</td>
<td onclick="p20editmesh(1)">
<img src="/images/meshicon50.png" width=50 height=50 />
</td>
<td onclick="p20editmesh(1)">
<div style=margin-left:5px>
<strong style="font-size:large"><span id=p20meshName></span></strong><br />
</div>
</td>
</tr>
</table>
<div style="overflow-y:auto;position:absolute;top:55px;bottom:0px;left:0px;right:0px">
<div id=p20info style="margin-left:8px;margin-right:8px"></div>
</div>
</div>
</div>
</div>
<div id=footer style="height:32px;width:100%;text-align:center;background-color:#113962;position:absolute;bottom:0px">
<table id=footerMenu cellpadding=0 cellspacing=0 style="height:32px;width:100%;color:white;cursor:pointer;table-layout:fixed"></table>
</div>
</div>
<div id=dialog style="display:none">
<div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
<div id=id_dialogclose style=float:right;padding:5px;cursor:pointer onclick=setDialogMode()><b>X</b></div>
<div id=id_dialogtitle style=padding:5px></div>
<div style=width:100%;margin:6px></div>
</div>
<div style="margin-right:16px;margin-left:8px">
<div id=dialog1 style="margin:auto;text-align:center;margin:3px">
<div id=id_dialogMessage style="padding:10px"></div>
</div>
<div id=dialog2 style="margin:auto;margin:3px">
<div id=id_dialogOptions></div>
</div>
<div id=dialog3 style="margin:auto;margin:3px">
<select id="deskkeys" style="width:100%">
<option value=10>Ctrl+Alt+Del</option>
<option value=11>Tab</option>
<option value=5>Win</option>
<option value=0>Win+Down</option>
<option value=1>Win+Up</option>
<option value=2>Win+L</option>
<option value=3>Win+M</option>
<option value=4>Shift+Win+M</option>
<option value=6>Win+R</option>
<option value=7>Alt-F4</option>
<option value=8>Ctrl-W</option>
<option value=9>Alt-Tab</option>
<option value=12>Shift-F10</option>
</select>
</div>
<div id=dialog7 style="margin:auto;margin:3px">
<div id="d7meshkvm">
<h4 style="width:100%;border-bottom:1px solid gray">Agent Remote Desktop</h4>
<table style="width:100%">
<tr>
<td>
Quality
</td>
<td style="width:100px">
<select id="d7bitmapquality" style="float:right;width:200px" dir="rtl"></select>
</td>
</tr>
<tr>
<td>
Scaling
</td>
<td style="width:100px">
<select id="d7bitmapscaling" style="float:right;width:200px" dir="rtl">
<option selected=selected value=1024>100%</option>
<option value=896>87.5%</option>
<option value=768>75%</option>
<option value=640>62.5%</option>
<option value=512>50%</option>
<option value=384>37.5%</option>
<option value=256>25%</option>
<option value=128>12.5%</option>
</select>
</td>
</tr>
<tr>
<td>
Rate
</td>
<td style="width:100px">
<select id="d7framelimiter" style="float:right;width:200px" dir="rtl">
<option selected=selected value=50>Fast</option>
<option value=100>Medium</option>
<option value=400>Slow</option>
<option value=1000>Very slow</option>
</select>
</td>
</tr>
<tr>
<td></td>
<td>
<label style="display:block" id="d7deskAutoLockLabel"><input type="checkbox" id="d7deskAutoLock" />Lock on Disconnect</label>
</td>
</tr>
</table>
</div>
<div id="d7amtkvm">
<h4 style="width:100%;border-bottom:1px solid gray">Intel&reg; AMT Hardware KVM</h4>
<table style="width:100%">
<tr>
<td>Encoding</td>
<td style="width:100px">
<select id="d7desktopmode" style="float:right;width:200px">
<option value="1">RLE8, Fastest</option>
<option value="2">RLE16, Recommended</option>
<option value="3">RAW8, Slow</option>
<option value="4">RAW16, Very Slow</option>
</select>
</td>
</tr>
</table>
</div>
</div>
</div>
<div id="idx_dlgButtonBar" style="padding:10px;margin-bottom:20px">
<input id="idx_dlgCancelButton" type="button" value="Cancel" style="float:right;width:80px;margin-left:5px" onclick="dialogclose(0)">
<input id="idx_dlgOkButton" type="button" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">
<div><input id="idx_dlgDeleteButton" type="button" value="Delete" style="display:none" onclick="dialogclose(2)" /></div>
</div>
</div>
<div id=topMenu style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:0px 0px 5px 5px;position:fixed;top:50px;right:5px;width:170px;display:none">
<div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer" onclick=topMenu(2)>My Files</div>
<div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer" onclick=topMenu(1)>My Account</div>
<div id="logoutMenuOption"><a id="logoutMenuOptionRef" href=/logout><div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer">Logout</div></a></div>
</div>
<audio id="chimes"><source src="sounds/chimes.mp3" type="audio/mp3" /></audio>
<iframe name="fileUploadFrame" style=display:none></iframe>
<script>
'use strict';
var random = '{{{randomlength}}}' // Random length string for BREACH mitigation
// Process server-side web state
var webState = '{{{webstate}}}';
if (webState != '') { webState = JSON.parse(decodeURIComponent(webState)); }
for (var i in webState) { localStorage.setItem(i, webState[i]); }
if (webState && !webState.loctag) { delete localStorage.removeItem('loctag'); }
// Fetch URL arguments & do sanitation
var urlargs = parseUriArgs();
if (urlargs.key != null) { urlargs.key = "" + urlargs.key; }
if (urlargs.key && (isAlphaNumeric(urlargs.key) == false)) { delete urlargs.key; }
if (urlargs.locale && (isAlphaNumeric(urlargs.locale) == false)) { delete urlargs.locale; }
delete urlargs.user;
delete urlargs.pass;
delete urlargs.viewmode;
delete urlargs.gotonode;
delete urlargs.gotodevicename;
delete urlargs.gotomesh;
delete urlargs.panel;
// Check if we are in debug mode
var args = parseUriArgs();
if (args.key && (isAlphaNumeric(args.key) == false)) { delete args.key; }
if (args.locale && (isAlphaNumeric(args.locale) == false)) { delete args.locale; }
var debugLevel = parseInt('{{{debuglevel}}}');
var features = parseInt('{{{features}}}');
var features2 = parseInt('{{{features2}}}');
var sessionTime = parseInt('{{{sessiontime}}}');
var sessionRefreshTimer = null;
var domain = '{{{domain}}}';
var domainUrl = '{{{domainurl}}}';
var authCookie = '{{{authCookie}}}';
var authRelayCookie = '{{{authRelayCookie}}}';
var logoutControls = JSON.parse(decodeURIComponent('{{{logoutControls}}}'));
var authCookieRenewTimer = null;
var webRelayPort = parseInt('{{{webRelayPort}}}');
var webRelayDns = '{{{webRelayDns}}}';
var meshserver = null;
var xdr = null;
var usergroups = null;
var stars = {}; // Devices that have been "stared" by the user.
var serverinfo = null;
var nodes = [];
var meshes = {};
var filetree = {};
var userinfo = null;
var serverinfo = null;
var users = null;
var nodeShortIdent = 0;
var serverPublicNamePort = '{{{serverDnsName}}}:{{{serverPublicPort}}}';
var debugmode = false;
var attemptWebRTC = ((features & 128) != 0);
var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected", "Intel&reg; AMT Connected"];
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", "macOS x86-32bit", "Android x86", "PogoPlug ARM", "Android", "Linux Poky x86-32bit", "macOS x86-64bit", "ChromeOS", "Linux Poky x86-64bit", "Linux NoKVM x86-32bit", "Linux NoKVM x86-64bit", "Windows MinCore console", "Windows MinCore service", "NodeJS", "ARM-Linaro", "ARMv6l / ARMv7l", "ARMv8 64bit", "ARMv6l / ARMv7l / NoKVM", "MIPS24KC (OpenWRT)", "Apple Silicon", "FreeBSD x86-64", "Unknown", "Linux ARM 64 bit (glibc/2.24 NOKVM)", "Alpine Linux x86 64 Bit (MUSL)", "Assistant (Windows)", "Armada370 - ARM32/HF (libc/2.26)", "OpenWRT x86-64", "OpenBSD x86-64", "Unknown", "Unknown", "MIPSEL24KC (OpenWRT)", "ARMADA/CORTEX-A53/MUSL (OpenWRT)"];
var files;
var terminal;
var passRequirements = '{{{passRequirements}}}';
if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); }
var sessionActivity = Date.now();
var deskPinchZoom;
var deskKeyboardShortcuts = [];
var nightMode = setNightMode();
var xterm = null;
var xtermfit = null;
var xtermResizeTimer = null;
var devicePagingState = null;
// Console Message Display Timers
var p11DeskConsoleMsgTimer = null;
var p12TermConsoleMsgTimer = null;
var p13FilesConsoleMsgTimer = null;
// Check if WebP is supported
var webpSupport = false;
check_webp_feature('lossy', function (f, x) { webpSupport = x; });
function startup() {
if ((features & 32) == 0) {
// Guard against other site's top frames (web bugs).
var loc = null;
try { loc = top.location.toString().toLowerCase(); } catch (e) { }
if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
}
if (!args.locale) { var x = getstore('loctag', 0); if ((x != null) && (x != '*')) { args.locale = x; } }
window.onresize = center;
center();
QV('changeEmailId', (features & 0x200000) == 0);
QH('p1message', "Connecting...");
go(1);
// Document keys
document.onkeypress = ondeskkeypress;
document.onkeydown = ondeskkeydown;
document.onkeyup = ondeskkeyup;
document.onclick = function (e) { if ((xxdialogMode == 999) && (e.target.id != 'topMenuIcon')) { QV('topMenu', false); xxdialogMode = 0; } }
// Connect to the mesh server
meshserver = MeshServerCreateControl(domainUrl);
meshserver.onStateChanged = onStateChanged;
meshserver.onMessage = onMessage;
meshserver.trace = args.trace;
meshserver.Start();
// Setup stared devices
try { stars = JSON.parse(getstore('stars', '{}')); } catch (ex) { }
// Setup logout control
if (logoutControls && logoutControls.logoutUrl) { Q('logoutMenuOptionRef').href = logoutControls.logoutUrl; }
// Load desktop settings
var t = localStorage.getItem('desktopsettings');
if (t != null) { desktopsettings = JSON.parse(t); }
applyDesktopSettings();
//attemptWebRTC = false; // For now, default WebRTC off unless we set it in the URL.
if (args.webrtc != null) { attemptWebRTC = (args.webrtc == 1); }
// Session Refresh Timer
if (sessionTime >= 10) { sessionRefreshTimer = setTimeout(refreshCookieSession, Math.round((sessionTime * 60000) * 0.8)); }
// Hide night mode button if needed
QV('setDarkModeLink', (features2 & 0x00300000) == 0);
// Set the user's desktop shortcut keys
deskKeyboardShortcuts = [];
var deskKeyboardShortcutsStr = getstore('deskKeyShortcuts', '0x0A002E,0x100000,0x100028,0x100026,0x10004C,0x10004D,0x11004D,0x100052,0x020073,0x080057,0x020009,0x100025,0x100027').split(',');
for (var i in deskKeyboardShortcutsStr) { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); }
updateDeskShortcutKeys();
updateTermShortcutKeys();
}
function refreshCookieSession() {
var xdr = null;
try { xdr = new XDomainRequest(); } catch (e) { }
if (!xdr) xdr = new XMLHttpRequest();
xdr.open('GET', window.location.origin + domainUrl + 'refresh.ashx');
xdr.timeout = 15000;
xdr.onload = function () { sessionRefreshTimer = setTimeout(refreshCookieSession, Math.round((sessionTime * 60000) * 0.8)); };
xdr.onerror = xdr.ontimeout = function () { sessionRefreshTimer = null; };
xdr.send();
}
function onStateChanged(server, state, prevState, errorCode) {
if (state == 0) {
// Control web socket disconnected
setDialogMode(0); // Close any dialog boxes if present
go(0); // Go to disconnection panel
deleteAllNotifications(); // Close and clear notifications if present
if (errorCode == 'noauth') { QH('p0span', "Unable to perform authentication"); return; }
if (prevState == 2) { setTimeout(serverPoll, 5000); } else { QH('p0span', "Unable to connect web socket"); }
// Clean up here
if (authCookieRenewTimer != null) { clearInterval(authCookieRenewTimer); authCookieRenewTimer = null; }
devicePagingState = null;
updateDevicePageState();
} else if (state == 2) {
// Fetch list of meshes, nodes, files
meshserver.send({ action: 'usergroups' });
meshserver.send({ action: 'meshes' });
meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip });
meshserver.send({ action: 'files' });
authCookieRenewTimer = setInterval(function () { meshserver.send({ action: 'authcookie' }); }, 1800000); // Request a cookie refresh every 30 minutes.
}
QV('topMenuIcon', state == 2);
}
// Poll the server, if it responds, refresh the page.
function serverPoll() {
xdr = null;
try { xdr = new XDomainRequest(); } catch (e) { }
if (!xdr) xdr = new XMLHttpRequest();
xdr.open('HEAD', window.location.href);
xdr.timeout = 15000;
// Make sure there isn't a reverse proxy in front that may just be returning 5xx codes
// Status code 4xx should still be allowed, since a page could potentially be removed, etc
xdr.onload = function () { if (xdr.status < 500) reload(); else setTimeout(serverPoll, 10000); };
xdr.onerror = xdr.ontimeout = function () { setTimeout(serverPoll, 10000); };
xdr.send();
}
function updateSelf() {
var accountSettingsLocked = ((features2 & 0x100) != 0);
if (userinfo) { accountSettingsLocked = ((userinfo.siteadmin != 0xFFFFFFFF) && ((userinfo.siteadmin & 1024) != 0)) || ((features2 & 0x100) != 0); } // Not admin and have account features locked, or using a loginToken
QV('p3AccountActions', ((features & 4) == 0) && (serverinfo.domainauth == false) && (accountSettingsLocked == false)); // Hide Account Actions if in single user mode or domain authentication
QV('logoutMenuOption', ((features & 4) == 0) && (serverinfo.domainauth == false)); // Hide logout if in single user mode or domain authentication
QV('p2AccountSecurity', ((features & 4) == 0) && (serverinfo.domainauth == false) && ((features & 4096) != 0) && (accountSettingsLocked == false)); // Hide Account Security if in single user mode or domain authentication, 2 factor auth not supported.
QV('p2AccountImage', !accountSettingsLocked);
QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
QV('manageAuthApp', (serverinfo.lock2factor != true) && (features & 4096) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
QV('manageOtp', (serverinfo.lock2factor != true) && ((features2 & 0x40000) == 0) && (features & 4096) && ((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0)));
QV('authPhoneNumberCheck', (userinfo.phone != null));
QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
QV('authAppSetupCheck', userinfo.otpsecret == 1);
//QV('authKeySetupCheck', userinfo.otphkeys > 0);
QV('authCodesSetupCheck', userinfo.otpkeys > 0);
QV('p2AccountActions', ((features & 4) == 0) && (serverinfo.domainauth == false) && (userinfo != null) && (userinfo._id.split('/')[2].startsWith('~') == false));
// On the mobile app, don't allow group creation (for now).
QV('p3createMeshLink1', false);
QV('p3createMeshLink2', false);
// Update user image
if ((userinfo.flags != null) && (userinfo.flags & 1)) {
if (userinfo.accountImageRnd == null) { userinfo.accountImageRnd = Math.floor(Math.random() * 9999999999); }
Q('p2AccountImage').src = 'userimage.ashx?rnd=' + userinfo.accountImageRnd;
} else {
Q('p2AccountImage').src = 'images/user-256.png';
}
if (typeof userinfo.passchange == 'number') {
if (userinfo.passchange == -1) { QH('p2nextPasswordUpdateTime', " - Reset on next login."); }
else if ((passRequirements != null) && (typeof passRequirements.reset == 'number')) {
var seconds = (userinfo.passchange) + (passRequirements.reset * 86400) - Math.floor(Date.now() / 1000);
if (seconds < 0) { QH('p2nextPasswordUpdateTime', " - Reset on next login."); }
else if (seconds < 3600) { var secs = Math.floor(seconds / 60); QH('p2nextPasswordUpdateTime',format((secs == 1)?" - Reset in 1 minute.":" - Reset in {0} minutes.", secs)); }
else if (seconds < 86400) { var hours = Math.floor(seconds / 3600); QH('p2nextPasswordUpdateTime', format((hours == 1) ? " - Reset in 1 hour." : " - Reset in {0} hours.", hours)); }
else { var days = Math.floor(seconds / 86400); QH('p2nextPasswordUpdateTime', format((hours == 1) ? " - Reset in 1 day." : " - Reset in {0} days.", days)); }
}
}
}
function setSessionActivity() { sessionActivity = Date.now(); }
function checkIdleSessionTimeout() { var delta = (Date.now() - sessionActivity); if (delta > serverinfo.timeout) { window.location.href = 'logout'; } }
function onMessage(server, message) {
switch (message.action) {
case 'serverinfo': {
serverinfo = message.serverinfo;
if (serverinfo.timeout) { setInterval(checkIdleSessionTimeout, 10000); checkIdleSessionTimeout(); }
if (userinfo != null) updateSelf();
if (serverinfo.certExpire != null) {
var days = Math.floor((serverinfo.certExpire - Date.now()) / 86400000);
if ((days >= 0) && (days < 20)) {
addNotification({ text: format("Certificate expires in {0} day(s)", days) });
}
}
// Arrange the user interface
QV('manageEmail2FA', (features & 0x00800000) && (serverinfo.lock2factor != true));
QV('managePhoneNumber1', (features & 0x02000000) && (features & 0x04000000) && (serverinfo.lock2factor != true));
QV('managePhoneNumber2', (features & 0x02000000) && !(features & 0x04000000) && (serverinfo.lock2factor != true));
break;
}
case 'authcookie': {
// Got an authentication cookie refresh
authCookie = message.cookie;
authRelayCookie = message.rcookie;
break;
}
case 'userinfo': {
userinfo = message.userinfo;
QH('p3userName', userinfo.name);
//updateSiteAdmin();
if (serverinfo != null) updateSelf();
break;
}
case 'users': {
users = {};
for (var m in message.users) { users[message.users[m]._id] = message.users[m]; }
if (currentUser != null) { currentUser = users[currentUser._id]; }
updateUsers();
break;
}
case 'wssessioncount': {
wssessions = message.wssessions;
updateUsers();
break;
}
case 'meshes': {
meshes = {};
for (var m in message.meshes) { meshes[message.meshes[m]._id] = message.meshes[m]; }
if (currentMesh != null) { currentMesh = meshes[currentMesh._id]; }
updateMeshes();
mainUpdate(4);
break;
}
case 'usergroups': {
var groupCount = 0;
if (Array.isArray(message.ugroups)) {
usergroups = {};
for (var i in message.ugroups) { groupCount++; usergroups[message.ugroups[i]._id] = message.ugroups[i]; }
if (groupCount == 0) { usergroups = null; }
} else {
usergroups = message.ugroups;
for (var i in message.ugroups) { groupCount++; }
if (groupCount == 0) { usergroups = null; }
}
//mainUpdate(8192);
break;
}
case 'files': {
filetree = setupBackPointers(message.filetree);
updateFiles();
//d3updatefiles();
break;
}
case 'nodes': {
nodes = [];
for (var m in message.nodes) {
for (var n in message.nodes[m]) {
message.nodes[m][n].namel = message.nodes[m][n].name.toLowerCase();
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; }
message.nodes[m][n].meshnamel = meshes[m]?meshes[m].name.toLowerCase():'*';
message.nodes[m][n].meshid = m;
message.nodes[m][n].state = (message.nodes[m][n].state) ? (message.nodes[m][n].state) : 0;
message.nodes[m][n].desc = message.nodes[m][n].desc;
if (!message.nodes[m][n].icon) message.nodes[m][n].icon = 1;
message.nodes[m][n].ident = ++nodeShortIdent;
nodes.push(message.nodes[m][n]);
}
}
// If we are currently looking at a node this is now gone, change the view.
if ((currentNode != null) && (IsNodeViewable(currentNode) == false)) { currentNode = null; go(2); }
// Change the reference to the current node
if (currentNode != null) { currentNode = getNodeFromId(currentNode._id); if (currentNode != null) { gotoDevice(currentNode._id, xxcurrentView, true); } else { go(2); } }
// Update device paging
devicePagingState = (message.totalcount == null) ? null : { total: message.totalcount, skip: message.skip, limit: message.limit };
updateDevicePageState();
//onSortSelectChange();
//onSearchInputChanged();
mainUpdate(4);
//refreshMap(false, true);
if (xxcurrentView == 0) { if ('{{viewmode}}' != '') { go(parseInt('{{viewmode}}')); } else { setDialogMode(0); go(2); } }
if ('{{currentNode}}' != '') { gotoDevice('{{currentNode}}', parseInt('{{viewmode}}')); }
break;
}
case 'powertimeline': {
if (message.nodeid != powerTimelineReq) break;
powerTimelineNode = message.nodeid;
powerTimeline = message.timeline;
powerTimelineUpdate = Date.now() + 300000; // Update every 5 minutes
for (var i in powerTimeline) { if (i % 2 == 1) { powerTimeline[i] = powerTimeline[i] * 1000; } } // Decompress time
if (currentNode._id == message.nodeid) { drawDeviceTimeline(); }
break;
}
case 'getsysinfo': {
if (message.nodeid != powerTimelineReq) break;
if (message.noinfo === true) {
updateDeviceDetails(getNodeFromId(message.nodeid));
} else {
updateDeviceDetails(getNodeFromId(message.nodeid), message.hardware);
}
break;
}
case 'lastconnect': {
var node = getNodeFromId(message.nodeid);
if (node != null) {
node.lastconnect = message.time;
node.lastaddr = message.addr;
}
break;
}
case 'msg': {
// Check if this is a message from a node
if (message.nodeid != null) {
var index = -1;
if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == message.nodeid) { index = i; break; } } }
if (index != -1) {
if (message.type == 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
else if (message.type == 'notify') { // This is a notification message.
var n = getstore('notifications', 0);
if (((n & 8) == 0) && (message.amtMessage != null)) { break; } // Intel AMT desktop & terminal messages should be ignored.
var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
if (message.id != null) { n.id = message.id; }
if (message.nodeid != null) { n.nodeid = message.nodeid; }
if (message.tag != null) { n.tag = message.tag; }
if (message.url != null) { n.url = message.url; }
if (message.username != null) { n.username = message.username; }
if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
addNotification(n);
} else if ((message.type == 'userSessions') && (currentNode != null) && (currentNode._id == message.nodeid) && (desktop == null)) {
// Got list of user sessions
var userSessions = [];
if (message.data != null) { for (var i in message.data) { if ((message.data[i].State == 'Active') || (message.data[i].StationName == 'Console') || (debugmode == 3)) { userSessions.push(message.data[i]); } } }
if (userSessions.length == 0) { connectDesktop(null, 1, null, message.tag); } // No active sessions, do a normal connection.
else if (userSessions.length == 1) { connectDesktop(null, 1, userSessions[0].SessionId, message.tag); } // One active session, connect to it
else {
var x = '';
for (var i in userSessions) {
x += '<div style="text-align:left;cursor:pointer;background-color:gray;margin:5px;padding:5px;border-radius:5px" onclick=connectDesktop(event,1,' + userSessions[i].SessionId + ',' + message.tag + ')>' + userSessions[i].State + ', ' + userSessions[i].StationName;
if (userSessions[i].Username) { if (userSessions[i].Domain) { x += ' - ' + userSessions[i].Domain + '/' + userSessions[i].Username; } else { x += ' - ' + userSessions[i].Username; } }
x += '</div>';
}
QH('p11DeskSessionSelector', x);
QV('p11DeskSessionSelector', true);
}
}
}
} else {
if (message.type == 'notify') { // This is a notification message.
var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
if (message.id != null) { n.id = message.id; }
if (message.tag != null) { n.tag = message.tag; }
if (message.url != null) { n.url = message.url; }
if (message.username != null) { n.username = message.username; }
if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
addNotification(n);
}
}
break;
}
case 'getnetworkinfo': {
if (currentNode._id != message.nodeid) return;
updateDeviceDetails(getNodeFromId(message.nodeid), null, message);
break;
}
case 'getNotes': {
var n = Q('d2devNotes');
if (n && (message.id == decodeURIComponent(n.attributes['noteid'].value))) {
if (message.notes) { QH('d2devNotes', decodeURIComponent(message.notes)); } else { QH('d2devNotes', ''); }
var ro = (n.attributes['ro'].value == 'true');
if (ro == false) { // If we have permissions, set read/write on this note.
n.removeAttribute('readonly');
QE('idx_dlgOkButton', true);
QV('idx_dlgOkButton', true);
focusTextBox('d2devNotes');
}
}
break;
}
case 'otpauth-request': {
if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-request')) {
if (message.err != null) {
var otpauthErrors = ['', "2FA is locked", "Backup codes are locked", "Login token in use", "OTP 2FA not allowed", "Account is locked", "Unable to load OTPLIB"];
if ((message.err > 0) && (message.err < otpauthErrors.length)) { QH('d2optinfo', otpauthErrors[message.err]); } else { QH('d2optinfo', format("Error #{0}", message.err)); }
} else {
var secret = message.secret;
if (secret.length == 52) { secret = secret.split(/(.............)/).filter(Boolean).join(' '); }
else if (secret.length == 32) { secret = secret.split(/(....)/).filter(Boolean).join(' '); secret = secret.substring(0, 20) + '<br/>' + secret.substring(20) }
QH('d2optinfo', format("Install <a href=\"https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2\" rel=\"noreferrer noopener\" target=_blank>Google Authenticator</a> or a compatible application, use <a href=\"{0}\" rel=\"noreferrer noopener\" target=_blank> this link</a> or enter the secret below. Then, enter the current 6 digit token to activate 2-Step login.", message.url) + '<br /><br /><div style=width:100%;text-align:center><tt id=d2optsecret secret="' + message.secret + '" style=font-size:15px>' + secret + '</tt><br /><br />Token: <input type=text autocomplete="one-time-code" inputmode="numeric" pattern="[0-9]*" onkeypress=\"return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)\" onkeyup=account_addOtpCheck(event) onkeydown=account_addOtpCheck() maxlength=6 id=d2otpauthinput type=text></div>');
QV('idx_dlgOkButton', true);
QE('idx_dlgOkButton', false);
Q('d2otpauthinput').focus();
}
}
break;
}
case 'otpauth-setup': {
if (xxdialogMode) return;
setDialogMode(2, "Authenticator App", 1, null, message.success ? "<b style=color:green>2-step login activation successful</b>. You will now need a valid token to login again." : "<b style=color:red>2-step login activation failed</b>. Clear the secret from the application and try again. You only have a few minutes to enter the proper code.");
break;
}
case 'otpauth-clear': {
if (xxdialogMode) return;
setDialogMode(2, "Authenticator App", 1, null, message.success ? "<b style=color:green>2-step login activation removed</b>. You can reactivate this feature at any time." : "<b style=color:red>2-step login activation removal failed</b>. Try again.");
break;
}
case 'otpauth-getpasswords': {
if (xxdialogMode) return;
var x = "One time tokens can be used as secondary authentication. Generate a set, print them and keep them in a safe place.";
x += '<div style=\'border-radius:6px;border: 2px dashed #888;width:100%;margin-top:8px\'><div style=\'padding:8px;font-family:Arial, Helvetica, sans-serif;font-size:20px;font-weight:bold\'><table style=width:100%;text-align:center>';
if (message.passwords) {
var j = 0;
for (var i in message.passwords) {
if (++j % 2) { x += '<tr>'; }
var p = '' + message.passwords[i].p;
while (p.length < 8) { p = '0' + p; }
if (message.passwords[i].u === true) { x += '<td>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); } else { x += '<td><strike style=color:#BBB>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); + '</strike>'; }
}
} else {
x += '<tr><td>' + "No Active Tokens";
}
x += '</table></div></div><br />';
x += '<div><input type=button value=\'' + "Close" + '\' onclick=setDialogMode(0) style=float:right></input>';
x += '<input type=button value=\'' + "New Tokens" + '\' onclick=\'account_manageOtp(1);\'></input>';
if (message.passwords != null) { x += '<input type=button value=\'' + "Clear" + '\' onclick=\'account_manageOtp(2);\'></input>'; }
x += '</div><br />';
setDialogMode(2, "Manage Backup Codes", 8, null, x, 'otpauth-manage');
break;
}
case 'verifyPhone': {
if (xxdialogMode && (xxdialogTag != 'verifyPhone')) return;
var x = '<table><tr><td><img src="images/phone80.png" style=padding:8px>';
x += '<td>Check your phone and enter the verification code.';
x += '<br /><br /><div style=width:100%;text-align:center>' + "Verification code:" + ' <input type=tel pattern="[0-9]" inputmode="number" maxlength=6 id=d2phoneCodeInput onKeyUp=account_managePhoneCodeValidate() onkeypress="if (event.key==\'Enter\') account_managePhoneCodeValidate(1)"></div></table>';
setDialogMode(2, "Phone Notifications", 3, account_managePhoneConfirm, x, message.cookie);
Q('d2phoneCodeInput').focus();
account_managePhoneCodeValidate();
break;
}
case 'previousLogins': {
if ((xxdialogMode == 2) && (xxdialogTag == 'previousLogins')) {
var x = '', c = 'BBB', xx = '';
if (message.events.length == 0) {
x += 'No previous login.';
} else {
x += '<div style=max-height:260px;overflow-y:scroll;overflow-x:hidden>';
for (var i in message.events) {
var m = message.events[i].m;
if (m == 107) { m = "Valid login"; c = 'BBD1BB'; xx = ''; }
else if (m == 108) { m = "Invalid 2FA"; c = 'DD9DC3'; xx = 'x'; }
else if (m == 109) { m = "Locked account"; c = 'E1BBBB'; xx = 'x'; }
else if (m == 110) { m = "Invalid password"; c = 'E1BBBB'; xx = 'x'; }
x += '<div style=width:260px;background-color:#' + c + ';border-radius:6px;margin-bottom:4px;padding:4px><div><b>' + EscapeHtml(m) + '</b><br />' + printDateTime(new Date(message.events[i].t)) + '</div><div style=font-size:x-small>' + EscapeHtml(message.events[i].a.join(', ')) + '</div></div></tr>';
}
x += '</div>';
}
setDialogMode(2, "Previous Logins", 1, null, x);
}
break;
}
case 'event': {
/*
if (!message.event.nolog) {
events.unshift(message.event);
var eventLimit = parseInt(p3limitdropdown.value);
while (events.length > eventLimit) { events.pop(); } // Remove element(s) at the end
events_update();
}
*/
if (message.event.noact) break; // Take no action on this event
switch (message.event.action) {
case 'serverinfochange': {
if (message.event.lock2factor != null) { serverinfo.lock2factor = message.event.lock2factor; updateSelf(); }
break;
}
case 'userWebState': {
// New user web state, update the web page as needed
if (localStorage != null) {
var webstate = JSON.parse(message.event.state);
for (var i in webstate) { localStorage.setItem(i, webstate[i]); }
// Update stars
if (webstate.stars != null) { stars = JSON.parse(webstate.stars); }
// Update the web page
if ((webstate.loctag != null) && (webstate.loctag != oldLoctag)) {
if (webstate.loctag != null) { args.locale = webstate.loctag; } else { delete args.locale; }
mainUpdate(4 + 128);
} else if (webstate.stars != null) {
mainUpdate(4);
if (Q('SearchInput').value == '*') { onSearchInputChanged(); }
}
if (currentNode) { refreshDevice(currentNode._id); }
// Set the user's desktop shortcut keys
if (webstate.deskKeyShortcuts != null) {
deskKeyboardShortcuts = [];
var deskKeyboardShortcutsStr = webstate.deskKeyShortcuts.split(',');
for (var i in deskKeyboardShortcutsStr) { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); }
updateDeskShortcutKeys();
}
}
break;
}
case 'accountchange': {
// An account was created or changed
if ((typeof message.event.account != 'object') || (message.event.account == null)) { console.log(message.event); return; };
if (userinfo.name == message.event.account.name) {
var newsiteadmin = message.event.account.siteadmin ? message.event.account.siteadmin : 0;
var oldsiteadmin = userinfo.siteadmin ? userinfo.siteadmin : 0;
if ((message.event.account.quota != userinfo.quota) || (((userinfo.siteadmin & 8) == 0) && ((message.event.account.siteadmin & 8) != 0))) { meshserver.send({ action: 'files' }); }
userinfo = message.event.account;
//if (oldsiteadmin != newsiteadmin) updateSiteAdmin();
updateSelf();
// If our list of nodes may have changes, request the new list now.
if (message.event.nodeListChange == userinfo._id) { meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip }); }
}
break;
}
case 'createusergroup':
case 'usergroupchange': {
// User group changed
if (usergroups == null) { usergroups = {}; }
var ugroup = usergroups[message.event.ugrpid];
if (ugroup == null) {
// This is a new user group for us
usergroups[message.event.ugrpid] = { _id: message.event.ugrpid, name: message.event.name, desc: message.event.desc, domain: message.event.domain, links: message.event.links };
} else {
// This is an existing user group
ugroup.name = message.event.name;
ugroup.desc = message.event.desc;
ugroup.links = message.event.links;
ugroup.flags = message.event.flags;
}
//mainUpdate(8192 + 16384);
// Group update, refresh all our device groups and nodes. TODO: Optimize this to only do this when needed.
meshserver.send({ action: 'meshes' });
meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip });
break;
}
case 'deleteusergroup': {
// User group removed
if ((usergroups != null) && (usergroups[message.event.ugrpid] != null)) {
delete usergroups[message.event.ugrpid];
var c = 0;
for (var i in usergroups) { c++; }
if (c == 0) { usergroups = null; } // If user groups is empty, set it to null.
//mainUpdate(8192 + 16384);
}
break;
}
case 'createmesh': {
// A new mesh was created
if ((meshes[message.event.meshid] == null) && ((userinfo.manageAllDeviceGroups) || (message.event.mesh.links[userinfo._id] != null))) { // Check if this is a mesh create for a mesh we own. If site administrator, we get all messages so need to ignore some.
meshes[message.event.meshid] = message.event.mesh;
mainUpdate(4 + 128);
meshserver.send({ action: 'files' });
}
break;
}
case 'meshchange': {
// Update mesh information
if (meshes[message.event.meshid] == null) {
// Check if we have any access to this device group
var add = false;
if (message.event.links[userinfo._id] != null) { add = true; }
if (userinfo.links[message.event.meshid] != null) { add = true; }
for (var i in userinfo.links) { if ((i.startsWith('ugrp/')) && (message.event.links[i] != null)) { add = true; } }
// This is a new mesh for us
if (add) {
meshes[message.event.meshid] = { _id: message.event.meshid, name: message.event.name, mtype: message.event.mtype, desc: message.event.desc, links: message.event.links, relayid: message.event.relayid };
meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip }); // Request a refresh of all nodes (TODO: We could optimize this to only request nodes for the new mesh).
}
} else {
// This is an existing mesh
if (meshes[message.event.meshid].name != message.event.name) {
meshes[message.event.meshid].name = message.event.name;
for (var i in nodes) { if (nodes[i].meshid == message.event.meshid) { nodes[i].meshnamel = message.event.name.toLowerCase(); } }
}
meshes[message.event.meshid].desc = message.event.desc;
meshes[message.event.meshid].links = message.event.links;
if (message.event.relayid != null) { meshes[message.event.meshid].relayid = message.event.relayid; }
// Check if we lost rights to this mesh in this change.
if (IsMeshViewable(message.event.meshid) == false) {
if ((xxcurrentView == 20) && (currentMesh == meshes[message.event.meshid])) go(2);
delete meshes[message.event.meshid];
// Delete all nodes in that mesh, except ones with direct links
var newnodes = [];
for (var i in nodes) { if ((nodes[i].meshid != message.event.meshid) || ((userinfo.links != null) && (userinfo.links[nodes[i]._id] != null))) { newnodes.push(nodes[i]); } }
nodes = newnodes;
// If we are looking at a node in the deleted mesh, move back to "My Devices"
if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(2); currentNode = null; }
}
}
mainUpdate(4 + 128);
meshserver.send({ action: 'files' });
// If we are looking at a mesh that is now deleted, move back to "My Account"
if (xxcurrentView == 20 && currentMesh._id == message.event.meshid) { p20updateMesh(); }
break;
}
case 'deletemesh': {
// Delete the mesh
if (meshes[message.event.meshid]) {
delete meshes[message.event.meshid];
updateMeshes();
meshserver.send({ action: 'files' });
}
// Delete all nodes in that mesh
var newnodes = [];
for (var i in nodes) { if (nodes[i].meshid != message.event.meshid) { newnodes.push(nodes[i]); } }
nodes = newnodes;
mainUpdate(4);
// If we are looking at a mesh that is now deleted, move back to "My Account"
if (xxcurrentView >= 20 && xxcurrentView < 30 && currentMesh._id == message.event.meshid) { setDialogMode(0); go(2); }
// If we are looking at a node in the deleted mesh, move back to "My Devices"
if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(2); }
break;
}
case 'addnode': {
var node = message.event.node;
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.
if (getNodeFromId(node._id) != null) break; // This node is already known.
node.namel = node.name.toLowerCase();
if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
node.meshnamel = meshes[node.meshid]?meshes[node.meshid].name.toLowerCase():'*';
node.state = 0;
if (!node.icon) node.icon = 1;
node.ident = ++nodeShortIdent;
nodes.push(node);
//onSortSelectChange();
//onSearchInputChanged();
mainUpdate(4);
//updateMapMarkers();
break;
}
case 'removenode': {
var index = -1;
for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
if (index != -1) {
var node = nodes[index];
if (currentNode == node) {
if (xxcurrentView >= 10 && xxcurrentView < 20) { setDialogMode(0); go(2); }
currentNode = null;
// TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
}
nodes.splice(index, 1);
mainUpdate(4);
//updateMapMarkers();
}
break;
}
case 'changenode': {
var index = -1;
for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
if (index != -1) {
var node = nodes[index];
// Change the node
node.name = message.event.node.name;
node.rname = message.event.node.rname;
node.host = message.event.node.host;
node.desc = message.event.node.desc;
node.publicip = message.event.node.publicip;
node.iploc = message.event.node.iploc;
node.wifiloc = message.event.node.wifiloc;
node.gpsloc = message.event.node.gpsloc;
node.tags = message.event.node.tags;
node.ssh = message.event.node.ssh;
node.rdp = message.event.node.rdp;
node.userloc = message.event.node.userloc;
node.rdpport = message.event.node.rdpport;
node.rfbport = message.event.node.rfbport;
node.sshport = message.event.node.sshport;
node.httpport = message.event.node.httpport;
node.httpsport = message.event.node.httpsport;
node.consent = message.event.node.consent;
node.pmt = message.event.node.pmt;
if (message.event.node.agent != null) {
if (node.agent == null) node.agent = {};
if (message.event.node.agent.ver != null) { node.agent.ver = message.event.node.agent.ver; }
if (message.event.node.agent.id != null) { node.agent.id = message.event.node.agent.id; }
if (message.event.node.agent.caps != null) { node.agent.caps = message.event.node.agent.caps; }
if (message.event.node.agent.root != null) { node.agent.root = message.event.node.agent.root; }
if (message.event.node.agent.core != null) { node.agent.core = message.event.node.agent.core; } else { if (node.agent.core) { delete node.agent.core; } }
node.agent.tag = message.event.node.agent.tag;
}
if (message.event.node.intelamt != null) {
if (node.intelamt == null) node.intelamt = {};
if (message.event.node.intelamt.state != null) { node.intelamt.state = message.event.node.intelamt.state; }
if (message.event.node.intelamt.host != null) { node.intelamt.user = message.event.node.intelamt.host; }
if (message.event.node.intelamt.user != null) { node.intelamt.user = message.event.node.intelamt.user; }
if (message.event.node.intelamt.tls != null) { node.intelamt.tls = message.event.node.intelamt.tls; }
if (message.event.node.intelamt.ver != null) { node.intelamt.ver = message.event.node.intelamt.ver; }
if (message.event.node.intelamt.tag != null) { node.intelamt.tag = message.event.node.intelamt.tag; }
if (message.event.node.intelamt.uuid != null) { node.intelamt.uuid = message.event.node.intelamt.uuid; }
if (message.event.node.intelamt.realm != null) { node.intelamt.realm = message.event.node.intelamt.realm; }
if (message.event.node.intelamt.flags != null) { node.intelamt.flags = message.event.node.intelamt.flags; }
if (message.event.node.intelamt.warn != null) { node.intelamt.warn = message.event.node.intelamt.warn; } else { delete node.intelamt.warn; }
}
if (message.event.node.av != null) { node.av = message.event.node.av; }
if (message.event.node.wsc != null) { node.wsc = message.event.node.wsc; }
node.namel = node.name.toLowerCase();
if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
if (message.event.node.icon) { node.icon = message.event.node.icon; }
//onSortSelectChange(true);
//drawNotifications();
refreshDevice(node._id);
updateDeviceViewDevice(node);
if (currentNode == node) { updateDeviceDetails(); }
//if ((currentNode == node) && (xxdialogMode != null) && (xxdialogTag == '@xxmap')) { p10showNodeLocationDialog(); }
}
break;
}
case 'nodemeshchange': {
var index = -1;
for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
if (index != -1) {
var node = nodes[index];
if ((meshes[message.event.newMeshId] == null) && ((userinfo.links == null) || (userinfo.links[node._id] == null))) {
// We don't see the new mesh, remove this device
// TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(2); currentNode = null; }
nodes.splice(index, 1);
} else {
// We see the new mesh, move this device
node.meshid = message.event.newMeshId;
node.meshnamel = meshes[message.event.newMeshId]?meshes[message.event.newMeshId].name.toLowerCase():'*';
}
mainUpdate(4);
refreshDevice(message.event.nodeid);
} else {
// This is a new device, add it.
var node = message.event.node;
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.
node.namel = node.name.toLowerCase();
if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
node.meshnamel = meshes[node.meshid]?meshes[node.meshid].name.toLowerCase():'*';
node.state = 0;
if (!node.icon) node.icon = 1;
node.ident = ++nodeShortIdent;
if (nodes == null) { }
nodes.push(node);
// Web page update
//mainUpdate(1 | 2 | 4 | 16);
mainUpdate(4);
}
break;
}
case 'nodeconnect': {
// Indicated a node has changed connectivity state
var index = -1;
for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
if (index != -1) {
var node = nodes[index];
// Change the node connection state
node.conn = message.event.conn;
node.pwr = message.event.pwr;
// Clear sesssion information if needed
if ((node.conn & 1) == 0) { delete node.sessions; }
refreshDevice(node._id);
updateDeviceViewDevice(node);
}
break;
}
case 'login': {
// Update the last login time
if (users != null && users['user/' + domain + '/' + message.event.username.toLowerCase()]) { users['user/' + domain + '/' + message.event.username.toLowerCase()].login = message.event.time; }
break;
}
case 'notify': {
var n = { text: message.event.value, title: message.event.title, icon: message.event.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
if (message.id != null) { n.id = message.id; }
if (message.event.tag != null) { n.tag = message.event.tag; }
if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
addNotification(n);
break;
}
case 'sysinfohash': {
// If the sysinfo document has changed and we are looking at it, request an update.
if ((currentNode != null) && (message.event.nodeid == powerTimelineReq)) { meshserver.send({ action: 'getsysinfo', nodeid: message.event.nodeid }); }
break;
}
case 'ifchange': {
// Network interface changed for a device, if we are currently viewing this device, ask for an update.
if ((currentNode != null) && (currentNode._id == message.event.nodeid)) { meshserver.send({ action: 'getnetworkinfo', nodeid: currentNode._id }); }
break;
}
case 'devicesessions': {
// List of sessions for a given device
var node = getNodeFromId(message.event.nodeid);
if (node == null) break; // Unknown node
node.sessions = message.event.sessions;
if (node.sessions != null) {
for (var i in node.sessions) { if (Object.keys(node.sessions[i]).length == 0) { delete node.sessions[i]; } }
if (Object.keys(node.sessions).length == 0) { delete node.sessions; }
}
refreshDevice(message.event.nodeid);
updateDeviceViewDevice(node);
//if ((currentNode != null) && (currentNode._id == message.event.nodeid)) { gotoDevice(currentNode._id, xxcurrentView, true); }
// If we are looking at the sessions dialog box for this device now, update it
if (xxdialogTag == ('SESSIONS-' + message.event.nodeid)) { showDeviceSessions(message.event.nodeid, true); }
//if (xxdialogTag == ('MESSAGES-' + message.event.nodeid)) { showDeviceMessages(message.event.nodeid, true); }
if (xxdialogTag == ('HELPREQ-' + message.event.nodeid)) { showDeviceHelpRequests(message.event.nodeid, true); }
break;
}
case 'stopped': { // Server is stopping.
// TODO: Disconnect
break;
}
default:
//console.log('Unknown message.event.action', message.event.action);
break;
}
break;
}
default:
//console.log('Unknown message.action', message.action);
break;
}
}
// To boost the speed of the web page when even floods occur, this method perform a delayed update on the web page.
var updateNaggleTimer = null;
var updateNaggleFlags = 0;
function mainUpdate(flags) {
updateNaggleFlags |= flags;
if (updateNaggleTimer == null) {
updateNaggleTimer = setTimeout(function () {
if (updateNaggleFlags & 1) { onSearchInputChanged(); }
if (updateNaggleFlags & 4) { updateDevices(); updateDeviceDetails(); }
if (updateNaggleFlags & 128) { updateMeshes(); }
updateNaggleTimer = null;
updateNaggleFlags = 0;
gotoStartViewPage();
}, 150);
}
}
// Go to the correct starting view page
function gotoStartViewPage() {
var xviewmode = parseInt('{{viewmode}}');
if (xxcurrentView > 1) return;
if ('{{currentNode}}'.toLowerCase() != '') { // The .toLowerCase here is the minifier will not optimize this out.
if (getNodeFromId('{{currentNode}}') == null) return; // This node is not loaded yet
gotoDevice('{{currentNode}}', xviewmode);
} else if (args.gotonode != null) {
if (args.gotonode.length == 96) { args.gotonode = btoa(hex2rstr(args.gotonode)).split('+').join('@').split('/').join('$'); } // This is a HEX encoded NodeID, convert it to Base64
if (getNodeFromId('node/' + domain + '/' + args.gotonode) == null) return; // This node is not loaded yet
if (args.panel) { currentDevicePanel = parseInt(args.panel); }
gotoDevice('node/' + domain + '/' + args.gotonode, xviewmode);
} else if (args.gotodevicename != null) {
var foundNode = null;
if (nodes != null) { for (var i in nodes) { if (nodes[i].name == args.gotodevicename) { foundNode = nodes[i]._id; } } }
if (foundNode) { gotoDevice(foundNode, xviewmode); go(xviewmode); }
} else if (args.gotomesh != null) {
if (meshes['mesh/' + domain + '/' + args.gotomesh] == null) return; // This device group is not loaded yet
gotoMesh('mesh/' + domain + '/' + args.gotomesh);
go(xviewmode);
} else if (!isNaN(xviewmode)) {
go(xviewmode);
} else {
setDialogMode(0);
go(1);
}
delete args.gotonode;
delete args.gotomesh;
delete args.panel;
if (xxcurrentView < 2) { go(2); }
}
//
// Menu System
//
function topMenu(select) {
if ((xxdialogMode != null) && (xxdialogMode != 0) && (xxdialogMode != 999)) return;
if (select === undefined) {
var x = (QS('topMenu').display == 'none');
if (x == true) { if ((xxdialogMode == 0) || (xxdialogMode == null)) { QV('topMenu', true); xxdialogMode = 999; } } else { QV('topMenu', false); xxdialogMode = 0; }
} else {
QV('topMenu', false);
xxdialogMode = 0;
if ((select == 1) && (xxcurrentView != 3)) { goForward('account'); } // My Account
if ((select == 2) && (xxcurrentView != 5)) { goForward('files'); } // My Files
}
}
var backStack = [];
function goBack() { if (xxdialogMode) return; if (backStack.length > 0) { backStack.pop(); } goStack(); }
function goForward(id) { if (xxdialogMode) return; backStack.push(id); goStack(); }
function goStack() {
if (backStack.length == 0) { go(2); return; }
var id = backStack[backStack.length - 1], idtype = id.split('/')[0];
if (idtype == 'node') { setupDeviceMenu(0); gotoDevice(id); }
if (idtype == 'mesh') { gotoMesh(id); }
if (idtype == 'account') { go(3); }
if (idtype == 'devices') { go(2); }
if (idtype == 'files') { go(5); }
}
function updateFooterMenu(options) {
while (options != null && options.length < 3) { options.push({ n: '' }); }
var x = '', prev = '';
if (options != null) { for (var i in options) { x += '<td style="cursor:pointer' + ((prev == '') ? '' : ';border-left:solid 1px white') + '" onclick="' + options[i].f + '">' + options[i].n; prev = options[i].n; } }
QH('footerMenu', '<tr>' + x);
}
//
// MY ACCOUNT
//
function account_viewPreviousLogins() {
if (xxdialogMode) return;
setDialogMode(2, "Previous Logins", 1, null, "Loading...", 'previousLogins');
meshserver.send({ action: 'previousLogins' });
}
function account_manageImage(mode) {
if (xxdialogMode) return;
var user = (mode == 0) ? userinfo : currentUser;
var x = '<input id=p2file type=file style=width:100% accept="image/*" onchange=account_manageImageEx()><div style=width:100%><canvas id=p2canvas width=256 height=256 style="width:256px;height:256px;margin-left:12px;margin-top:8px;border-radius:16px;box-shadow: 0px 0px 15px #000" onclick=account_canvasClick() /></div>';
setDialogMode(2, "Manage Account Image", 7, account_manageImageEx2, x, user._id);
var ctx = Q('p2canvas').getContext('2d');
if (user.accountImageRnd == null) { user.accountImageRnd = Math.floor(Math.random() * 9999999999); }
var arg = '';
if (mode == 1) { arg = '&id=' + user._id.split('/')[2]; }
var myImg = new Image();
myImg.onload = function () { ctx.clearRect(0, 0, 256, 256); ctx.drawImage(myImg, 0, 0); };
myImg.src = ((user.flags != null) && (user.flags & 1)) ? ('userimage.ashx?rnd=' + user.accountImageRnd + arg) : 'images/user-256.png';
QE('idx_dlgDeleteButton', (user.flags != null) && (user.flags & 1));
QE('idx_dlgOkButton', false);
}
function account_canvasClick() { Q('p2file').click(); }
function account_manageImageEx() {
var file = Q('p2file').files[0];
var img = new Image;
img.onload = function () {
var cx = 0, cy = 0, min = Math.min(img.width, img.height);
if (img.width > min) { cx = (img.width - min) / 2; }
if (img.height > min) { cy = (img.height - min) / 2; }
var ctx = Q('p2canvas').getContext('2d');
ctx.imageSmoothingEnabled = true;
ctx.webkitImageSmoothingEnabled = true;
ctx.mozImageSmoothingEnabled = true;
ctx.clearRect(0, 0, 256, 256);
ctx.drawImage(img, cx, cy, min, min, 0, 0, 256, 256);
QE('idx_dlgOkButton', true);
}
img.src = URL.createObjectURL(file);
}
function account_manageImageEx2(b, userid) {
// Send updated image, or 0 if we pressed the delete button
meshserver.send({ action: 'updateUserImage', userid: userid, image: (b == 2) ? 0 : Q('p2canvas').toDataURL('image/jpeg', 0.8) });
//meshserver.send({ action: 'updateUserImage', image: (b == 2)?0:Q('p2canvas').toDataURL('image/png', 0.8) });
}
function toggleNightMode() {
if (xxdialogMode) return;
var cNightMode = getstore('nightMode', '0');
var x = '<input type=radio id=night0 name=nightmoderadio value=0 ' + ((cNightMode == 0) ? 'checked' : '') + '><label for=night0>' + "Browser default" + '</label><br>';
x += '<input type=radio id=night2 name=nightmoderadio value=2 ' + ((cNightMode == 2) ? 'checked' : '') + '><label for=night2>' + "Light mode" + '</label><br>';
x += '<input type=radio id=night1 name=nightmoderadio value=1 ' + ((cNightMode == 1) ? 'checked' : '') + '><label for=night1>' + "Dark mode" + '</label><br>';
setDialogMode(2, "Night Mode", 3, toggleNightModeEx, x);
QV('uiMenu', false);
}
function toggleNightModeEx() {
// Save new night mode
var nNightMode = '0';
if (Q('night1').checked) { nNightMode = '1'; }
if (Q('night2').checked) { nNightMode = '2'; }
putstore('nightMode', nNightMode);
setNightMode();
}
function setNightMode() {
// Set night mode
var nNightMode = getstore('nightMode', '0')
nightMode = false;
if ((features2 & 0x00100000) != 0) { nNightMode = '1'; }
if ((features2 & 0x00200000) != 0) { nNightMode = '2'; }
if (nNightMode == '1') { nightMode = true; }
else if ((nNightMode == '0') && (window.matchMedia)) { nightMode = window.matchMedia('(prefers-color-scheme: dark)').matches }
if (nightMode) { QC('body').add('night'); QS('body')['background-color'] = '#000'; QS('body')['color'] = 'lightgray'; } else { QC('body').remove('night'); QS('body')['background-color'] = '#FFF'; QS('body')['color'] = 'black'; }
return nightMode;
}
function account_managePhone() {
if (xxdialogMode || ((features & 0x02000000) == 0)) return;
var x;
if (userinfo.phone != null) {
x = '<table style=width:100%><tr><td style=width:56px><img src="images/phone80.png" style=padding:8px>';
x += '<td style=text-align:center><div style=padding:6px>' + "Verified phone number" + '</div><div style=font-size:20px>' + userinfo.phone + '</div>';
x += '<div style=margin:10px><label><input id=d2delPhone type=checkbox onclick=account_managePhoneRemoveValidate() />' + "Remove phone number" + '</label></div>';
setDialogMode(2, "Phone Notifications", 3, account_managePhoneRemove, x);
account_managePhoneRemoveValidate();
} else {
x = '<table style=width:100%><tr><td style=width:56px><img src="images/phone80.png" style=padding:8px>';
x += '<td>Enter your SMS capable phone number. Once verified, the number may be used for login verification and other notifications.';
x += '<br /><br /><div style=width:100%;text-align:center>' + "Phone number:" + ' <input type=tel pattern="[0-9]" autocomplete="tel" inputmode="tel" maxlength=18 id=d2phoneinput onKeyUp=account_managePhoneValidate() onkeypress="if (event.key==\'Enter\') account_managePhoneValidate(1)"></div></table>';
setDialogMode(2, "Phone Notifications", 3, account_managePhoneAdd, x, 'verifyPhone');
Q('d2phoneinput').focus();
account_managePhoneValidate();
}
}
function isPhoneNumber(x) { return x.match(/^\(?([0-9]{3,4})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/) }
function account_managePhoneValidate(x) { var ok = isPhoneNumber(Q('d2phoneinput').value); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); } }
function account_managePhoneCodeValidate(x) { var ok = (Q('d2phoneCodeInput').value.length == 6) && Q('d2phoneCodeInput').value.match(/[0-9]/); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); } }
function account_managePhoneConfirm(b, tag) { meshserver.send({ action: 'confirmPhone', code: Q('d2phoneCodeInput').value, cookie: tag }); }
function account_managePhoneAdd() { if (isPhoneNumber(Q('d2phoneinput').value) == false) return; QE('d2phoneinput', false); meshserver.send({ action: 'verifyPhone', phone: Q('d2phoneinput').value }); }
function account_managePhoneRemove() { if (Q('d2delPhone').checked) { meshserver.send({ action: 'removePhone' }); } }
function account_managePhoneRemoveValidate() { QE('idx_dlgOkButton', Q('d2delPhone').checked); }
function account_manageAuthEmail() {
if (xxdialogMode || ((features & 0x00800000) == 0)) return;
var emailU2Fenabled = ((userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
setDialogMode(2, "Email Authentication", 1, function () {
if (emailU2Fenabled != Q('email2facheck').checked) { meshserver.send({ action: 'otpemail', enabled: Q('email2facheck').checked }); }
}, "When enabled, on each login, you will be given the option to receive a login token to you email account for added security." + '<br /><br /><label><input id=email2facheck type=checkbox ' + (emailU2Fenabled ? 'checked' : '') + '/>' + "Enable email two-factor authentication." + '</label>');
}
function account_manageAuthApp() {
if (xxdialogMode || ((features & 4096) == 0)) return;
if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); }
}
function account_addOtp() {
if (xxdialogMode || (userinfo.otpsecret == 1) || ((features & 4096) == 0)) return;
setDialogMode(2, "Authenticator App", 2, function () { meshserver.send({ action: 'otpauth-setup', secret: Q('d2optsecret').attributes.secret.value, token: Q('d2otpauthinput').value }); }, '<div id=d2optinfo>' + "Loading..." + '</div>', 'otpauth-request');
meshserver.send({ action: 'otpauth-request' });
}
function account_addOtpCheck(e) {
var tokenIsValid = (Q('d2otpauthinput').value.length == 6);
QE('idx_dlgOkButton', tokenIsValid);
if (e && (e.keyCode == 13) && tokenIsValid) { dialogclose(1); }
}
function account_removeOtp() {
if (xxdialogMode || (userinfo.otpsecret != 1) || ((features & 4096) == 0)) return;
setDialogMode(2, "Authenticator App", 3, function () { meshserver.send({ action: 'otpauth-clear' }); }, "Confirm removal of authenticator application 2-step login?");
}
function account_manageOtp(action) {
if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-manage')) { dialogclose(0); }
if (xxdialogMode || ((features & 4096) == 0) || ((userinfo.otpsecret != 1) && (userinfo.otphkeys < 1))) return;
meshserver.send({ action: 'otpauth-getpasswords', subaction: action });
}
function account_showVerifyEmail() {
if (xxdialogMode || (userinfo.emailVerified == true) || (serverinfo.emailcheck != true)) return;
var x = "Click ok to send a verification mail to:" + '<br /><div style=padding:8px><b>' + EscapeHtml(userinfo.email) + '</b></div>' + "Please wait a few minute to receive the verification.";
setDialogMode(2, "Email Verification", 3, account_showVerifyEmailEx, x);
}
function account_showVerifyEmailEx() {
meshserver.send({ action: 'verifyemail', email: userinfo.email });
}
function account_showChangeEmail() {
if (xxdialogMode) return;
var x = addHtmlValue("Email", '<input id=dp3email style=width:170px maxlength=256 onchange=account_validateEmail() onkeyup=account_validateEmail(event) />');
setDialogMode(2, "Email Address Change", 3, account_changeEmail, x);
if (userinfo.email != null) { Q('dp3email').value = userinfo.email; }
account_validateEmail();
Q('dp3email').focus();
}
function account_validateEmail(e, email) {
QE('idx_dlgOkButton', validateEmail(Q('dp3email').value) && (Q('dp3email').value != userinfo.email));
if ((e != null) && (e.keyCode == 13)) { dialogclose(1); }
}
function account_changeEmail() {
meshserver.send({ action: 'changeemail', email: Q('dp3email').value });
}
function account_showDeleteAccount() {
if (xxdialogMode) return;
var x = '<form method=post><table style=margin-left:10px><input type=hidden name=action value=deleteaccount /><input type=hidden name=authcookie value=' + authCookie + ' /><tr>';
x += '<td align=right>' + "Password:" + '</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateDeleteAccount() onkeyup=account_validateDeleteAccount() /></td>';
x += '</tr><tr><td align=right>' + "Password:" + '</td><td><input id=apassword2 type=password name=apassword2 autocomplete=off onchange=account_validateDeleteAccount() onkeyup=account_validateDeleteAccount() /></td>';
x += '</tr></table><div style=padding:10px;margin-bottom:4px>';
x += '<input id=account_dlgCancelButton type=button value="' + "Cancel" + '" style=float:right;width:80px;margin-left:5px onclick=dialogclose(0)>';
x += '<input id=account_dlgOkButton type=submit value="' + "OK" + '" style="float:right;width:80px" onclick=dialogclose(1)>';
x += '</div><br /></form>';
setDialogMode(2, "Delete Account", 0, null, x);
account_validateDeleteAccount();
Q('apassword1').focus();
}
function account_showChangePassword() {
if (xxdialogMode) return false;
var x = '<table style=margin-left:10px>';
x += '<tr><td align=right>' + nobreak("Old password:") + '</td><td><input id=apassword0 type=password name=apassword0 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /> <b></b></td></tr>';
x += '<tr><td align=right>' + nobreak("New password:") + '</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /> <b><span id=dxPassWarn></span></b></td></tr>';
x += '<tr><td align=right>' + nobreak("New password:") + '</td><td><input id=apassword2 type=password name=apassword2 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /></td></tr>';
if (features & 0x00010000) { x += '<tr><td align=right>' + "Password hint:" + '</td><td><input id=apasswordhint name=apasswordhint maxlength=250 type=text autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /></td></tr>'; }
x += '</table>'
if (passRequirements) {
var r = [], rc = 0;
for (var i in passRequirements) { if ((i != 'reset') && (i != 'hint')) { r.push(i + ':' + passRequirements[i]); rc++; } }
if (rc > 0) { x += '<br /><span style=font-size:x-small>' + format("Requirements: {0}.", r.join(', ')) + '</span>'; }
}
x += '<br />';
setDialogMode(2, "Change Password", 3, account_showChangePasswordEx, x);
Q('apassword0').focus();
account_validateNewPassword();
return false;
}
function account_showChangePasswordEx() {
if (Q('apassword1').value == Q('apassword2').value) {
var r = { action: 'changepassword', oldpass: Q('apassword0').value, newpass: Q('apassword1').value };
if (features & 0x00010000) { r.hint = Q('apasswordhint').value; }
meshserver.send(r);
}
}
function account_createMesh() {
if (xxdialogMode) return;
// Check if we are disallowed from creating a device group
if ((userinfo.siteadmin != 0xFFFFFFFF) && ((userinfo.siteadmin & 64) != 0)) { setDialogMode(2, "New Device Group", 1, null, "This account does not have the rights to create a new device group."); return; }
// Remind the user to verify the email address
if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" to change and verify an email address."); return; }
// Remind the user to add two factor authentication
if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
// We are allowed, let's prompt to information
var x = addHtmlValue("Name", '<input id=dp3meshname style=width:170px maxlength=64 onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate() />');
x += addHtmlValue("Type", '<div style=width:170px;margin:0;padding:0><select id=dp3meshtype style=width:100% onchange=account_validateMeshCreate() ><option value=2>' + "Software Agent Group" + '</option><option value=1>' + "Intel&reg; AMT only" + '</option></select></div>');
x += addHtmlValue("Description", '<div style=width:170px;margin:0;padding:0><textarea id=dp3meshdesc maxlength=1024 style=width:100%;resize:none></textarea></div>');
setDialogMode(2, "Create Device Group", 3, account_createMeshEx, x);
account_validateMeshCreate();
Q('dp3meshname').focus();
}
function account_validateMeshCreate() {
QE('idx_dlgOkButton', Q('dp3meshname').value.length > 0);
}
function account_createMeshEx(button, tag) {
meshserver.send({ action: 'createmesh', meshname: Q('dp3meshname').value, meshtype: parseInt(Q('dp3meshtype').value), desc: Q('dp3meshdesc').value });
}
function account_validateDeleteAccount() {
QE('account_dlgOkButton', (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value));
}
function account_validateNewPassword() {
var r = '', ok = (Q('apassword0').value.length > 0) && (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value) && (Q('apassword0').value != Q('apassword1').value);
if ((features & 0x00010000) && (Q('apasswordhint').value == Q('apassword1').value)) { ok = false; }
if (Q('apassword1').value != '') {
if (passRequirements == null || passRequirements == '') {
// No password requirements, display password strength
var passStrength = checkPasswordStrength(Q('apassword1').value);
if (passStrength >= 80) { r = '<span style=color:green>Strong<span>'; } else if (passStrength >= 60) { r = '<span style=color:blue>&#9679;<span>'; } else { r = '<span style=color:red>&#9679;<span>'; }
} else {
// Password requirements provided, use that
var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements);
if (passReq == false) { ok = false; r = '<span style=color:red>' + "Policy" + '<span>' }
}
}
QH('dxPassWarn', r);
//QE('account_dlgOkButton', ok);
QE('idx_dlgOkButton', ok);
}
// Return a password strength score
function checkPasswordStrength(password) {
var r = 0, letters = {}, varCount = 0, variations = { digits: /\d/.test(password), lower: /[a-z]/.test(password), upper: /[A-Z]/.test(password), nonWords: /\W/.test(password) }
if (!password) return 0;
for (var i = 0; i < password.length; i++) { letters[password[i]] = (letters[password[i]] || 0) + 1; r += 5.0 / letters[password[i]]; }
for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
return parseInt(r + (varCount - 1) * 10);
}
// Check password requirements
function checkPasswordRequirements(password, requirements) {
if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true;
if (requirements.min) { if (password.length < requirements.min) return false; }
if (requirements.max) { if (password.length > requirements.max) return false; }
var numeric = 0, lower = 0, upper = 0, nonalpha = 0;
for (var i = 0; i < password.length; i++) {
if (/\d/.test(password[i])) { numeric++; }
if (/[a-z]/.test(password[i])) { lower++; }
if (/[A-Z]/.test(password[i])) { upper++; }
if (/\W/.test(password[i])) { nonalpha++; }
}
if (requirements.numeric && (numeric < requirements.numeric)) return false;
if (requirements.lower && (lower < requirements.lower)) return false;
if (requirements.upper && (upper < requirements.upper)) return false;
if (requirements.nonalpha && (nonalpha < requirements.nonalpha)) return false;
return true;
}
function updateMeshes() {
var r = '', count = 0;
for (i in meshes) {
count++;
// Mesh rights
var meshrights = GetMeshRights(meshes[i]);
var rights = "Partial Rights";
if (meshrights == 0xFFFFFFFF) rights = "Full Administrator"; else if (meshrights == 0) rights = "No Rights";
// Print the mesh information
r += '<div style=cursor:pointer onclick=goForward(\'' + i + '\')>';
r += '<div style="float:left;margin-left:4px"><img src="/images/meshicon50.png" width=50 height=50 /></div>';
r += '<div class=meshList>';
r += '<div><div style=color:black;padding-left:12px;padding-top:2px><b>' + EscapeHtml(meshes[i].name) + '</b></div><div style=padding-left:12px;padding-top:3px;color:black>' + rights + '</div></div>';
r += '</div></div>';
}
QH('p3meshes', r);
QV('p3noMeshFound', count == 0);
}
function gotoMesh(meshid) {
currentMesh = meshes[meshid];
if (currentMesh == null) { goBack(); }
p20updateMesh();
go(20);
}
//
// MY FILES
//
var filetreelinkpath;
var filetreelocation = [];
function p5refreshFiles() { meshserver.send({ action: 'files' }); }
function updateFiles() {
QV('MainMenuMyFiles', ((features & 8) == 0));
if ((features & 8) != 0) return; // If running on a server without files, exit now.
var html1 = '', html2 = '', displayPath = '<a style=cursor:pointer;color:black onclick=p5folderup(0)>' + "Root" + '</a>', fullPath = 'Root', publicPath, filetreex = filetree, folderdepth = 1;
// Navigate to path location, build the paths at the same time
var filetreelocation2 = [], oldlinkpath = filetreelinkpath, checkedBoxes = [], checkboxes = document.getElementsByName('fc');
for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { checkedBoxes.push(checkboxes[i].value) }; } // Save all existing checked boxes
filetreelinkpath = '';
for (var i in filetreelocation) {
if ((filetreex.f != null) && (filetreex.f[filetreelocation[i]] != null)) {
filetreelocation2.push(filetreelocation[i]);
fullPath += ' / ' + filetreelocation[i];
if ((folderdepth == 1)) {
var sp = filetreelocation[i].split('/');
publicPath = window.location + sp[0] + 'files/' + sp[2];
//if (filetreelocation[i] === userinfo._id) { filetreelinkpath += 'self'; } else { filetreelinkpath += (sp[0] + '/' + sp[2]); }
filetreelinkpath += filetreelocation[i];
} else {
if (filetreelinkpath != '') { filetreelinkpath += '/' + filetreelocation[i]; if (folderdepth > 2) { publicPath += '/' + filetreelocation[i]; } }
}
filetreex = filetreex.f[filetreelocation[i]];
displayPath += ' / <a style=cursor:pointer;color:black onclick=p5folderup(' + folderdepth + ')>' + EscapeHtml(filetreex.n != null ? filetreex.n : filetreelocation[i]) + '</a>';
folderdepth++;
} else {
break;
}
}
filetreelocation = filetreelocation2; // In case we could not go down the full path, we set the new path location here.
var publicfolder = fullPath.toLowerCase().startsWith('root / ' + userinfo._id + ' / public');
// Sort the files
var filetreexx = p5sort_files(filetreex.f);
// Display all files and folders at this location
for (var i in filetreexx) {
// Figure out the name and shortname
var f = filetreexx[i], name = f.n, shortname;
if (name.length > 40) { shortname = EscapeHtml(name.substring(0, 40)) + "..."; } else { shortname = EscapeHtml(name); }
// Figure out the date
//var fdatestr = '';
//if (f.d != null) { var fdate = new Date(f.d), fdatestr = (fdate.getMonth() + 1) + '/' + (fdate.getDate()) + '/' + fdate.getFullYear() + ' ' + printTime(fdate) + '&nbsp;'; }
// Figure out the size
var fsize = '';
if (f.s != null) { fsize = getFileSizeStr(f.s); }
var h = '';
if (f.t < 3 || f.t == 4) {
var right = (f.t == 1 || f.t == 4) ? p5getQuotabar(f) : '';
h = '<div class=filelist file=999><input file=999 style=float:left name=fc class=fcb type=checkbox onchange=p5setActions() value=\'' + EscapeHtml(name) + '\'>&nbsp;<span style=float:right;padding-right:4px>' + right + '</span><span><div class=fileIcon' + f.t + '></div><a style=cursor:pointer onclick=p5folderset("' + encodeURIComponent(f.nx) + '")>' + shortname + '</a></span></div>';
} else {
var link = shortname;
var publiclink = '';
if (publicfolder) { publiclink = ' (<a style=cursor:pointer onclick=\'p5showPublicLink("' + publicPath + '/' + f.nx + '")\'>' + "Link" + '</a>)'; }
if (f.s > 0) { link = '<a rel=\"noreferrer noopener\" target=\"_blank\" href=\"downloadfile.ashx?link=' + encodeURIComponent(filetreelinkpath + '/' + f.nx) + '\">' + shortname + '</a>' + publiclink; }
h = '<div class=filelist file=3><input file=3 style=float:left name=fc class=fcb type=checkbox onchange=p5setActions() value=\'' + f.nx + '\'>&nbsp;<span style=float:right;padding-right:4px>' + EscapeHtml(fsize) + '</span><span><div class=fileIcon' + f.t + '></div>' + link + '</span></div>';
}
if (f.t < 3) { html1 += h; } else { html2 += h; }
}
//if (f.parent == null) { }
QH('p5rightOfButtons', p5getQuotabar(filetreex));
QH('p5files', html1 + html2);
QH('p5currentpath', displayPath);
QE('p5FolderUp', filetreelocation.length != 0);
QV('p5PublicShare', publicfolder);
// Re-check all boxes if needed
if (oldlinkpath == filetreelinkpath) {
checkboxes = document.getElementsByName('fc');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = (checkedBoxes.indexOf(checkboxes[i].value) >= 0);
}
}
p5setActions();
}
function getNiceSize(bytes) {
if (bytes <= 0) return "Storage exceed";
if (bytes < 2048) return format("{0}b left", bytes);
if (bytes < 2097152) return format("{0}k left", Math.round(bytes / 1024));
if (bytes < 2147483648) return format("{0}m left", Math.round(bytes / 1024 / 1024));
return format("{0}g left", Math.round(bytes / 1024 / 1024 / 1024));
}
function p5getQuotabar(f) {
while (f.t > 1 && f.t != 4) { f = f.parent; }
if ((f.t != 1 && f.t != 4) || (f.maxbytes == null)) return '';
return getNiceSize(f.maxbytes - f.s) + ' <progress style=height:10px;width:100px value=' + f.s + ' max=' + f.maxbytes + ' />';
}
function p5showPublicLink(u) { setDialogMode(2, "Public Link", 1, null, '<input type=text style=width:100% value="' + u + '" readonly />'); }
var sortorder;
function p5sort_filename(a, b) { if (a.ln > b.ln) return (1 * sortorder); if (a.ln < b.ln) return (-1 * sortorder); return 0; }
function p5sort_timestamp(a, b) { if (a.d > b.d) return (1 * sortorder); if (a.d < b.d) return (-1 * sortorder); return 0; }
function p5sort_bysize(a, b) { if (a.s == b.s) return p5sort_filename(a, b); return (((a.s - b.s)) * sortorder); }
function p5sort_files(files) {
var r = [], sortselection = Q('p5sortdropdown').value;
for (var i in files) { files[i].nx = i; if (files[i].n == null) { files[i].n = i; } files[i].ln = files[i].n.toLowerCase(); r.push(files[i]); }
sortorder = 1;
if (sortselection > 3) { sortorder = -1; sortselection -= 3; }
if (sortselection == 1) { r.sort(p5sort_filename); }
else if (sortselection == 2) { r.sort(p5sort_bysize); }
else if (sortselection == 3) { r.sort(p5sort_timestamp); }
return r;
}
function p5setActions() {
var cc = getFileSelCount(), tc = getFileCount(), sfc = getFileSelCount(false); // In order: number of entires selected, number of total entries, number of selected entires that are files (not folders)
QE('p5DeleteFileButton', (cc > 0) && (filetreelocation.length > 0));
QE('p5NewFolderButton', filetreelocation.length > 0);
QE('p5UploadButton', filetreelocation.length > 0);
QE('p5RenameFileButton', (cc == 1) && (filetreelocation.length > 0));
QE('p5SelectAllButton', tc > 0);
Q('p5SelectAllButton').value = (cc > 0 ? "None" : "All");
QE('p5CutButton', (sfc > 0) && (cc == sfc));
QE('p5CopyButton', (sfc > 0) && (cc == sfc));
QE('p5PasteButton', (p5clipboard != null) && (p5clipboard.length > 0) && (filetreelocation.length > 0));
}
function getFileSelCount(includeDirs) { var cc = 0, checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && ((includeDirs != false) || (checkboxes[i].attributes.file.value == '3'))) cc++; } return cc; }
function getFileSelDirCount() { var cc = 0, checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '999')) cc++; } return cc; }
function getFileCount() { var cc = 0; var checkboxes = document.getElementsByName('fc'); return checkboxes.length; }
function p5selectallfile() { var nv = (getFileSelCount() == 0), checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { checkboxes[i].checked = nv; } p5setActions(); }
function setupBackPointers(x) { if (x.f != null) { var fs = 0, fc = 0; for (var i in x.f) { setupBackPointers(x.f[i]); x.f[i].parent = x; if (x.f[i].s) { fs += x.f[i].s; } if (x.f[i].c) { fc += x.f[i].c; } if (x.f[i].t == 3) { fc++; } } x.s = fs; x.c = fc; } return x; }
function getFileSizeStr(size) { if (size == 1) return "1 byte"; return format("{0} bytes", size); }
function p5folderup(x) { if (x == null) { filetreelocation.pop(); } else { while (filetreelocation.length > x) { filetreelocation.pop(); } } updateFiles(); return false; }
function p5folderset(x) { filetreelocation.push(decodeURIComponent(x)); updateFiles(); return false; }
function p5createfolder() { setDialogMode(2, "New Folder", 3, p5createfolderEx, '<input type=text id=p5renameinput maxlength=64 onkeyup=p5fileNameCheck(event) style=width:100% />'); focusTextBox('p5renameinput'); p5fileNameCheck(); }
function p5createfolderEx() { meshserver.send({ action: 'fileoperation', fileop: 'createfolder', path: filetreelocation, newfolder: Q('p5renameinput').value }); }
function p5deletefile() { var cc = getFileSelCount(), rec = (getFileSelDirCount() > 0) ? '<br /><br /><label><input type=checkbox id=p5recdeleteinput>' + "Recursive delete" + '</label><br>' : '<input type=checkbox id=p5recdeleteinput style=\'display:none\'>'; setDialogMode(2, "Delete", 3, p5deletefileEx, (cc > 1) ? (format("Delete {0} selected items?", cc) + rec) : ("Delete selected item?" + rec)); }
function p5deletefileEx() { var delfiles = [], checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { delfiles.push(checkboxes[i].value); } } meshserver.send({ action: 'fileoperation', fileop: 'delete', path: filetreelocation, delfiles: delfiles, rec: Q('p5recdeleteinput').checked }); }
function p5renamefile() { var renamefile, checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { renamefile = checkboxes[i].value; } } setDialogMode(2, "Rename", 3, p5renamefileEx, '<input type=text id=p5renameinput maxlength=64 onkeyup=p5fileNameCheck(event) style=width:100% value="' + renamefile + '" />', { action: 'fileoperation', fileop: 'rename', path: filetreelocation, oldname: renamefile }); focusTextBox('p5renameinput'); p5fileNameCheck(); }
function p5renamefileEx(b, t) { t.newname = Q('p5renameinput').value; meshserver.send(t); }
function p5fileNameCheck(e) { var x = isFilenameValid(Q('p5renameinput').value); QE('idx_dlgOkButton', x); if ((x == true) && (e && e.keyCode == 13)) { dialogclose(1); } }
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] != '.'); } })();
function p5uploadFile() { setDialogMode(2, "Upload File", 3, p5uploadFileEx, '<form method=post enctype=multipart/form-data action=uploadfile.ashx target=fileUploadFrame><input type=text name=link style=display:none id=p5uploadpath value="' + encodeURIComponent(filetreelinkpath) + '" /><input type=file name=files id=p5uploadinput style=width:100% multiple=multiple onchange="updateUploadDialogOk(\'p5uploadinput\')" /><input type=hidden name=authCookie value=' + authCookie + ' /><input type=submit id=p5loginSubmit style=display:none /></form>'); updateUploadDialogOk('p5uploadinput'); }
function p5uploadFileEx() { Q('p5loginSubmit').click(); }
function updateUploadDialogOk(x) { QE('idx_dlgOkButton', Q(x).value != ''); }
var p5clipboard = null, p5clipboardFolder = null, p5clipboardCut = 0;
function p5copyFile(cut) { var checkboxes = document.getElementsByName('fc'); p5clipboard = []; p5clipboardCut = cut, p5clipboardFolder = Clone(filetreelocation); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '3')) { p5clipboard.push(checkboxes[i].value); } } p5updateClipview(); }
function p5pasteFile() { var x = ''; if ((p5clipboard != null) && (p5clipboard.length > 0)) { x = format("Confirm {0} of {1} entrie{2} to this location?", (p5clipboardCut == 0 ? 'copy' : 'move'), p5clipboard.length, ((p5clipboard.length > 1) ? 's' : '')) } setDialogMode(2, "Paste", 3, p5pasteFileEx, x); }
function p5pasteFileEx() { meshserver.send({ action: 'fileoperation', fileop: (p5clipboardCut == 0 ? 'copy' : 'move'), scpath: p5clipboardFolder, path: filetreelocation, names: p5clipboard }); p5folderup(999); if (p5clipboardCut == 1) { p5clipboard = null, p5clipboardFolder = null, p5clipboardCut = 0; p5updateClipview(); } }
function p5updateClipview() { var x = ''; if ((p5clipboard != null) && (p5clipboard.length > 0)) { x = format("Holding {0} entrie{1} for {2}", p5clipboard.length, ((p5clipboard.length > 1) ? 's' : ''), (p5clipboardCut == 0 ? "copy" : "move")) + ', <a href=# onclick="return p5clearClip()" style=cursor:pointer>' + "Clear" + '</a>.' } QH('p5bottomstatus', x); p5setActions(); }
function p5clearClip() { p5clipboard = null; p5clipboardFolder = null; p5clipboardCut = 0; p5updateClipview(); return false; }
function p5fileDragDrop(e) {
haltEvent(e);
QV('bigfail', false);
QV('bigok', false);
//QV('p5fileCatchAllInput', false);
if (e.dataTransfer == null || e.dataTransfer.files.length == 0 || filetreelocation.length == 0) return;
var names = [], sizes = [], types = [], datas = [], readercount = e.dataTransfer.files.length;
for (var i = 0; i < e.dataTransfer.files.length; i++) {
var reader = new FileReader(), file = e.dataTransfer.files[i];
names.push(file.name);
sizes.push(file.size);
types.push(file.type);
reader.onload = function (event) {
datas.push(event.target.result);
if (--readercount == 0) {
Q('p5fileDragName').value = names.join('*');
Q('p5fileDragSize').value = sizes.join('*');
Q('p5fileDragType').value = types.join('*');
Q('p5fileDragData').value = datas.join('*');
Q('p5fileDragLink').value = encodeURIComponent(filetreelinkpath);
Q('p5loginSubmit2').click();
}
}
reader.readAsDataURL(file);
}
}
var p5dragtimer = null;
function p5fileDragOver(e) {
haltEvent(e);
if (p5dragtimer != null) { clearTimeout(p5dragtimer); p5dragtimer = null; }
var ac = true; // TODO: Set to true if we can accept the file
if (filetreelocation.length == 0) { ac = false; }
QV('bigok', ac);
QV('bigfail', !ac);
//QV('p5fileCatchAllInput', ac);
}
function p5fileDragLeave(e) {
haltEvent(e);
if (e.target.id != 'p5filetable') {
QV('bigfail', false);
QV('bigok', false);
//QV('p5fileCatchAllInput', false);
} else {
p5dragtimer = setTimeout('QV(\'bigfail\',false);QV(\'bigok\',false);p5dragtimer=null;', 200);
}
}
//
// MY DEVICES
//
function onRealNameCheckBox() {
showRealNames = Q('RealNameCheckBox').checked;
putstore('showRealNames', showRealNames ? 1 : 0);
mainUpdate(5);
}
function onOnlineCheckBox(e) {
putstore('onlineOnly', Q('OnlineCheckBox').checked ? 1 : 0);
onSearchInputChanged();
}
function updateDevicePageState() {
if ((devicePagingState == null) || (devicePagingState.total <= devicePagingState.limit)) {
QV('devViewPageState', false);
QV('devViewPageButton2', false);
QV('devViewPageButton3', false);
} else {
var currentPage = Math.floor((devicePagingState.skip + devicePagingState.limit) / devicePagingState.limit);
var maxPage = Math.ceil(devicePagingState.total / devicePagingState.limit);
QV('devViewPageState', true);
QV('devViewPageButton2', true);
QV('devViewPageButton3', true);
QH('devViewPageState', currentPage + '/' + maxPage);
}
}
function onDeviceViewPageChange(i) {
if (devicePagingState == null) return;
var currentPage = (Math.floor((devicePagingState.skip + devicePagingState.limit) / devicePagingState.limit));
var maxPage = Math.ceil(devicePagingState.total / devicePagingState.limit);
switch (i) {
case 2: { if (currentPage > 1) meshserver.send({ action: 'nodes', skip: (currentPage - 2) * devicePagingState.limit }); break; } // Goto previous page
case 3: { if (currentPage < maxPage) meshserver.send({ action: 'nodes', skip: currentPage * devicePagingState.limit }); break; } // Goto next page
}
}
function onDeviceSearchChanged(e) {
setTimeout(function () { onSearchInputChanged(); }, 10);
}
function clearSearchInput() {
Q('SearchInput').value = '';
Q('OnlineCheckBox').checked = false;
onSearchInputChanged();
}
function onSearchInputChanged() {
var x = Q('SearchInput').value.toLowerCase().trim(); putstore('_search', Q('SearchInput').value);
QS('SearchInput')['background-color'] = (x == '') ? '#FFFFFF' : '#FDFFBE';
var userSearch = null, ipSearch = null, groupSearch = null, tagSearch = null, agentTagSearch = null, wscSearch = null, osSearch = null, amtSearch = null;
if (x.startsWith("user:".toLowerCase())) { userSearch = x.substring("user:".length); }
else if (x.startsWith("u:".toLowerCase())) { userSearch = x.substring("u:".length); }
else if (x.startsWith("ip:".toLowerCase())) { ipSearch = x.substring("ip:".length); }
else if (x.startsWith("group:".toLowerCase())) { groupSearch = x.substring("group:".length); }
else if (x.startsWith("g:".toLowerCase())) { groupSearch = x.substring("g:".length); }
else if (x.startsWith("tag:".toLowerCase())) { tagSearch = Q('SearchInput').value.trim().substring("tag:".length); }
else if (x.startsWith("t:".toLowerCase())) { tagSearch = Q('SearchInput').value.trim().substring("t:".length); }
else if (x.startsWith("atag:".toLowerCase())) { agentTagSearch = Q('SearchInput').value.trim().substring("atag:".length).toLowerCase(); }
else if (x.startsWith("a:".toLowerCase())) { agentTagSearch = Q('SearchInput').value.trim().substring("a:".length).toLowerCase(); }
else if (x.startsWith("os:".toLowerCase())) { osSearch = Q('SearchInput').value.trim().substring("os:".length).toLowerCase(); }
else if (x.startsWith("amt:".toLowerCase())) { amtSearch = Q('SearchInput').value.trim().substring("amt:".length).toLowerCase(); }
else if (x == 'wsc:ok') { wscSearch = 1; }
else if (x == 'wsc:noav') { wscSearch = 2; }
else if (x == 'wsc:noupdate') { wscSearch = 3; }
else if (x == 'wsc:nofirewall') { wscSearch = 4; }
else if (x == 'wsc:any') { wscSearch = 5; }
if (x == '') {
// No search
for (var d in nodes) { nodes[d].v = true; }
} else if (ipSearch != null) {
// IP address search
for (var d in nodes) { nodes[d].v = ((nodes[d].ip != null) && (nodes[d].ip.indexOf(ipSearch) >= 0)); }
} else if (groupSearch != null) {
// Group filter
for (var d in nodes) { nodes[d].v = (meshes[nodes[d].meshid].name.toLowerCase().indexOf(groupSearch) >= 0); }
} else if (tagSearch != null) {
// Tag filter
for (var d in nodes) {
nodes[d].v = ((nodes[d].tags == null) && (tagSearch == '')) || ((nodes[d].tags != null) && (nodes[d].tags.indexOf(tagSearch) >= 0));
}
} else if (agentTagSearch != null) {
// Agent Tag filter
for (var d in nodes) {
nodes[d].v = (((nodes[d].agent != null) && (nodes[d].agent.tag == null)) && (agentTagSearch == '')) || ((nodes[d].agent != null) && (nodes[d].agent.tag != null) && (nodes[d].agent.tag.toLowerCase().indexOf(agentTagSearch) >= 0));
}
} else if (userSearch != null) {
// User search
for (var d in nodes) {
nodes[d].v = false;
if (nodes[d].users && nodes[d].users.length > 0) { for (var i in nodes[d].users) { if (nodes[d].users[i].toLowerCase().indexOf(userSearch) >= 0) { nodes[d].v = true; } } }
}
} else if (osSearch != null) {
// OS search
for (var d in nodes) { nodes[d].v = ((nodes[d].osdesc != null) && (nodes[d].osdesc.toLowerCase().indexOf(osSearch) >= 0)); }
} else if (amtSearch != null) {
// Intel AMT search
for (var d in nodes) { nodes[d].v = (nodes[d].intelamt != null) && ((amtSearch == '') || (nodes[d].intelamt.state == amtSearch)); }
} else if (wscSearch != null) {
// Windows Security Center
for (var d in nodes) {
nodes[d].v = false;
if (nodes[d].wsc) {
if ((wscSearch == 1) && (nodes[d].wsc.antiVirus == 'OK') && (nodes[d].wsc.autoUpdate == 'OK') && (nodes[d].wsc.firewall == 'OK')) { nodes[d].v = true; }
else if (((wscSearch == 2) || (wscSearch == 5)) && (nodes[d].wsc.antiVirus != 'OK')) { nodes[d].v = true; }
else if (((wscSearch == 3) || (wscSearch == 5)) && (nodes[d].wsc.autoUpdate != 'OK')) { nodes[d].v = true; }
else if (((wscSearch == 4) || (wscSearch == 5)) && (nodes[d].wsc.firewall != 'OK')) { nodes[d].v = true; }
}
}
} else if (x == '*') {
// Star filter
for (var d in nodes) { nodes[d].v = (stars[nodes[d]._id] == 1); }
} else {
// Device name search
try {
var rs = x.split(/\s+/).join('|'), rx = new RegExp(rs); // In some cases (like +), this can throw an exception.
for (var d in nodes) {
if (features2 & 0x00008000) {
nodes[d].v = (rx.test(nodes[d].name.toLowerCase())) || ((nodes[d].rnamel != null) && rx.test(nodes[d].rnamel.toLowerCase()));
} else {
if (showRealNames) {
nodes[d].v = (nodes[d].rnamel != null) && rx.test(nodes[d].rnamel.toLowerCase());
} else {
nodes[d].v = rx.test(nodes[d].name.toLowerCase());
}
}
}
} catch (ex) { for (var d in nodes) { nodes[d].v = true; } }
}
// Check power state
var onlineOnly = Q('OnlineCheckBox').checked;
if (onlineOnly) { for (var d in nodes) { if ((nodes[d].conn == null) || (nodes[d].conn == 0)) { nodes[d].v = false; } } }
mainUpdate(4);
}
var gotKeyPressEvent = false;
function ondeskkeypress(e, t) {
setSessionActivity();
if (desktop && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 1)) {
gotKeyPressEvent = true;
Q('softKeyboard').value = '';
// Check what keys we are allows to send
if (currentNode != null) {
var meshrights = GetMeshRights(currentNode.meshid);
var inputAllowed = ((features2 & 0x2000) == 0) && ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
if (inputAllowed == false) return false;
var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
}
return desktop.m.handleKeys(e);
}
if (terminal && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 5) && (t !== 1)) {
if (e.altKey == true) { return true; }
gotKeyPressEvent = true;
Q('softKeyboard').value = '';
var k = 0;
if (e.charCode != 0) { k = e.charCode; } else if (e.keyCode != 0) { k = e.keyCode; }
if (k != 0) {
if (terminal.urlname == 'sshterminalrelay.ashx') {
// SSH
terminal.socket.send('~' + String.fromCharCode(k));
} else {
// Agent
terminal.sendText(String.fromCharCode(k));
}
}
return false;
}
}
function ondeskkeydown(e, t) {
setSessionActivity();
if (desktop && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 1)) {
gotKeyPressEvent = false;
Q('softKeyboard').value = '';
// Check what keys we are allows to send
if (currentNode != null) {
var meshrights = GetMeshRights(currentNode.meshid);
var inputAllowed = ((features2 & 0x2000) == 0) && ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
if (inputAllowed == false) return false;
var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
}
return desktop.m.handleKeyDown(e);
}
if (terminal && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 5) && (t !== 1)) {
if (e.altKey == true) { return true; }
Q('softKeyboard').value = '';
gotKeyPressEvent = false;
var k = 0;
if (e.charCode != 0) { k = e.charCode; } else if (e.keyCode != 0) { k = e.keyCode; }
if (k == 8) { terminal.sendText(String.fromCharCode(k)); } // Enter and backspace
else if (e.ctrlKey && (k >= 64) && (k <= 95)) {
// Ctrl keys
if (terminal.urlname == 'sshterminalrelay.ashx') {
// SSH
terminal.socket.send('~' + String.fromCharCode(k - 64));
} else {
// Agent
terminal.sendText(String.fromCharCode(k - 64));
}
}
}
}
function ondeskkeyup(e, t) {
setSessionActivity();
if (desktop && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 1)) {
var inputStr = Q('softKeyboard').value;
Q('softKeyboard').value = '';
// Check what keys we are allows to send
if (currentNode != null) {
var meshrights = GetMeshRights(currentNode.meshid);
var inputAllowed = ((features2 & 0x2000) == 0) && ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
if (inputAllowed == false) return false;
var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
}
if ((gotKeyPressEvent == false) && (inputStr.length > 0) && desktop.m.SendKeyUnicode) {
// This is a mobile keyboard, we need to send that is in the input control.
var inputchar = inputStr[inputStr.length - 1].charCodeAt(0);
desktop.m.SendKeyUnicode(desktop.m.KeyAction.DOWN, inputchar);
desktop.m.SendKeyUnicode(desktop.m.KeyAction.UP, inputchar);
} else {
return desktop.m.handleKeyUp(e);
}
}
if (terminal && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 5) && (gotKeyPressEvent == false) && (t !== 1)) {
if (e.altKey == true) { return true; }
var inputStr = Q('softKeyboard').value;
Q('softKeyboard').value = '';
if (terminal.urlname == 'sshterminalrelay.ashx') {
// SSH
terminal.socket.send('~' + inputStr);
} else {
// Agent
if (inputStr)
terminal.sendText(inputStr);
}
return false;
}
}
var sort = 0;
var deviceHeaderId = 0;
var deviceHeaderCount;
var deviceHeaders = {};
var showRealNames = false;
var deviceHeaderTotal = 0;
var deviceHeaders = {};
var deviceHeadersTitles = {};
function updateDevices() {
var r = '', c = 0, current = null, count = 0, displayedMeshes = {}, groups = {}, groupCount = {};
// 3 wide, list view or desktop view
deviceHeaderId = 0;
deviceHeaderCount = {};
deviceHeaderTotal = 0;
deviceHeaders = {};
deviceHeadersTitles = {};
var current;
// Perform node sort
if (sort == 0) { nodes.sort(meshSort); }
else if (sort == 1) { nodes.sort(powerSort); }
else if (sort == 2) { if (showRealNames == true) { nodes.sort(deviceHostSort); } else { nodes.sort(deviceSort); } }
// Go thru the list of nodes and display them
for (var i in nodes) {
if (nodes[i].v == false) continue;
//var meshrights = GetNodeRights(nodes[i]);
if (sort == 0) {
// Mesh header
nodes.sort(meshSort);
//if (nodes[i].meshid != current) {
if (((meshes[nodes[i].meshid]?nodes[i].meshid:'*') != current)) {
deviceHeaderSet();
var extra = '';
if ((meshes[nodes[i].meshid] != null) && (meshes[nodes[i].meshid].mtype == 1)) { extra = '<span style=color:lightgray>' + ", Intel&reg; AMT only" + '</span>'; }
if (current != null) { if (c == 2) { r += '<td><div style=width:301px></div></td>'; } if (r != '') { r += '</tr></table>'; } }
r += '<div class=DevSt style=padding-top:4px><span style=float:right>';
//r += getMeshActions(meshes[nodes[i].meshid], meshrights);
if (meshes[nodes[i].meshid]) {
r += '</span><span id=MxMESH style=cursor:pointer onclick=goForward("' + nodes[i].meshid + '")>' + EscapeHtml(meshes[nodes[i].meshid].name) + '</span>' + extra + '<span id=DevxHeader' + deviceHeaderId + ' style=color:lightgray></span></div>';
current = nodes[i].meshid;
} else {
r += '</span><span id=MxMESH><i>' + "Individual Devices" + '</i></span><span id=DevxHeader' + deviceHeaderId + ' style=color:lightgray></span></div>';
current = '*';
}
displayedMeshes[current] = 1;
c = 0;
}
} else if (sort == 1) {
// Power header
if (nodes[i].pwr !== current) {
deviceHeaderSet();
if (current !== null) { if (c == 2) { r += '<td><div style=width:301px></div></td>'; } if (r != '') { r += '</tr></table>'; } }
r += '<div class=DevSt style=width:100%;padding-top:4px><span>' + PowerStateStr2(nodes[i].pwr) + '</span><span id=DevxHeader' + deviceHeaderId + ' style=color:lightgray></span></div>';
current = nodes[i].pwr;
c = 0;
}
} else if (sort == 2) {
// Device header
if (current == null) { current = '1'; }
}
count++;
r += '<div name=xxdevice onclick=goForward(\'' + nodes[i]._id + '\') class=devList1 id=\'' + nodes[i]._id + '\'></div>'; // This is a standin for the device, it gets rendered only if visible.
// If we are displaying devices by group, put the device in the right group.
/*
if ((sort == 3) && (r != '')) {
if (nodes[i].tags) {
for (var j in nodes[i].tags) {
var tag = nodes[i].tags[j];
if (groups[tag] == null) { groups[tag] = r; groupCount[tag] = 1; } else { groups[tag] += r; groupCount[tag] += 1; }
if (view == 3) break;
}
}
r = '';
}
*/
deviceHeaderTotal++;
if (typeof deviceHeaderCount[nodes[i].state] == 'undefined') { deviceHeaderCount[nodes[i].state] = 1; } else { deviceHeaderCount[nodes[i].state]++; }
}
// If there is nothing to display, explain the problem
var viewNothing = false;
if ((r == '') && (nodes.length > 0) && (Q('SearchInput').value != '')) {
viewNothing = true;
r = '<div style="margin:30px">' + "No devices matching this search." + '</div>';
}
// Display all empty device groups, we need to do this because users can add devices to these at any time.
if ((sort == 0) && (Q('SearchInput').value == '')) {
for (var i in meshes) {
var mesh = meshes[i];
if ((displayedMeshes[mesh._id] == null) && (IsMeshViewable(mesh))) {
if ((current != '') && (r != '')) { r += '</tr></table>'; }
r += '<div><div colspan=3 class=DevSt><span style=float:right>';
//r += getMeshActions(mesh, meshrights);
r += '</span><span id=MxMESH style=cursor:pointer onclick=goForward("' + mesh._id + '")>' + EscapeHtml(mesh.name) + '</span></div>';
if (mesh.mtype == 1) { r += '<div style=padding:10px><i>' + "No Intel&reg; AMT devices in this group"; }
if (mesh.mtype > 1) { r += '<div style=padding:10px><i>' + "No devices in this group"; }
r += '.</i></div></div>';
current = mesh._id;
count++;
}
}
}
if (count == 0) {
if ((Q('SearchInput').value != '') || (Q('OnlineCheckBox').checked)) {
QH('xdevices', '<div style="margin-top:50px;text-align:center"><span style="font-size:30px">' + "No devices" + '</span><br /><br />' + "No devices matching this search." + ' <a onclick=clearSearchInput() style=cursor:pointer>' + "Clear search filter" + '</a></div>');
} else {
QH('xdevices', '<div style="margin-top:50px;text-align:center"><span style="font-size:30px">' + "No devices" + '</span><br /><br />' + "Use the desktop version of this website to add devices." + '</div>');
}
} else {
QH('xdevices', r);
}
deviceHeaderSet();
for (var i in deviceHeaders) { QH(i, deviceHeaders[i]); }
for (var i in deviceHeadersTitles) { Q(i).title = deviceHeadersTitles[i]; }
onDevicesScrollEx();
}
var onDevicesTouchActive = false;
var onDevicesScrollnagleTimer = null;
function onDevicesScroll() {
if (onDevicesScrollnagleTimer == null) { onDevicesScrollnagleTimer = setTimeout(onDevicesScrollEx, 250); }
}
function onDeviceTouch(x) {
if (onDevicesTouchActive == x) return;
onDevicesTouchActive = x;
if (x == false) onDevicesScrollEx();
}
function onDevicesScrollEx() {
var devdivs = document.getElementsByName('xxdevice');
onDevicesScrollnagleTimer = null;
for (var i = 0; i < devdivs.length; i++) {
// Show
var node = getNodeFromId(devdivs[i].id)
if (node == null) break;
updateDeviceViewHtml(devdivs[i], node);
}
}
// Update a single device in the current view
function updateDeviceViewDevice(node) {
if (node == null) return;
var devdiv = Q(node._id);
if ((devdiv != null) && (devdiv.innerHTML != '')) { updateDeviceViewHtml(devdiv, node); } // Only update if the device is visible
}
function updateDeviceViewHtml(div, node) {
var visibleTop = Q('xdevices').scrollTop - 250, visibleBottom = Q('xdevices').scrollTop + Q('xdevices').clientHeight + 250;
if ((div.offsetTop >= visibleTop) && (div.offsetTop < visibleBottom)) {
var title = EscapeHtml(node.name);
if (title.length == 0) { title = '<i>' + "None" + '</i>'; }
if ((node.rname != null) && (node.rname.length > 0)) { title += ' / ' + EscapeHtml(node.rname); }
var name = EscapeHtml(node.name);
if (showRealNames == true && node.rname != null) name = EscapeHtml(node.rname);
if (name.length == 0) { name = '<i>' + "None" + '</i>'; }
// Add device notification icons
var devNotify = '', devNotifySub = '';
// This device is "starred"
if (stars[node._id] == 1) {
devNotifySub += '<img class=deviceNotifyDotSub src=images/icon-star-notify-16.png width=16 height=16>';
}
// This device has session information
if (node.sessions != null) {
// Display any agent messages
if (node.sessions.msg != null) {
devNotifySub += '<div style="width:16;height:16" class=deviceNotifyDotSub>' + Object.keys(node.sessions.msg).length + '</div>';
}
// Sessions are active
if ((node.sessions.kvm != null) || (node.sessions.terminal != null) || (node.sessions.files != null) || (node.sessions.tcp != null) || (node.sessions.udp != null)) {
devNotifySub += '<img class=deviceNotifyDotSub src=images/icon-relay-notify.png width=16 height=16>';
}
// Help is required
if (node.sessions.help != null) {
devNotifySub += '<img class=deviceNotifyDotSub src=images/icon-help-notify-16.png width=16 height=16>';
}
// Battery state
if (node.sessions.battery != null) {
var bat = node.sessions.battery;
var statestr = '';
if (bat.state == 'ac') { statestr = "Device is plugged-in"; }
else if (bat.state == 'dc') { statestr = "Device is battery powered"; }
var levelstr = '', levelnum = -1;
if ((typeof bat.level == 'number') && (bat.level >= 0) && (bat.level <= 100)) {
levelstr = bat.level + '%';
levelnum = (Math.floor((bat.level + 10) / 25) + 1);
if (levelnum > 5) { lvl = 5; }
if (bat.state == 'ac') { if (bat.level == 100) { levelnum = 11; } else { levelnum += 5; } }
}
if (levelnum > 0) {
devNotify += '<div class="deviceBatterySmall deviceBatterySmall' + levelnum + '" title="' + ((statestr != null) ? (statestr + ', ' + levelstr) : levelstr) + '"></div>';
}
}
}
// Add any device icons
if (devNotifySub != '') { devNotify += '<div class=deviceNotifyDot>' + devNotifySub + '</div>'; }
// Node
var icon = node.icon, nodestate = NodeStateStr(node);
if (((!node.conn) || (node.conn == 0)) && (node.mtype != 3)) { icon += ' gray'; }
div.innerHTML = '<div>' + devNotify + '<div class="i' + icon + ' devList2"></div><div class=devList3><div class=devList4><b>' + name + '</b></div><div class=devList5>' + nodestate + '</div></div></div>';
} else {
div.innerHTML = ''; // Hide
}
}
// Show device help requests
function showDeviceHelpRequests(nodeid, force, e) {
if (e) haltEvent(e);
if (xxdialogMode && !force) return false;
var node = null, x = '';
if (nodeid == null) { node = currentNode; } else { node = getNodeFromId(nodeid); }
if ((node == null) || (node.sessions == null)) { setDialogMode(0); return false; }
if (node.sessions.help != null) { for (var j in node.sessions.help) { x += '<div style=margin-bottom:6px><b>' + EscapeHtml(j) + '</b></div><div style=margin-bottom:6px>' + EscapeHtml(node.sessions.help[j]) + '</div>'; } }
if (x != '') { setDialogMode(2, "Help Requests" + ' - ' + EscapeHtml(node.name), 1, null, x, 'HELPREQ-' + node._id); } else { setDialogMode(0); }
return false;
}
// Show currently active sessions on this device
function showDeviceSessions(nodeid, force, e) {
if (((force !== true) && xxdialogMode) || (currentNode == null)) return;
var node = currentNode, x = '';
for (var i in node.sessions) {
if ((i == 'kvm') && (node.sessions.multidesk == null)) {
x += '<u>' + "Remote Desktop" + '</u>';
for (var j in node.sessions.kvm) {
if (j.startsWith('user/')) {
var trash = '';
if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("kvm", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
x += addHtmlValue4(getUserName(j), ((node.sessions.kvm[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.kvm[j]))) + trash);
} else if (j == 'busy') {
x += addHtmlValue2("Device is busy", ((node.sessions.kvm[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.kvm[j]))));
}
}
} else if (i == 'multidesk') {
x += '<u>' + "Remote Desktop" + '</u>';
for (var j in node.sessions.multidesk) {
var trash = '';
if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("multidesk", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
x += addHtmlValue4(getUserName(j), ((node.sessions.multidesk[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.multidesk[j]))) + trash);
}
} else if (i == 'terminal') {
x += '<u>' + "Terminal" + '</u>';
for (var j in node.sessions.terminal) {
var trash = '';
if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("terminal", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
x += addHtmlValue4(getUserName(j), ((node.sessions.terminal[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.terminal[j]))) + trash);
}
} else if (i == 'files') {
x += '<u>' + "Files" + '</u>';
for (var j in node.sessions.files) {
var trash = '';
if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("files", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
x += addHtmlValue4(getUserName(j), ((node.sessions.files[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.files[j]))) + trash);
}
} else if (i == 'tcp') {
x += '<u>' + "TCP Routing" + '</u>';
for (var j in node.sessions.tcp) {
var trash = '';
if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("tcp", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
x += addHtmlValue4(getUserName(j), ((node.sessions.tcp[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.tcp[j]))) + trash);
}
} else if (i == 'udp') {
x += '<u>' + "UDP Routing" + '</u>';
for (var j in node.sessions.udp) {
var trash = '';
if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("udp", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
x += addHtmlValue4(getUserName(j), ((node.sessions.udp[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.udp[j]))) + trash);
}
}
}
if (x != '') { setDialogMode(2, "Sessions" + ' - ' + EscapeHtml(node.name), 1, null, x, 'SESSIONS-' + node._id); } else { setDialogMode(0); }
}
function endDeviceSession(protocol, nodeid, userid) {
var userIdSplit = decodeURIComponent(userid).split('/'), uid = userIdSplit[0] + '/' + userIdSplit[1] + '/' + userIdSplit[2], guestname = null;
if ((userIdSplit.length == 4) && (userIdSplit[3].startsWith('guest:'))) { guestname = atob(userIdSplit[3].substring(6)); }
if (protocol == 'multidesk') {
meshserver.send({ action: 'endDesktopMultiplex', nodeid: decodeURIComponent(nodeid), xuserid: uid, guestname, guestname });
} else {
meshserver.send({ action: 'msg', type: 'endtunnel', nodeid: decodeURIComponent(nodeid), xuserid: uid, guestname, guestname, protocol: protocol });
}
}
// Show currently active sessions on this device
function showDeviceMessages(nodeid, force, e) {
if (e) haltEvent(e);
if (xxdialogMode && !force) return false;
var node = null, x = '<div style=max-height:200px;width:100%;overflow-y:auto;overflow-x:hidden>', count = 0;
if (nodeid == null) { node = currentNode; } else { node = getNodeFromId(nodeid); }
if ((node == null) || (node.sessions == null) || (node.sessions.msg == null)) { setDialogMode(0); return false; }
for (var i in node.sessions.msg) {
var msg = i, icon = 5;
if (typeof node.sessions.msg[i].msg == 'string') { msg = node.sessions.msg[i].msg; }
if (typeof node.sessions.msg[i].icon == 'number') { icon = node.sessions.msg[i].icon; }
if ((icon < 1) || (icon > 9)) { icon = 5; }
x += '<table style=width:100%><td style=width:24px><div class=NotifyIconSmall' + icon + '></div><td><div style="border-radius:5px;background-color:#BBB;width:calc(100% - 18px);padding:8px">' + EscapeHtml(msg) + '</div></table>';
count++;
}
x += '</div>';
if (count > 0) setDialogMode(2, "Agent Messages" + ' - ' + EscapeHtml(node.name), 1, null, x, 'MESSAGES-' + node._id);
return false;
}
var powerStatetable = ['', "Powered", "Sleep", "Sleep", "Sleep", "Hibernating", "Power off", "Present", "Off"];
var powerStateStrings = ['', "Powered", "Sleeping", "Sleeping", "Deep Sleep", "Hibernating", "Soft-Off", "Present", "Off"];
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", "The device is powered off"];
var powerColorTable = ['#00000000', 'black', 'blue', 'blue', 'lightblue', 'blueviolet', 'darkgreen', 'lightseagreen', 'lightseagreen'];
function NodeStateStr(node) {
var states = [];
if (node.state > 0 && node.state < powerStatetable.length) state.push(powerStatetable[node.state]);
if (node.conn) {
if ((node.conn & 1) != 0) { states.push('<span>' + ((node.mtype == 4) ? ((node.porttype == 'PDU') ? "Switch" : "IP-KVM") : "Agent") + '</span>'); }
if ((node.conn & 2) != 0) { states.push('<span>' + "CIRA" + '</span>'); }
else if ((node.conn & 4) != 0) { states.push('<span>' + "Intel&reg; AMT" + '</span>'); }
if ((node.conn & 8) != 0) { states.push('<span>' + "Relay" + '</span>'); }
if ((node.conn & 16) != 0) { states.push('<span>' + "MQTT" + '</span>'); }
}
if ((node.pwr != null) && (node.pwr != 0)) { states.push(powerStateStrings[node.pwr]); }
return states.join(', ');
}
function PowerStateStr(x) {
if (x < powerStatetable.length) return powerStatetable[x];
return '';
}
function PowerStateStr2(x) {
if ((x != 0) && (x < powerStatetable.length)) return powerStatetable[x];
return "Unknown";
}
function onSortSelectChange(skipsave) {
sort = document.getElementById('sortselect').selectedIndex;
if (!skipsave) { putstore('sort', sort); }
mainUpdate(4);
}
function deviceHeaderSet() {
if (deviceHeaderId == 0) { deviceHeaderId = 1; return; }
deviceHeaders['DevxHeader' + deviceHeaderId] = ', ' + deviceHeaderTotal + ((deviceHeaderTotal == 1) ? " node" : " nodes");
var title = '';
for (var x in deviceHeaderCount) { if (title.length > 0) title += ', '; title += deviceHeaderCount[x] + ' ' + PowerStateStr2(x); }
deviceHeadersTitles['DevxHeader' + deviceHeaderId] = title;
deviceHeaderId++;
deviceHeaderCount = {};
deviceHeaderTotal = 0;
}
/*
function meshSort(a, b) { if (a.meshnamel > b.meshnamel) return 1; if (a.meshnamel < b.meshnamel) return -1; if (a.meshid == b.meshid) { if (showRealNames == true) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; } else { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; } } return 0; }
function powerSort(a, b) { var ap = a.pwr ? a.pwr : 0; var bp = b.pwr ? b.pwr : 0; if (ap == bp) { if (showRealNames == true) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; } else { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; } } if (ap > bp) return 1; if (ap < bp) return -1; return 0; }
function deviceSort(a, b) { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; }
function deviceHostSort(a, b) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; }
*/
var sortCollator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })
function meshSort(a, b) {
var x = sortCollator.compare(a.meshnamel, b.meshnamel);
if (x != 0) return x;
x = sortCollator.compare(a.meshid, b.meshid);
if (x != 0) return x;
if (showRealNames == true) { return sortCollator.compare(a.rnamel, b.rnamel); }
return sortCollator.compare(a.namel, b.namel);
}
function powerSort(a, b) { var ap = a.pwr ? a.pwr : 0; var bp = b.pwr ? b.pwr : 0; if (ap > bp) return -1; if (ap < bp) return 1; if (ap == bp) { if (showRealNames == true) { return sortCollator.compare(a.rnamel, b.rnamel); } else { return sortCollator.compare(a.namel, b.namel); } } }
function deviceSort(a, b) { return sortCollator.compare(a.namel, b.namel); }
function deviceHostSort(a, b) { return sortCollator.compare(a.rnamel, b.rnamel); }
//
// MY DEVICE
//
function refreshDevice(nodeid) {
if (!currentNode || currentNode._id != nodeid) return;
gotoDevice(nodeid, xxcurrentView, true);
}
var currentDevicePanel = 0;
var currentNode;
var powerTimelineNode = null;
var powerTimelineReq = null;
var powerTimelineUpdate = null;
var powerTimeline = null;
function getCurrentNode() { return currentNode; };
function gotoDevice(nodeid, panel, refresh) {
// Remind the user to verify the email address
if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" to change and verify an email address."); return; }
// Remind the user to add two factor authentication
if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
var node = getNodeFromId(nodeid);
if (node == null) { goBack(); return; }
var mesh = meshes[node.meshid];
var meshrights = GetNodeRights(node);
var deviceSwitch = ((currentNode == null) || (currentNode._id != nodeid));
if (!currentNode || currentNode._id != node._id || refresh == true) {
currentNode = node;
// Setup session notification
QV('p10deviceNotify', (currentNode.sessions != null) && ((node.sessions.kvm != null) || (node.sessions.terminal != null) || (node.sessions.files != null) || (node.sessions.tcp != null) || (node.sessions.udp != null)));
QV('p10deviceStar', stars[currentNode._id] == 1);
QV('p10deviceHelp', (currentNode.sessions != null) && (currentNode.sessions.help != null))
if ((currentNode.sessions != null) && (currentNode.sessions.msg != null)) { QV('p10deviceMsg', true); QH('p10deviceMsg', Object.keys(currentNode.sessions.msg).length); } else { QV('p10deviceMsg', false); }
// Device Battery
QV('p10deviceBattery', false);
if ((currentNode.sessions != null) && (currentNode.sessions.battery != null)) {
var bat = currentNode.sessions.battery;
var statestr = '';
if (bat.state == 'ac') { statestr = "Device is plugged-in"; }
if (bat.state == 'dc') { statestr = "Device is battery powered"; }
var levelstr = '', levelnum = -1;
if ((typeof bat.level == 'number') && (bat.level >= 0) && (bat.level <= 100)) {
levelstr = bat.level + '%';
levelnum = (Math.floor((bat.level + 10) / 25) + 1);
if (levelnum > 5) { lvl = 5; }
if (bat.state == 'ac') { if (bat.level == 100) { levelnum = 11; } else { levelnum += 5; } }
}
if (levelnum > 0) {
Q('p10deviceBattery').title = (statestr != null) ? (statestr + ', ' + levelstr) : levelstr;
QV('p10deviceBattery', true);
Q('p10deviceBattery').className = 'deviceBatteryLarge deviceBatteryLarge' + levelnum;
}
} else {
QV('p10deviceBattery', false);
}
// Add node name
var nname = EscapeHtml(node.name);
if (nname.length == 0) { nname = '<i>' + "None" + '</i>'; }
if ((meshrights & 4) != 0) { nname = '<span onclick=showEditNodeValueDialog(0) style=cursor:pointer>' + nname + '</span>'; }
QH('p10deviceName', nname);
// Node attributes
var x = '<table style=width:100%>';
// Attribute: Mesh
if (mesh) { x += addDeviceAttribute('<span>' + "Group" + '</span>', '<a onclick=goForward("' + node.meshid + '") style=cursor:pointer>' + EscapeHtml(meshes[node.meshid].name) + '</a>'); }
// Attribute: Name
if (node.rname != null) { x += addDeviceAttribute('<span>' + "Name" + '</span>', '<span>' + EscapeHtml(node.rname) + '</span>'); }
// Attribute: Host
if ((((features & 1) == 0) && (node.mtype != 4)) || (node.mtype == 3)) { // If not WAN-only, local hostname is in use
if ((meshrights & 4) != 0) {
if (node.host) {
x += addDeviceAttribute("Hostname", '<span onclick=showEditNodeValueDialog(1) style=cursor:pointer>' + EscapeHtml(node.host) + '</span>');
} else {
x += addDeviceAttribute("Hostname", '<span onclick=showEditNodeValueDialog(1) style=cursor:pointer><i>' + "None" + '</i></span>');
}
} else {
x += addDeviceAttribute("Hostname", EscapeHtml(node.host));
}
}
// Attribute: Description
var description = node.desc ? EscapeHtml(node.desc) : '<i>' + "None" + '</i>';
if ((meshrights & 4) != 0) {
x += addDeviceAttribute("Description", '<span onclick=showEditNodeValueDialog(2) style=cursor:pointer>' + description + '</span>');
} else {
x += addDeviceAttribute("Description", description);
}
// IP-KVM information
if (node.mtype == 4) {
if (node.portnum != null) { x += addDeviceAttribute("Port Number", node.portnum); }
if (node.porttype != null) { x += addDeviceAttribute("Port Type", node.porttype); }
}
// Attribute: Mesh Agent
if ((node.agent != null) && (node.agent.id != null) && (node.mtype == 3)) {
if (node.agent.id == 4) { x += addDeviceAttribute("Device Type", "Windows"); }
if (node.agent.id == 6) { x += addDeviceAttribute("Device Type", "Linux"); }
if (node.agent.id == 29) { x += addDeviceAttribute("Device Type", "macOS"); }
} else if ((node.agent != null) && (node.agent.id != null) && (node.agent.ver != null)) {
var str = '';
if (node.agent.id <= agentsStr.length) { str = agentsStr[node.agent.id]; } else { str = agentsStr[0]; }
if (node.agent.ver != 0) { str += ' v' + node.agent.ver; }
if (node.agent.id == 14) { str = node.agent.core; }
if ((node.agent.root === false) && ((node.conn & 1) != 0)) { str += ', ' + "Restricted"; }
x += addDeviceAttribute("Mesh Agent", str);
}
// Attribute: Intel AMT
if (node.intelamt != null) {
var str = '';
var provisioningStates = { 0: nobreak("Not Activated (Pre)"), 1: nobreak("Not Activated (In)"), 2: nobreak("Activated") };
if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>' + nobreak("Unknown State") + '</i>, v' + EscapeHtml(node.intelamt.ver); }
else if ((node.intelamt.ver == null) && (node.intelamt.state == 2)) { str += '<i>' + "Activated" + '</i>'; }
else if ((node.intelamt.ver == null) || (node.intelamt.state == null)) { str += '<i>' + "Unknown Version & State" + '</i>'; }
else {
str += provisioningStates[node.intelamt.state];
if (node.intelamt.flags) { if (node.intelamt.flags & 2) { str = ' <span>' + "CCM" + '</span>'; } else if (node.intelamt.flags & 4) { str = ' <span>' + "ACM" + '</span>'; } }
str += (', v' + EscapeHtml(node.intelamt.ver));
}
// If Intel AMT is activated, show additional options
if (node.intelamt.state == 2) {
if (node.intelamt.tls == 1) { str += ', <span title="' + "Intel&reg; AMT is setup with TLS network security" + '">' + "TLS" + '</span>'; }
var editUserCredentialsIcon = false;
if (node.intelamt.user == null || node.intelamt.user == '') { // If credentials are not set, allow setting them.
if ((meshrights & 4) != 0) {
str += ', <i style=color:#FF0000;cursor:pointer title="' + "Edit Intel&reg; AMT credentials" + '" onclick=editDeviceAmtSettings("' + node._id + '")>' + "No Credentials" + '</i>';
editUserCredentialsIcon = true;
} else {
str += ', <i style=color:#FF0000>' + "No Credentials" + '</i>';
}
} else if (((features2 & 1) != 0) && (node.intelamt.warn != null)) { // If AMT manager is running and warned of invalid credentials, allow setting them.
var warn = null;
if ((node.intelamt.warn & 1) != 0) { warn = "Invalid Credentials"; }
if ((node.intelamt.warn & 8) != 0) { warn = "Trying Credentials"; }
if (warn != null) {
if ((meshrights & 4) != 0) {
str += ', <i style=color:#FF0000;cursor:pointer title="' + "Edit Intel&reg; AMT credentials" + '" onclick=editDeviceAmtSettings("' + node._id + '")>' + warn + '</i>';
editUserCredentialsIcon = true;
} else {
str += ', <i style=color:#FF0000>' + warn + '</i>';
}
}
}
// If the AMT manager is not running, always allow Intel AMT credentials to be edited.
if (((meshrights & 4) != 0) && ((features2 & 1) == 0)) { editUserCredentialsIcon = true; }
str += ' ';
if (editUserCredentialsIcon) {
str += '<img src=images/link4.png height=10 width=10 title="' + "Edit Intel&reg; AMT credentials" + '" style=cursor:pointer onclick=editDeviceAmtSettings("' + node._id + '")>';
}
}
/*
if (node.intelamt.state == 2) {
if (node.intelamt.user == null || node.intelamt.user == '') {
if ((meshrights & 4) != 0) {
str += ', <i style=color:#FF0000;cursor:pointer onclick=editDeviceAmtSettings("' + node._id + '")>' + nobreak("No Credentials") + '</i>';
} else {
str += ', <i style=color:#FF0000>' + "No Credentials" + '</i>';
}
}
str += ' ';
if ((meshrights & 4) != 0) {
str += '<img src=images/link4.png height=10 width=10 style=cursor:pointer onclick=editDeviceAmtSettings("' + node._id + '")>';
}
}
*/
var meName = "Intel&reg; ME";
if (typeof node.intelamt.sku == 'number') {
if ((node.intelamt.sku & 8) != 0) { meName = "Intel&reg; AMT"; }
else if ((node.intelamt.sku & 16) != 0) { meName = "Intel&reg; SM"; }
}
x += addDeviceAttribute(meName, str);
}
// Attribute: Mesh Agent Tag
if ((node.agent != null) && (node.agent.tag != null) && (node.agent.tag != 'mailto:')) {
var tag = EscapeHtml(node.agent.tag);
if (tag.startsWith('mailto:')) { tag = '<a href="' + tag + '">' + tag.substring(7) + '</a>'; }
x += addDeviceAttribute("Agent Tag", tag);
}
// Attribute: Intel AMT
//if (node.intelamt && node.intelamt.user) { x += addDeviceAttribute('Intel&reg; AMT', node.intelamt.user); }
// Attribute: Connectivity (Only show this if more than just the agent is connected).
var connectivity = node.conn;
if (connectivity && connectivity > 1) {
var cstate = [];
if ((node.conn & 1) != 0) cstate.push('<span>' + ((node.mtype == 4) ? ((node.porttype == 'PDU') ? "Switch" : "IP-KVM") : "Agent") + '</span>');
if ((node.conn & 2) != 0) cstate.push('<span>' + "Intel&reg; AMT CIRA" + '</span>');
else if ((node.conn & 4) != 0) cstate.push('<span>' + "Intel&reg; AMT" + '</span>');
if ((node.conn & 8) != 0) cstate.push('<span>' + "Agent Relay" + '</span>');
if ((node.conn & 16) != 0) cstate.push('<span>' + "MQTT" + '</span>');
x += addDeviceAttribute("Connectivity", cstate.join(', '));
}
// Node tags
var groupingTags = '<i>' + "None" + '</i>';
if (node.tags != null) { groupingTags = ''; for (var i in node.tags) { groupingTags += '<span style="background-color:lightgray;padding:3px;border-radius:5px">' + EscapeHtml(node.tags[i]) + '</span> '; } }
if ((meshrights & 4) != 0) {
x += addDeviceAttribute("Tags", '<span onclick=showEditNodeValueDialog(3) style=cursor:pointer;color:black>' + groupingTags + '</span>');
} else {
x += addDeviceAttribute("Tags", '<span style=line-height:26px;color:black>' + groupingTags + '</span>');
}
// SSH & RDP Credentials
if ((node.ssh != null) || (node.rdp != null)) {
var y = [];
if ((meshrights & 4) != 0) {
if (node.ssh != null) { y.push('<span onclick=showClearSshDialog(3) style=cursor:pointer>' + ((node.ssh == 1) ? "SSH-User+Pass" : ((node.ssh == 2) ? "SSH-User+Key+Pass" : "SSH-User+Key")) + ' <img class=hoverButton src="images/link5.png" width=10 height=10 /></span>'); }
if (node.rdp != null) { y.push('<span onclick=showClearRdpDialog(3) style=cursor:pointer>' + "RDP" + ' <img class=hoverButton src="images/link5.png" width=10 height=10 /></span>'); }
} else {
if (node.ssh != null) { y.push(((node.ssh == 1) ? "SSH-User+Pass" : ((node.ssh == 2) ? "SSH-User+Key+Pass" : "SSH-User+Key"))); }
if (node.rdp != null) { y.push("RDP"); }
}
x += addDeviceAttribute("Credentials", y.join(', '));
}
x += '</table><br />';
// Show action button, only show if we have permissions 4, 8, 64
if (((meshrights & (4 + 8 + 64 + 262144)) != 0) && (node.mtype < 3)) { x += '<input type=button value="' + "Actions" + '" onclick=deviceActionFunction() />'; }
x += '<input type=button value="' + "Notes" + '" onclick=showNotes(' + ((meshrights & 128) == 0) + ',"' + encodeURIComponent(node._id) + '") />';
//if ((connectivity & 1) && (meshrights & 8) && (node.agent.id < 5)) { x += '<input type=button value=Toast onclick=deviceToastFunction() />'; }
if ((node.mtype == 4) && (connectivity & 1)) {
if (node.porttype == 'PDU') {
if (node.pwr == 1) {
if (meshrights & 0x40000) { x += '<input type=button value="' + "Turn off" + '" title="' + "Turn off" + '" onclick=setIpPduState(0) />'; }
} else if (node.pwr == 8) {
if (meshrights & 0x40) { x += '<input type=button value="' + "Turn on" + '" title="' + "Turn on" + '" onclick=setIpPduState(1) />'; }
}
} else {
if (meshrights & 8) { x += '<input type=button value="' + "Remote Control" + '" title="' + "Remote Control" + '" onclick=openIpKvmRemoteControl("' + encodeURIComponentEx(node._id) + '") />'; }x
}
}
QH('p10html', x);
// If we are looking at a local non-windows device, enable terminal and files capability.
if ((node.mtype == 3) && (node.agent != null) && (node.agent.id > 4) && (features2 & 0x00000200)) { node.agent.caps = 6; }
// Show node last 7 days timeline
//drawDeviceTimeline();
setupTerminal();
setupFiles();
if (meshrights & 16) { setupConsole(); }
// Show bottom buttons
x = '<div style=float:right;font-size:x-small;margin-right:10px>';
if ((meshrights & 0x8000) != 0) { x += '<a style=cursor:pointer onclick=p10showDeleteNodeDialog("' + node._id + '")>' + "Delete Device" + '</a>'; }
x += '</div><div style=font-size:x-small>';
if (webRelayPort != 0) {
x += '<a onclick=p10WebRouter("' + node._id + '",1,' + (node.httpport ? node.httpport : 80) + ')>' + "HTTP" + ((node.httpport && (node.httpport != 80)) ? '/' + node.httpport : '') + '</a>&nbsp;';
x += '<a onclick=p10WebRouter("' + node._id + '",2,' + (node.httpsport ? node.httpsport : 443) + ')>' + "HTTPS" + ((node.httpsport && (node.httpsport != 443)) ? '/' + node.httpsport : '') + '</a>&nbsp;';
}
//if (mesh.mtype == 2) x += '<a style=cursor:pointer onclick=p10showNodeNetInfoDialog("' + node._id + '")>Interfaces</a>&nbsp;';
//if (xxmap != null) x += '<a style=cursor:pointer onclick=p10showNodeLocationDialog("' + node._id + '")>Location</a>&nbsp;';
x += '</div><br>'
QH('p10html3', x);
// Set the node power state
var powerstate = PowerStateStr(node.state);
//if (node.state == 0) { powerstate = 'Unknown State'; }
if ((connectivity & 1) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += ((node.mtype == 4) ? ((node.porttype == 'PDU') ? "Switch" : "IP-KVM") : "Mesh Agent"); }
if ((connectivity & 2) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += "Intel&reg; AMT connected"; }
else if ((connectivity & 4) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += "Intel&reg; AMT detected"; }
if ((connectivity & 16) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += "MQTT channel connected"; }
if ((node.porttype == 'PDU') || ((node.pwr > 1) && (node.pwr != 7))) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += powerStateStrings[node.pwr]; }
QH('MainComputerState', '<span style=font-size:12px>' + powerstate + '</span>');
// Set the node icon
QH('MainComputerImage', '<div class="i' + node.icon + '"></div>');
// Request the power timeline
if ((powerTimelineNode != currentNode._id) && (powerTimelineReq != currentNode._id)) {
QH('p10html2', '');
powerTimelineReq = currentNode._id;
meshserver.send({ action: 'powertimeline', nodeid: currentNode._id });
meshserver.send({ action: 'lastconnect', nodeid: currentNode._id });
meshserver.send({ action: 'getsysinfo', nodeid: currentNode._id });
meshserver.send({ action: 'getnetworkinfo', nodeid: currentNode._id });
QH('p10detailshtml', '');
}
// Clear user consent status if present
if (deviceSwitch) {
p11clearConsoleMsg();
p13clearConsoleMsg();
}
// Clear the desktop session selector
QV('p11DeskSessionSelector', false);
QH('p11DeskSessionSelector', '');
}
setupDesktop(); // Always refresh the desktop, even if we are on the same device, we need to do some canvas switching.
if (!panel) panel = 10;
go(panel);
// Update the footer menu
if (xxcurrentView == 10) { setupDeviceMenu(); }
}
function setIpPduState(op) {
if (op == 0) {
setDialogMode(2, "Power Operation", 3, function () { meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: 2 }); }, "Perform power off?"); // Turn off
} else {
setDialogMode(2, "Power Operation", 3, function () { meshserver.send({ action: 'wakedevices', nodeids: [currentNode._id] }); }, "Perform power on?"); // Turn on
}
}
function openIpKvmRemoteControl(nodeid) {
if (xxdialogMode) return;
var nid = decodeURIComponent(nodeid).split('/')[2];
safeNewWindow('/ipkvm.ashx/' + nid + '/', 'ipkvm:' + nid);
}
function deviceToastFunction() {
if (xxdialogMode) return;
setDialogMode(2, "Device Toast", 3, deviceToastFunctionEx, '<textarea id=d2devToast style=width:100%;height:80px;resize:none;overflow-y:scroll></textarea>');
}
function deviceToastFunctionEx() {
meshserver.send({ action: 'toast', nodeids: [currentNode._id], title: 'MeshCentral', msg: Q('d2devToast').value });
}
// && ((meshrights == 0xFFFFFFFF) || ((meshrights & 65536) == 0))
function setupDeviceMenu(op, obj) {
var meshrights = GetNodeRights(currentNode);
if (op != null) { currentDevicePanel = op; }
QV('p10general', currentDevicePanel == 0);
QV('p10desktop', currentDevicePanel == 1); // Show if we have remote control rights or desktop view only rights
QV('p10files', currentDevicePanel == 2);
QV('p10details', currentDevicePanel == 3);
QV('p10console', currentDevicePanel == 4);
QV('p10terminal', currentDevicePanel == 5);
var menus = [];
if (currentDevicePanel != 0) { menus.push({ n: "General", f: 'setupDeviceMenu(0)' }); }
if ((currentDevicePanel != 1) &&
(currentNode != null) &&
((meshrights & 8) || (meshrights & 256)) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 65536) == 0)) &&
(((currentNode.agent == null) && (currentNode.intelamt) && ((typeof currentNode.intelamt.sku !== 'number') || ((currentNode.intelamt.sku & 8) != 0))) || (currentNode.agent && (currentNode.agent.caps & 1)))
) { menus.push({ n: "Desktop", f: 'setupDeviceMenu(1)' }); }
if ((currentDevicePanel != 5) &&
(currentNode != null) &&
((meshrights & 8) || (meshrights & 256)) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 512) == 0)) &&
(((currentNode.agent == null) && (currentNode.intelamt) && ((typeof currentNode.intelamt.sku !== 'number') || ((currentNode.intelamt.sku & 8) != 0))) || (currentNode.agent && (currentNode.agent.caps & 2)))
) { menus.push({ n: "Terminal", f: 'setupDeviceMenu(5)' }); }
if ((currentDevicePanel != 2) && (currentNode != null) && (meshrights & 8) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 1024) == 0)) && ((currentNode.mtype != 1) && (currentNode.agent) && (currentNode.agent.caps & 4))) { menus.push({ n: "Files", f: 'setupDeviceMenu(2)' }); }
if ((currentDevicePanel != 3) && (currentNode != null) && (currentNode.mtype < 3) && ((meshrights & 1048576) != 0)) { menus.push({ n: "Details", f: 'setupDeviceMenu(3)' }); }
if ((currentDevicePanel != 4) && (currentNode != null) && (meshrights & 0x00000010) && (currentNode.mtype == 2)) { menus.push({ n: "Console", f: 'setupDeviceMenu(4)' }); }
updateFooterMenu(menus);
updateCurrentUrl();
if (currentDevicePanel == 1) { deskAdjust(); }
}
function deviceActionFunction() {
if (xxdialogMode) return;
var rights = GetNodeRights(currentNode), count = 0;
var x = "Select an operation to perform on this device." + '<br /><br />';
var y = '<select id=d2deviceop style=float:right;width:170px onchange=deviceActionFunctionValidate()>';
var z = '';
if ((currentNode.agent != null) && (currentNode.agent.id == 14)) {
if (((currentNode.conn & 1) != 0) && ((rights & 8) != 0)) {
count++;
y += '<option value=400>' + "Flash" + '</option>';
y += '<option value=401>' + "Vibrate" + '</option>';
z += '<div id=d2devicetimediv>' + addHtmlValue("Time", '<select id=d2devicetime style=float:right;width:170px><option value=1000>' + "1 second" + '</option><option value=5000>' + "5 seconds" + '</option><option value=10000>' + "10 seconds" + '</option></select>') + '</div>';
}
} else {
if ((rights & 64) != 0) { count++; y += '<option value=100>' + "Wake-up" + '</option>'; } // Wake-up permission
//if (((currentNode.conn & 1) != 0) && ((rights & 131072) != 0)) { count++; y += '<option value=106>' + "Run Commands" + '</option>'; } // Remote command permission
if ((currentNode.conn != 0) && ((rights & 262144) != 0)) { count++; y += '<option value=4>' + "Sleep" + '</option><option value=3>' + "Reset" + '</option><option value=2>' + "Power off" + '</option>'; }
//if ((currentNode.conn & 16) != 0) { count++; y += '<option value=103>' + "Send MQTT Message" + '</option>'; }
if ((currentNode.intelamt != null) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && ((rights & 262144) != 0)) {
count++;
y += '<option value=310>' + "Intel&reg; AMT Reset" + '</option>';
y += '<option value=308>' + "Intel&reg; AMT Power off" + '</option>';
}
if ((currentNode.intelamt != null) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && ((rights & 64) != 0)) {
count++;
y += '<option value=302>' + "Intel&reg; AMT Power on" + '</option>';
}
//if ((getNodeAmtVersion(currentNode) >= 15) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && (rights == 0xFFFFFFFF) && ((features & 0x00000400) == 0)) { count++; y += '<option value=107>' + "Intel&reg; AMT One Click Recovery" + '</option>'; } // CIRA (2) or AMT (4) connected
//if (((currentNode.conn & 1) != 0) && ((rights & 32768) != 0)) { count++; y += '<option value=104>' + "Uninstall Agent" + '</option>'; }
}
y += '</select>';
x += addHtmlValue("Operation", y);
if (count == 0) { x = "No actions currently available for this device."; }
setDialogMode(2, "Device Action", (count == 0) ? 2 : 3, deviceActionFunctionEx, x + z);
if (count > 0) { deviceActionFunctionValidate(); }
}
function deviceActionFunctionValidate() {
var op = Q('d2deviceop').value;
try { QV('d2devicetimediv', (op == 400) || (op == 401)); } catch (ex) { }
}
function deviceActionFunctionEx() {
var op = Q('d2deviceop').value;
if (op == 100) {
// Device wake
meshserver.send({ action: 'wakedevices', nodeids: [currentNode._id] });
} else if (op == 103) {
// Send MQTT Message
//p10showSendMqttMsgDialog([currentNode._id]);
} else if (op == 104) {
// Uninstall agent
//p10showSendUninstallAgentDialog([currentNode._id]);
} else if (op == 106) {
// Run commands
/*
var wintype = false, linuxtype = false;
if (currentNode.agent) { if ((currentNode.agent.id > 0) && (currentNode.agent.id < 5)) { wintype = true; } else { linuxtype = true; } }
if ((wintype == true) || (linuxtype == true)) {
var x = "Run commands on selected devices." + '<br />';
if (wintype == true) {
x += '<select id=d2cmdtype style=width:100%;margin-bottom:4px;margin-top:4px>';
x += '<option value=1>' + "Windows Command Prompt" + '</option><option value=2>' + "Windows PowerShell" + '</option>';
if (linuxtype == true) { x += '<option value=3>' + "Linux/BSD/macOS Command Shell" + '</option>'; }
x += '</select>';
}
x += '<select id=d2cmduser style=width:100%;margin-bottom:4px><option value=0>' + "Run as agent" + '</option><option value=1>' + "Run as user, agent if no user" + '</option><option value=2>' + "Must run as user" + '</option></select>';
x += '<textarea id=d2runcmd style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea>';
setDialogMode(2, "Run Commands", 3, deviceRunCmdsFunctionEx, x);
Q('d2runcmd').focus();
//QE('idx_dlgOkButton', true);
}
*/
} else if (op == 107) {
// Intel AMT One Click Recovery (OCR)
/*
Q('d3localmodeform').action = 'oneclickrecovery.ashx';
Q('d3auth').value = authCookie;
Q('d3filter').value = '.iso';
Q('d3attrib').value = currentNode._id;
setDialogMode(3, "Intel&reg; AMT One Click Recovery", 3, deviceActionOneClickRecovery);
d3init();
*/
} else if (op == 302) { // Intel AMT power on
setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function () { meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op) }); }, "Perform Intel&reg; AMT power on?");
} else if (op == 308) { // Intel AMT power off
setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function () { meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op) }); }, "Perform Intel&reg; AMT power off?");
} else if (op == 310) { // Intel AMT reset
setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function () { meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op) }); }, "Perform Intel&reg; AMT reset?");
} else if ((op == 400) || (op == 401)) {
// Flash / vibrate
meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op), time: parseInt(Q('d2devicetime').value) });
} else {
// Power operation
meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op) });
}
}
function showNotes(readonly, noteid) {
if (xxdialogMode) return;
if (noteid == null) { noteid = encodeURIComponentEx('p' + userinfo._id); }
var x = '<textarea id=d2devNotes ro=' + readonly + ' noteid=' + noteid + ' readonly style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea>';
if (noteid.startsWith('node%2F%2F')) { x += '<span style=font-size:10px>' + "Device group notes can be viewed and changed by other device group administrators." + '<span>'; }
setDialogMode(2, "Notes", 3, showNotesEx, x, noteid);
meshserver.send({ action: 'getNotes', id: decodeURIComponent(noteid) });
}
function showNotesEx(buttons, tag) { meshserver.send({ action: 'setNotes', id: decodeURIComponent(tag), notes: encodeURIComponentEx(Q('d2devNotes').value) }); }
function deviceChat(e) {
if (xxdialogMode) return;
setDialogMode(2, "Device Action", 3, function () {
var url = '/messenger?id=meshmessenger/' + encodeURIComponentEx(currentNode._id) + '/' + encodeURIComponentEx(userinfo._id) + '&title=' + currentNode.name;
if (serverinfo.domainsuffix != '') { url = '/' + serverinfo.domainsuffix + url; }
if ((authCookie != null) && (authCookie != '')) { url += '&auth=' + authCookie; }
if (e && (e.shiftKey == true)) {
safeNewWindow(url, 'meshmessenger:' + currentNode._id);
} else {
safeNewWindow(url, 'meshmessenger:' + currentNode._id, 'directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,width=400,height=560');
}
meshserver.send({ action: 'meshmessenger', nodeid: decodeURIComponent(currentNode._id) });
}, "Start chat session?");
}
// Look to see if we need to update the device timeline
function updateDeviceTimeline() {
if ((meshserver.State != 2) || (powerTimelineNode == null) || (powerTimelineUpdate == null) || (currentNode == null) || (currentNode.mtype == 3)) return;
if ((powerTimelineNode == powerTimelineReq) && (currentNode._id == powerTimelineNode) && (powerTimelineUpdate < Date.now())) { powerTimelineUpdate = null; meshserver.send({ action: 'powertimeline', nodeid: currentNode._id }); }
}
// Draw device power bars. The bars are 766px wide.
function drawDeviceTimeline() {
if (currentNode.mtype == 3) { QH('p10html2', '<br />'); return; }
var timeline = null, now = Date.now();
if (currentNode._id == powerTimelineNode) { timeline = powerTimeline; }
// Calculate when the timeline starts
var d = new Date();
d.setHours(0, 0, 0, 0);
d = new Date(d.getTime() - (1000 * 60 * 60 * 24 * 6));
var timelineStart = d.getTime();
// De-compact the timeline
var timeline2 = [];
if (timeline != null && timeline.length > 1) {
timeline2.push([0, timeline[1], timeline[0]]); // Start, End, Power
var ct = timeline[1];
for (var i = 2; i < timeline.length; i += 2) {
var power = timeline[i], dt = now;
if (timeline.length > (i + 1)) { dt = timeline[i + 1]; }
timeline2.push([ct, ct + dt, power]); // Start, End, Power
ct = ct + dt;
}
}
// Draw the timeline
var x = '', count = 1, date = new Date();
var totalWidth = Q('masthead').offsetWidth - (90 + 9 + 9 + 14); // Compute the total width of the power bar
date.setHours(0, 0, 0, 0);
for (var i = 0; i < 7; i++) {
var datavalue = '', start = date.getTime(), end = start + (1000 * 60 * 60 * 24);
for (var j in timeline2) {
var block = timeline2[j];
if (isTimeBlockInside(start, end, block[0], block[1]) == true) {
var ts = Math.max(start, block[0]);
var te = Math.min(Math.min(end, block[1]), now);
var width = Math.round(((te - ts) * totalWidth) / 86400000);
if (width > 0) { datavalue += '<div style=display:table-cell;width:' + width + 'px;background-color:' + powerColor(block[2]) + ';height:16px></div>'; }
}
}
x += '<tr style=' + (((count % 2) == 0) ? 'background-color:#DDD' : '') + '><td><div>&nbsp;' + printDate(date) + '<div></div></div></td><td><div>' + datavalue + '</div></td></tr>';
++count;
date = new Date(date.getTime() - (1000 * 60 * 60 * 24)); // Substract one day
}
QH('p10html2', '<table style="color:black;background-color:#EEE;border-color:#AAA;border-width:1px;border-style:solid;border-collapse:collapse;width:calc(100% - 18px);margin:9px" border=0 cellpadding=2 cellspacing=0><tbody><tr style=background-color:#AAAAAA;font-weight:bold><th scope=col style=text-align:center;width:90px>' + "Day" + '</th><th scope=col style=text-align:center>' + "Power State" + '</th></tr>' + x + '</tbody></table>');
}
// Return a color for the given power state
function powerColor(x) { if (x < powerColorTable.length) { return powerColorTable[x]; } return 'yellow'; }
// Return true if the time block is visible within the start/end period
function isTimeBlockInside(start, end, blockStart, blockEnd) {
if ((blockStart < start) && (blockEnd > end)) return true; // Block is wider than timespan
if ((blockStart > start) && (blockStart < end)) return true;
if ((blockEnd > start) && (blockEnd < end)) return true;
return false;
}
function addDeviceAttribute(name, value) {
return '<tr><td style=width:100px;color:gray>' + name + '</td><td style=overflow:hidden>' + value + '</td></tr>';
}
function editDeviceAmtSettings(nodeid, func) {
if (xxdialogMode) return;
var x = '', node = getNodeFromId(nodeid), buttons = 3, meshrights = GetNodeRights(node);
if ((meshrights & 4) == 0) return;
x += addHtmlValue("Username", '<input id=dp10username style=width:170px maxlength=32 autocomplete=nope placeholder="admin" onchange=validateDeviceAmtSettings() onkeyup=validateDeviceAmtSettings() />');
x += addHtmlValue("Password", '<input id=dp10password type=password style=width:170px autocomplete=nope maxlength=32 onchange=validateDeviceAmtSettings() onkeyup=validateDeviceAmtSettings() />');
// Only display the TLS setting if the Intel AMT manager is not running on the server. With the manager TLS is auto-detected.
if ((features2 & 1) == 0) { x += addHtmlValue("Security", '<select id=dp10tls style=width:176px><option value=0>' + "No TLS security" + '</option><option value=1>' + "TLS security required" + '</option></select>'); }
if ((node.intelamt.user != null) && (node.intelamt.user != '')) { buttons = 7; }
setDialogMode(2, "Edit Intel&reg; AMT credentials", buttons, editDeviceAmtSettingsEx, x, { node: node, func: func });
if ((node.intelamt.user != null) && (node.intelamt.user != '')) { Q('dp10username').value = node.intelamt.user; } else { Q('dp10username').value = 'admin'; }
if ((features2 & 1) == 0) { Q('dp10tls').value = node.intelamt.tls; }
validateDeviceAmtSettings();
}
function validateDeviceAmtSettings() {
QE('idx_dlgOkButton', passwordcheck(Q('dp10password').value));
}
function editDeviceAmtSettingsEx(button, tag) {
if (button == 2) {
// Delete button pressed, remove credentials
meshserver.send({ action: 'changedevice', nodeid: tag.node._id, intelamt: { user: '', pass: '' } });
} else {
// Change Intel AMT credentials
var amtuser = Q('dp10username').value;
if (amtuser == '') amtuser = 'admin';
var amtpass = Q('dp10password').value;
if (amtpass == '') amtuser = '';
var x = { action: 'changedevice', nodeid: tag.node._id, intelamt: { user: amtuser, pass: amtpass } };
if ((features2 & 1) == 0) { x.intelamt.tls = parseInt(Q('dp10tls').value); }
meshserver.send(x);
if (tag.func) { setTimeout(tag.func, 1000); }
}
}
function p10showDeleteNodeDialog(nodeid) {
if (xxdialogMode) return;
setDialogMode(2, "Delete Node", 3, p10showDeleteNodeDialogEx, format("Delete {0}?", EscapeHtml(currentNode.name)) + '<br /><br /><label><input id=p10check type=checkbox onchange=p10validateDeleteNodeDialog() />' + "Confirm" + '</label>', nodeid);
p10validateDeleteNodeDialog();
}
function p10validateDeleteNodeDialog() {
QE('idx_dlgOkButton', Q('p10check').checked);
}
function p10showDeleteNodeDialogEx(buttons, nodeid) {
meshserver.send({ action: 'removedevices', nodeids: [nodeid] });
}
function p10WebRouter(nodeid, protocol, port, addr) {
var relayid = null;
var node = getNodeFromId(nodeid);
if (node.mtype == 3) { // Setup device relay if needed
var mesh = meshes[node.meshid];
if (mesh && mesh.relayid) { relayid = mesh.relayid; addr = node.host; }
}
var servername = serverinfo.name;
if ((servername.indexOf('.') == -1) || ((features & 2) != 0)) { servername = window.location.hostname; } // If the server name is not set or it's in LAN-only mode, use the URL hostname as server name.
if (webRelayDns != '') { servername = webRelayDns; }
var url = 'https://' + servername + ':' + webRelayPort + '/control-redirect.ashx?n=' + nodeid + '&p=' + port + '&appid=' + protocol + '&c=' + authRelayCookie; // Protocol: 1 = HTTP, 2 = HTTPS
if (addr != null) { url += '&addr=' + addr; }
if (relayid != null) { url += '&relayid=' + relayid; }
safeNewWindow(url, 'WebRelay');
return false;
}
function p10showiconselector() {
if (xxdialogMode) return;
var rights = GetNodeRights(currentNode);
if ((rights & 4) == 0) return;
var x = '<table align=center><td style=text-align:center>';
x += '<div style=display:inline-block class=i1 onclick=p10setIcon(1)></div>';
x += '<div style=display:inline-block class=i2 onclick=p10setIcon(2)></div>';
x += '<div style=display:inline-block class=i3 onclick=p10setIcon(3)></div>';
x += '<div style=display:inline-block class=i4 onclick=p10setIcon(4)></div><br />';
x += '<div style=display:inline-block class=i5 onclick=p10setIcon(5)></div>';
x += '<div style=display:inline-block class=i6 onclick=p10setIcon(6)></div>';
x += '<div style=display:inline-block class=i7 onclick=p10setIcon(7)></div>';
x += '<div style=display:inline-block class=i8 onclick=p10setIcon(8)></div></table>';
setDialogMode(2, "Icon Selection", 0, null, x);
QV('id_dialogclose', true);
}
function p10setIcon(icon) {
setDialogMode(0);
meshserver.send({ action: 'changedevice', nodeid: currentNode._id, icon: icon });
}
function showClearSshDialog() { setDialogMode(2, "Edit Device", 3, showClearSshDialogEx, "Clear SSH credentials?"); }
function showClearSshDialogEx(button, mode) { meshserver.send({ action: 'changedevice', nodeid: currentNode._id, ssh: 0 }); }
function showClearRdpDialog() { setDialogMode(2, "Edit Device", 3, showClearRdpDialogEx, "Clear RDP credentials?"); }
function showClearRdpDialogEx(button, mode) { meshserver.send({ action: 'changedevice', nodeid: currentNode._id, rdp: 0 }); }
var showEditNodeValueDialog_modes = ["Device Name", "Hostname", "Description", "Tags"];
var showEditNodeValueDialog_modes2 = ['name', 'host', 'desc', 'tags'];
var showEditNodeValueDialog_modes3 = ['', '', '', "Group1, Group2, Group3"];
function showEditNodeValueDialog(mode) {
if (xxdialogMode) return;
var x = addHtmlValue(showEditNodeValueDialog_modes[mode], '<input id=dp10devicevalue style=width:170px maxlength=64 placeholder="' + showEditNodeValueDialog_modes3[mode] + '" onchange=p10editdevicevalueValidate(' + mode + ',event) onkeyup=p10editdevicevalueValidate(' + mode + ',event) />');
if (mode == 3) {
// Get a list of all possible device tags
var allTags = [], y = '';
for (var i in nodes) { if (nodes[i].tags) { for (var j in nodes[i].tags) { if (allTags.indexOf(nodes[i].tags[j]) == -1) { allTags.push(nodes[i].tags[j]); } } } }
if (allTags.length > 0) {
allTags.sort();
for (var i in allTags) { y += '<span style=padding:4px;background-color:#BBB;border-radius:3px;cursor:pointer onclick=showEditNodeValueDialogAddTag("' + encodeURIComponentEx(allTags[i]) + '")>' + EscapeHtml(allTags[i]) + '</span> '; }
x += '<div style=margin-top:8px;width:280px;line-height:26px;max-height:160px;overflow-y:auto>' + y + '</div>';
}
}
setDialogMode(2, "Edit Device", 3, showEditNodeValueDialogEx, x, mode);
var v = currentNode[showEditNodeValueDialog_modes2[mode]];
if (v == null) v = '';
if (Array.isArray(v)) { v = v.join(', '); }
Q('dp10devicevalue').value = v;
p10editdevicevalueValidate();
Q('dp10devicevalue').focus();
}
function showEditNodeValueDialogAddTag(t) {
var tt = Q('dp10devicevalue').value.split(','), t2 = [];
for (var i in tt) { t2.push(tt[i].trim()); }
if (t2.indexOf(t) >= 0) return;
Q('dp10devicevalue').value += ((Q('dp10devicevalue').value.length == 0) ? '' : ', ') + decodeURIComponent(t);
setTimeout(function () { Q('dp10devicevalue').selectionStart = Q('dp10devicevalue').selectionEnd = 90000; }, 0);
p10editdevicevalueValidate();
}
function showEditNodeValueDialogEx(button, mode) {
var x = { action: 'changedevice', nodeid: currentNode._id };
x[showEditNodeValueDialog_modes2[mode]] = Q('dp10devicevalue').value;
meshserver.send(x);
}
function p10editdevicevalueValidate(mode, e) {
var x = ((mode > 1) || (Q('dp10devicevalue').value.length > 0));
QE('idx_dlgOkButton', x);
if ((e != null) && (x == true) && (e.keyCode == 13)) { dialogclose(1); }
}
//
// DESKTOP
//
var desktop;
var desktopNode;
var desktopsettings = { encoding: 2, showfocus: false, showmouse: true, showcad: true, quality: 40, scaling: 1024, framerate: 50, autolock: false };
function setupDesktop() {
// Setup the remote desktop
if ((desktopNode != currentNode) && (desktop != null)) { desktop.Stop(); desktopNode = null; desktop = null; }
// If the device desktop is already connected in multi-desktop, use that.
if ((desktopNode != currentNode) || (desktop == null)) {
// Device is not already connected, just setup a blank canvas
//QH('DeskParent', '<canvas id=Desk width=640 height=200 style="width:100%;-ms-touch-action:none;margin-left:0px" oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event)></canvas>');
desktopNode = currentNode;
// Setup the mouse wheel
Q('Desk').addEventListener('DOMMouseScroll', function (e) { return dmousewheel(e); });
Q('Desk').addEventListener('mousewheel', function (e) { return dmousewheel(e); });
}
desktopNode = currentNode;
updateDesktopButtons();
// On some browsers like IE, we can't save screen shots. Hide the scheenshot/capture buttons.
if (!Q('Desk')['toBlob']) { QV('deskSaveBtn', false); }
}
// Show and enable the right buttons
function updateDesktopButtons() {
var mesh = meshes[currentNode.meshid];
var deskState = 0;
if (desktop != null) { deskState = desktop.State; }
var meshrights = GetNodeRights(currentNode);
// Show the right buttons
QV('disconnectbutton1', (deskState != 0));
QE('deskFullScreen', (deskState != 0));
QV('connectbutton1', (deskState == 0) && ((meshrights & 8) || (meshrights & 256)) && (currentNode.agent != null) && (currentNode.agent.caps & 1));
QV('connectbutton1h',
(deskState == 0) &&
(meshrights & 8) &&
(
((currentNode.intelamt != null) &&
(currentNode.intelamt.state == 2) &&
(currentNode.intelamt.ver != null) &&
((currentNode.intelamt.sku == null) ||
((typeof currentNode.intelamt.sku == 'number') &&
((currentNode.intelamt.sku & 8) != 0))))
)
);
// Show the right settings
QV('d7amtkvm', (currentNode.intelamt != null && ((typeof currentNode.intelamt.sku != 'number') || ((currentNode.intelamt.sku & 16) == 0)) && ((currentNode.intelamt.ver != null) || (currentNode.agent == null))) && ((deskState == 0) || (desktop.contype == 2)));
QV('d7meshkvm', ((currentNode.agent != null) && (currentNode.agent.caps & 1) && ((deskState == false) || (desktop.contype == 1))));
// Enable buttons
var online = ((currentNode.conn & 1) != 0); // If Agent (1) connected, enable remote desktop
QE('connectbutton1', online);
var hwonline = ((currentNode.conn & 6) != 0); // If CIRA (2) or AMT (4) connected, enable hardware terminal
QE('connectbutton1h', hwonline);
//QE('deskSaveBtn', deskState == 3);
//QV('DeskCAD', meshrights & 8);
//QE('DeskCAD', deskState == 3);
//QV('DeskWD', (currentNode.agent) && (currentNode.agent.id < 5));
//QE('DeskWD', deskState == 3);
//QV('deskkeys', (currentNode.agent) && (currentNode.agent.id < 5));
//QE('deskkeys', deskState == 3);
//QE('DeskToolsButton', online);
QV('DeskToastButton', ((meshrights & 16384) != 0) && (currentNode.agent) && (currentNode.agent.id < 5) && (meshrights & 8));
//QE('DeskToastButton', online);
QV('deskActionsBtn', meshrights & 8);
Q('DeskControl').checked = ((meshrights & 8) != 0);
if (online == false) QV('DeskTools', false);
}
// Used to translate incoming agent console messages
var agentConsoleMessages = ['', "Waiting for user to grant access...", "Denied", "Failed to start remote terminal session, {0} ({1})", "Timeout", "Received invalid network data"];
function formatAgentConsoleMessage(msg, msgid, msgargs) {
var r;
if (msgargs == null) { msgargs = []; }
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 ...
if (msgid && (msgid < agentConsoleMessages.length)) { r = EscapeHtml(format(agentConsoleMessages[msgid], (msgargs[0]), (msgargs[1]), (msgargs[2]))); } else { r = EscapeHtml(msg); }
return r.split('\n').join('<br />') + '<br /><br />';
}
function connectDesktop(e, contype, tsid, consent) {
setSessionActivity();
QV('p11DeskSessionSelector', false);
p11clearConsoleMsg();
if (desktop == null) {
desktopNode = currentNode;
if (contype == 2) {
// Setup the Intel AMT remote desktop
if ((desktopNode.intelamt.user == null) || (desktopNode.intelamt.user == '')) { editDeviceAmtSettings(desktopNode._id, connectDesktop); return; }
desktop = CreateAmtRedirect(CreateAmtRemoteDesktop('Desk'), authCookie);
desktop.debugmode = debugmode;
desktop.onStateChanged = onDesktopStateChange;
desktop.m.bpp = (desktopsettings.encoding == 1 || desktopsettings.encoding == 3) ? 1 : 2;
desktop.m.useZRLE = (desktopsettings.encoding < 3);
desktop.m.showmouse = true;
desktop.m.onScreenSizeChange = function (o, x, y) { if (fullscreen) { QS('deskarea3').width = (x * fullscreenzoom) + 'px'; QS('deskarea3').height = (y * fullscreenzoom) + 'px'; } deskAdjust(); }
// Use TLS if TLS is set
if (desktopNode.conn==4 && desktopNode.intelamt!=null && desktopNode.intelamt.tls==1) {
desktop.Start(desktopNode._id, 16995, '*', '*', 1);
} else {
desktop.Start(desktopNode._id, 16994, '*', '*', 0);
}
desktop.contype = 2;
} else if ((contype == null) || (contype == 1) || ((contype == 3) && (currentNode.agent.id > 4))) {
// Setup the Mesh Agent remote desktop
desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
desktop.debugmode = debugmode;
desktop.m.debugmode = debugmode;
desktop.attemptWebRTC = attemptWebRTC;
desktop.options = {};
if (tsid != null) { desktop.options.tsid = tsid; }
if (consent != null) { desktop.options.consent = consent; }
if (desktopsettings.autolock == true) { desktop.options.autolock = true; }
desktop.onStateChanged = onDesktopStateChange;
if ((features2 & 0x2000) != 0) desktop.m.stopInput = true;
desktop.onConsoleMessageChange = function () {
if (desktop.consoleMessage) {
Q('p11DeskConsoleMsg').innerHTML += formatAgentConsoleMessage(desktop.consoleMessage, desktop.consoleMessageId, desktop.consoleMessageArgs);
QV('p11DeskConsoleMsg', true);
if (p11DeskConsoleMsgTimer != null) { clearTimeout(p11DeskConsoleMsgTimer); }
if (desktop.consoleMessageTimeout) { p11DeskConsoleMsgTimer = setTimeout(p11clearConsoleMsg, desktop.consoleMessageTimeout * 1000); }
} else {
p11clearConsoleMsg();
}
}
desktop.m.ImageType = webpSupport ? 4 : 1; // Send 4 if WebP is supported, otherwise send 1 for JPEG.
desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best.
desktop.m.ScalingLevel = desktopsettings.scaling;
desktop.m.FrameRateTimer = desktopsettings.framerate;
desktop.m.onDisplayinfo = deskDisplayInfo;
desktop.m.onScreenSizeChange = function (o, x, y) { if (fullscreen) { QS('deskarea3').width = (x * fullscreenzoom) + 'px'; QS('deskarea3').height = (y * fullscreenzoom) + 'px'; } deskAdjust(); }
desktop.Start(desktopNode._id);
desktop.contype = 1;
} else if (contype == 3) {
// Ask for user sessions
meshserver.send({ action: 'msg', type: 'userSessions', nodeid: currentNode._id, tag: consent });
}
} else {
// Disconnect and clean up the remote desktop
desktop.Stop();
desktopNode = desktop = null;
}
}
function p11clearConsoleMsg() { QH('p11DeskConsoleMsg', ''); QV('p11DeskConsoleMsg', false); if (p11DeskConsoleMsgTimer) { clearTimeout(p11DeskConsoleMsgTimer); p11DeskConsoleMsgTimer = null; } }
function p12clearConsoleMsg() { QH('p12TermConsoleMsg', ''); QV('p12TermConsoleMsg', false); if (p12TermConsoleMsgTimer) { clearTimeout(p12TermConsoleMsgTimer); p12TermConsoleMsgTimer = null; } }
function p13clearConsoleMsg() { QH('p13FilesConsoleMsg', ''); QV('p13FilesConsoleMsg', false); if (p13FilesConsoleMsgTimer) { clearTimeout(p13FilesConsoleMsgTimer); p13FilesConsoleMsgTimer = null; } }
function p12setConsoleMsg(msg, timeout) {
if (msg) {
Q('p12TermConsoleMsg').innerHTML += msg;
QV('p12TermConsoleMsg', true);
if (p12TermConsoleMsgTimer != null) { clearTimeout(p12TermConsoleMsgTimer); }
if (timeout) { p12TermConsoleMsgTimer = setTimeout(p12clearConsoleMsg, timeout); }
} else {
p12clearConsoleMsg();
}
}
function p13setConsoleMsg(msg, timeout) {
if (msg) {
Q('p13FilesConsoleMsg').innerHTML += msg;
QV('p13FilesConsoleMsg', true);
if (p13FilesConsoleMsgTimer != null) { clearTimeout(p13FilesConsoleMsgTimer); }
if (timeout) { p13FilesConsoleMsgTimer = setTimeout(p13clearConsoleMsg, timeout); }
} else {
p13clearConsoleMsg();
}
}
function onDesktopStateChange(xdesktop, state) {
var xstate = state;
if ((xstate == 3) && (xdesktop.contype == 2)) { xstate++; }
var str = StatusStrs[xstate];
if ((desktop != null) && (desktop.webRtcActive == true)) { str += ", WebRTC"; }
//if (desktop.m.stopInput == true) { str += ', Loopback'; }
QH('deskstatus', str);
switch (state) {
case 0:
// Disconnect and clean up the remote desktop
desktop.Stop();
desktopNode = desktop = null;
QV('DeskScreens', false);
if (fullscreen == true) { deskToggleFull(); }
break;
case 2:
break;
default:
//console.log('Unknown onDesktopStateChange state', state);
break;
}
updateDesktopButtons();
deskAdjust();
setTimeout(deskAdjust, 50);
}
function showDesktopSettings() {
if (xxdialogMode) return;
applyDesktopSettings();
updateDesktopButtons();
setDialogMode(7, "Remote Desktop Settings", 3, showDesktopSettingsChanged);
}
function showDesktopSettingsChanged() {
desktopsettings.encoding = d7desktopmode.value;
desktopsettings.quality = d7bitmapquality.value;
desktopsettings.scaling = d7bitmapscaling.value;
desktopsettings.framerate = d7framelimiter.value;
desktopsettings.autolock = d7deskAutoLock.checked;
localStorage.setItem('desktopsettings', JSON.stringify(desktopsettings));
applyDesktopSettings();
if (desktop) {
if (desktop.contype == 1) {
if (desktop.State != 0) { desktop.m.SendCompressionLevel(webpSupport ? 4 : 1, desktopsettings.quality, desktopsettings.scaling, desktopsettings.framerate); }
desktop.sendCtrlMsg('{"ctrlChannel":"102938","type":"autolock","value":' + desktopsettings.autolock + '}');
}
if (desktop.contype == 2) {
if (desktop.State != 0) { desktop.Stop(); setTimeout(function () { connectDesktop(null, 2); }, 50); }
}
}
}
function applyDesktopSettings() {
var r = '', ops = (features & 512) ? [100, 90, 70, 50, 40, 30, 20, 10, 5, 1] : [50, 40, 30, 20, 10, 5, 1];
for (var i in ops) { r += '<option value=' + ops[i] + '>' + ops[i] + '%</option>'; }
QH('d7bitmapquality', r);
d7desktopmode.value = desktopsettings.encoding;
d7bitmapquality.value = 40; // Default value
if (ops.indexOf(parseInt(desktopsettings.quality)) >= 0) { d7bitmapquality.value = desktopsettings.quality; }
d7bitmapscaling.value = desktopsettings.scaling;
if (desktopsettings.framerate) { d7framelimiter.value = desktopsettings.framerate; }
if (desktopsettings.autolock != null) { d7deskAutoLock.checked = desktopsettings.autolock; }
}
var keyboardShown = false;
var keyboardShownTimer = null;
var fullScreenMode = false;
function toggleKeyboard() {
if (xxdialogMode) return;
if (keyboardShownTimer != null) { clearTimeout(keyboardShownTimer); }
if (keyboardShown) { Q('softKeyboard').blur(); keyboardShown = false; } else { Q('softKeyboard').focus(); keyboardShown = true; }
QV('deskkeybutton2a', fullscreen && !keyboardShown);
QV('deskkeybutton2b', fullscreen && keyboardShown);
}
function keyboardFocusChange() {
keyboardShownTimer = setTimeout(function () {
keyboardShownTimer = null;
keyboardShown = (Q('softKeyboard') == document.activeElement);
QV('deskkeybutton2a', fullscreen && !keyboardShown);
QV('deskkeybutton2b', fullscreen && keyboardShown);
}, 10);
}
function exitButton() {
if (xxdialogMode) return;
QV('deskButtonMenu', false);
QV('termButtonMenu', false);
deskToggleFull();
}
function deskMenuButton(x) {
toggleMenu(true);
deskSendKeys(x);
}
//
// Desktop Shortcut Keys
//
function updateDeskShortcutKeys() {
var x = '<div class="menuButton" onclick="deskMenuButton(-1)">' + "Customize" + '</div>';
for (var i in deskKeyboardShortcuts) { x += '<div class="menuButton" onclick="deskMenuButton(' + deskKeyboardShortcuts[i] + ')">' + keyShortcutTotext(deskKeyboardShortcuts[i]) + '</div>'; }
QH('deskButtonMenu', x);
}
var keyStrings = { 8: "BackSpace", 9: "Tab", 13: "Enter", 27: "Escape", 44 : "Print Screen", 45: "Insert", 46: "Del", 36: "Home", 35: "End", 33: "Page Up", 34: "Page Down", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 0: "None" }
function keyShortcutTotext(n) {
var x = [];
if (n & 0x010000) { x.push("Shift"); }
if (n & 0x020000) { x.push("Alt"); }
if (n & 0x080000) { x.push("Ctrl"); }
if (n & 0x100000) { x.push("Win"); }
n = (n & 0xFFFF);
if ((n >= 112) && (n <= 123)) { x.push('F' + (n - 111)); } // Fx keys
else if ((n != 0) && (keyStrings[n])) { x.push(keyStrings[n]); }
else { if (n != 0) { x.push(String.fromCharCode(n)); } }
return x.join(' + ');
}
// Customize keyboard shortcuts
function deskCustomizeKeys() {
if (xxdialogMode) return;
var x = '<div id=d2shortcuts style="width:100%;height:180px;padding:4px;overflow-y:auto;border:1px solid gray"></div><div style=width:100%;padding:5px>';
x += '<label><input id=d1kshift type=checkbox /> ' + "Shift" + '</label><label> <input id=d1kalt type=checkbox /> ' + "Alt" + '</label><label> <input id=d1kctrl type=checkbox /> ' + "Ctrl" + '</label> <input id=d1kwin type=checkbox /> ' + "Win" + '</label>';
x += ' <select id=d2keySelect>';
for (var i in keyStrings) { x += '<option value=' + i + '>' + keyStrings[i] + '</option>'; }
for (var i = 1; i <= 12; i++) { x += '<option value=' + (i + 111) + '>F' + i + '</option>'; }
for (var i = 0; i < 10; i++) { x += '<option value=' + (i + 48) + '>' + i + '</option>'; }
for (var i = 0; i < 26; i++) { x += '<option value=' + (i + 65) + '>' + String.fromCharCode(i + 65) + '</option>'; }
x += '</select> <input type=button value=' + "Add" + ' onclick=addDeskCustomizeKey() /></div>';
QH('p10dialog2', x);
xxdialogMode = 2;
QV('p10dialog', true);
deskUpdateShortcutList();
}
function deskCustomizeKeysEx() {
QV('p10dialog', false);
xxdialogMode = 0;
putstore('deskKeyShortcuts', deskKeyboardShortcuts.join(','));
updateDeskShortcutKeys();
}
function deskUpdateShortcutList() {
var x = '';
for (var i in deskKeyboardShortcuts) {
var kt = keyShortcutTotext(deskKeyboardShortcuts[i]), orderButtons = '';
if (i != (deskKeyboardShortcuts.length - 1)) { orderButtons += '<img width=8 height=8 style=float:right;cursor:pointer;padding:3px src="images/c2.png" onclick=deskCustomizeKeyDown(' + deskKeyboardShortcuts[i] + ')>'; }
if (i != 0) { orderButtons += '<img width=8 height=8 style=float:right;cursor:pointer;padding:3px src="images/c3.png" onclick=deskCustomizeKeyUp(' + deskKeyboardShortcuts[i] + ')>'; }
x += '<div style="width:100%;background-color:#AAA;border-radius:4px;margin-bottom:4px;padding:4px;text-align:left;box-sizing:border-box" value=' + deskKeyboardShortcuts[i] + '>' + kt + '<img width=10 height=10 style=float:right;cursor:pointer;padding:2px;margin-left:8px src="images/trash.png" onclick=removeDeskCustomizeKey(' + deskKeyboardShortcuts[i] + ')>' + orderButtons + '</div>';
}
if (x == '') { x = '<i>' + "No keyboard shortcuts defined" + '</i>'; }
QH('d2shortcuts', x);
}
function deskCustomizeKeyDown(k) {
var i = deskKeyboardShortcuts.indexOf(k), x = deskKeyboardShortcuts[i + 1];
deskKeyboardShortcuts[i + 1] = deskKeyboardShortcuts[i];
deskKeyboardShortcuts[i] = x;
deskUpdateShortcutList();
}
function deskCustomizeKeyUp(k) {
var i = deskKeyboardShortcuts.indexOf(k), x = deskKeyboardShortcuts[i];
deskKeyboardShortcuts[i] = deskKeyboardShortcuts[i - 1];
deskKeyboardShortcuts[i - 1] = x;
deskUpdateShortcutList();
}
function removeDeskCustomizeKey(k) {
var na = [];
for (var i in deskKeyboardShortcuts) { if (deskKeyboardShortcuts[i] != k) { na.push(deskKeyboardShortcuts[i]); } }
deskKeyboardShortcuts = na;
deskUpdateShortcutList();
}
function addDeskCustomizeKey() {
var k = parseInt(Q('d2keySelect').value);
if (Q('d1kshift').checked) { k |= 0x010000; }
if (Q('d1kalt').checked) { k |= 0x020000; }
if (Q('d1kctrl').checked) { k |= 0x080000; }
if (Q('d1kwin').checked) { k |= 0x100000; }
if ((k > 0) && (deskKeyboardShortcuts.indexOf(k) == -1)) { deskKeyboardShortcuts.push(k); deskUpdateShortcutList(); }
}
// Remote desktop special key combos for Windows
function deskSendKeys(ks) {
if (xxdialogMode || desktop == null || desktop.State != 3) return;
// Construct the key command
if (ks == -1) { deskCustomizeKeys(); return; } // Customize
if (ks == 0x0A002E) { desktop.m.sendcad(); return; } // CTRL-ALT-DEL
//if ((desktop.contype == 1) && (ks == 0x10004C)) { desktop.sendCtrlMsg('{"action":"lock"}'); return; } // Lock desktop, WIN + L
var flags = (ks & 0xFF0000) >> 16, key = (ks & 0xFFFF), keyArray = [], keyArray2 = [];
var amtTranslate = {
8: 0xff08, // BackSpace
9: 0xff09, // Tab
13: 0xff0d, // Return or Enter
27: 0xff1b, // Escape
45: 0xff63, // Insert
46: 0xffff, // Delete
36: 0xff50, // Home
35: 0xff57, // End
33: 0xff55, // Page Up
34: 0xff56, // Page Down
37: 0xff51, // Left arrow
38: 0xff52, // Up arrow
39: 0xff53, // Right arrow
40: 0xff54, // Down arrow
112: 0xffbe, // F1
113: 0xffbf, // F2
114: 0xffc0, // F3
115: 0xffc1, // F4
116: 0xffc2, // F5
117: 0xffc3, // F6
118: 0xffc4, // F7
119: 0xffc5, // F8
120: 0xffc6, // F9
121: 0xffc7, // F10
122: 0xffc8, // F11
123: 0xffc9 // F12
}
// 0x010000 = Shift
// 0x020000 = Left-Alt
// 0x080000 = Ctrl
// 0x100000 = Window
if (desktop.contype == 2) {
// Intel AMT
if (flags & 1) { keyArray.push([0xffe1, 1]); keyArray2.push([0xffe1, 0]); } // Shift
if (flags & 2) { keyArray.push([0xffe9, 1]); keyArray2.push([0xffe9, 0]); } // Left-alt
if (flags & 8) { keyArray.push([0xffe3, 1]); keyArray2.push([0xffe3, 0]); } // Ctrl
if (flags & 16) { keyArray.push([0xffe7, 1]); keyArray2.push([0xffe7, 0]); } // Windows key
if (amtTranslate[key]) { key = amtTranslate[key]; }
if ((key >= 65) && (key <= 90)) { key += 32; }
if (key != 0) { keyArray.push([key, 1]); keyArray2.push([key, 0]); }
keyArray2.reverse();
for (var i = 0; i < keyArray2.length; i++) { keyArray.push(keyArray2[i]); }
desktop.m.sendkey(keyArray);
} else {
// Agent desktop
if (flags & 1) { keyArray.push([desktop.m.KeyAction.DOWN, 16]); keyArray2.push([desktop.m.KeyAction.UP, 16]); } // Shift
if (flags & 2) { keyArray.push([desktop.m.KeyAction.EXDOWN, 18]); keyArray2.push([desktop.m.KeyAction.EXUP, 18]); } // Left-alt
if (flags & 8) { keyArray.push([desktop.m.KeyAction.EXDOWN, 17]); keyArray2.push([desktop.m.KeyAction.EXUP, 17]); } // Ctrl
if (flags & 16) { keyArray.push([desktop.m.KeyAction.EXDOWN, 0x5B]); keyArray2.push([desktop.m.KeyAction.EXUP, 0x5B]); } // Windows key
if (key != 0) { keyArray.push([desktop.m.KeyAction.DOWN, key]); keyArray2.push([desktop.m.KeyAction.UP, key]); }
keyArray2.reverse();
for (var i = 0; i < keyArray2.length; i++) { keyArray.push(keyArray2[i]); }
desktop.m.SendKeyMsgKC(keyArray);
}
}
function toggleMenu(x) {
if (xxdialogMode) return;
QV('deskButtonMenu', fullscreen && !x && (currentDevicePanel == 1));
QV('termButtonMenu', fullscreen && !x && (currentDevicePanel == 5));
QV('deskkeybutton3a', fullscreen && x);
QV('deskkeybutton3b', fullscreen && !x);
}
function deskChangeMouseButton(x) {
if (xxdialogMode) return;
if (desktop == null) return;
desktop.m.SwapMouse = !desktop.m.SwapMouse;
QV('deskkeybutton4a', fullscreen && (!desktop.m.SwapMouse));
QV('deskkeybutton4b', fullscreen && (desktop.m.SwapMouse));
}
function deskChangeFullscreenZoom() {
if (xxdialogMode) return;
if (currentDevicePanel == 1) {
if (desktop == null) return;
if (fullscreenzoom == 1) { fullscreenzoom = 0.5; } else { fullscreenzoom = 1; }
QV('deskkeybutton5a', fullscreen && (fullscreenzoom == 1));
QV('deskkeybutton5b', fullscreen && (fullscreenzoom != 1));
QS('deskarea3').width = (desktop.m.ScreenWidth * fullscreenzoom) + 'px';
QS('deskarea3').height = (desktop.m.ScreenHeight * fullscreenzoom) + 'px';
deskAdjust();
}
if (currentDevicePanel == 5) {
if (terminal == null) return;
xterm.setOption('fontSize', (xterm.getOption('fontSize') == 15) ? 10 : 15)
}
}
var fullscreen = false;
var fullscreenzoom = 1;
function deskToggleFull() {
fullscreen = !fullscreen;
QV('mastheadx', !fullscreen);
QV('masthead', !fullscreen);
QV('topbar', !fullscreen);
QV('p11deviceNameHeader', !fullscreen);
QV('footer', !fullscreen);
QV('column_l_bottomgap', !fullscreen);
QV('idx_deskFullBtn2', fullscreen);
QV('deskFullBtn', !fullscreen);
QV('p10deskTopTable', !fullscreen);
QV('deskarea1', !fullscreen);
QV('deskarea4', !fullscreen);
QV('termarea1', !fullscreen);
QV('termarea4', !fullscreen);
var rights = GetNodeRights(currentNode);
var inputAllowed = ((features2 & 0x2000) == 0) && (currentNode.agent.id != 14) && ((rights == 0xFFFFFFFF) || (((rights & 8) != 0) && ((rights & 256) == 0) && ((rights & 4096) == 0)));
// Show full screen buttons if needed
QV('deskkeybutton1', fullscreen);
if (currentDevicePanel == 1) { // Desktop panel is being shown (1 = Desktop, 5 = Terminal)
// Move shortcut key to desktop position
QS('deskkeybutton2a').top = QS('deskkeybutton2b').top = '210px';
// Move the zoom button to normal or top position
QS('deskkeybutton5a').top = QS('deskkeybutton5b').top = (inputAllowed) ? '160px' : '60px'; // Zoom
QV('deskkeybutton2a', fullscreen && inputAllowed);
QV('deskkeybutton2b', false);
QV('deskkeybutton3a', fullscreen && inputAllowed);
QV('deskkeybutton3b', false);
QV('deskkeybutton4a', fullscreen && inputAllowed && (!desktop.m.SwapMouse));
QV('deskkeybutton4b', fullscreen && inputAllowed && (desktop.m.SwapMouse));
QV('deskkeybutton5a', fullscreen && (fullscreenzoom == 1));
QV('deskkeybutton5b', fullscreen && (fullscreenzoom != 1));
}
if (currentDevicePanel == 5) {
// Move right buttons to terminal position
//QS('deskkeybutton3a').top = QS('deskkeybutton3b').top = '60px'; // Shortcuts
//QS('deskkeybutton5a').top = QS('deskkeybutton5b').top = '110px'; // Zoom
QS('deskkeybutton2a').top = QS('deskkeybutton2b').top = '110px'; // Keyboard
QV('deskkeybutton2a', fullscreen);
QV('deskkeybutton2b', false);
QV('deskkeybutton3a', fullscreen);
QV('deskkeybutton3b', false);
QV('deskkeybutton4a', false);
QV('deskkeybutton4b', false);
QV('deskkeybutton5a', false);
QV('deskkeybutton5a', false);
//QV('deskkeybutton5a', xterm.getOption('fontSize') == 15);
//QV('deskkeybutton5b', xterm.getOption('fontSize') != 15);
}
if (fullscreen) {
QS('DeskParent').height = null;
QS('page_content').top = '0px';
QS('page_content').bottom = '0px';
if (currentDevicePanel == 1) {
QS('p10desktop').top = '0px';
QS('p10desktop').overflow = 'scroll';
QS('deskarea3').top = '0px';
QS('deskarea3').width = (desktop.m.ScreenWidth * fullscreenzoom) + 'px';
QS('deskarea3').height = (desktop.m.ScreenHeight * fullscreenzoom) + 'px';
QS('deskarea3')['padding-right'] = '55px';
}
if (currentDevicePanel == 5) {
QS('p10terminal').top = '0px';
QS('p10terminal').overflow = 'scroll';
QS('termarea3').top = '0px';
QS('termarea3').bottom = null;
QS('termarea3').right = null;
QS('termarea3')['padding-right'] = '55px';
QS('termarea3')['height'] = '100%';
}
QS('body')['background-color'] = '#000';
QS('p10')['background-color'] = '#000';
} else {
QS('DeskParent').height = '100%';
QS('page_content').top = '50px';
QS('page_content').bottom = '32px';
if (currentDevicePanel == 1) {
QS('p10desktop').top = '55px';
QS('p10desktop').overflow = 'hidden';
QS('deskarea3').top = '32px';
QS('deskarea3').left = null;
QS('deskarea3').width = '100%';
QS('deskarea3').height = 'calc(100% - 64px)';
QS('deskarea3')['padding-right'] = '';
QS('DeskParent')['margin-top'] = null;
QS('DeskParent')['margin-left'] = null;
}
if (currentDevicePanel == 5) {
//xterm.setOption('fontSize', 15)
QS('p10terminal').top = '55px';
QS('p10terminal').overflow = 'hidden';
Q('p10terminal').scrollTop = 0;
Q('p10terminal').scrollLeft = 0;
QS('termarea3').top = '32px';
QS('termarea3').bottom = '32px';
//QS('termarea3').right = '0px';
QS('termarea3')['padding-right'] = null;
QS('termarea3')['height'] = 'calc(100% - 60px)';
}
QS('body')['background-color'] = nightMode ? '#000' : '#FFF';
QS('p10')['background-color'] = null;
}
if (currentDevicePanel == 1) { deskAdjust(); }
}
function deskAdjust() {
if (currentDevicePanel != 1) return; // If not on desktop tab, ignore this.
if (fullscreen) {
QS('Desk')['margin-top'] = null;
QS('Desk')['margin-bottom'] = null;
QS('Desk').width = '100%';
QS('Desk').height = '100%';
var parentH = Q('p10desktop').clientHeight, parentW = Q('p10desktop').clientWidth;
var deskH = Q('deskarea3').clientHeight, deskW = Q('deskarea3').clientWidth - 55;
if (parentH > deskH) { QS('deskarea3').top = ((parentH - deskH) / 2) + 'px'; } else { QS('deskarea3').top = null; }
if (parentW > deskW) { QS('deskarea3').left = ((parentW - deskW) / 2) + 'px'; } else { QS('deskarea3').left = null; }
} else {
var parentH = Q('DeskParent').clientHeight, parentW = Q('DeskParent').clientWidth;
var deskH = Q('Desk').height, deskW = Q('Desk').width;
var webPageFullScreen = false;
// Fixed aspect ratio
if ((parentH / parentW) > (deskH / deskW)) {
var hNew = ((deskH * parentW) / deskW) + 'px';
QS('Desk').height = hNew;
QS('Desk').width = '100%';
} else {
var wNew = ((deskW * parentH) / deskH) + 'px';
QS('Desk').width = wNew;
QS('Desk').height = '100%';
}
QS('DeskParent').overflow = 'hidden';
// Adjust top/bottom margins
var x = (Q('DeskParent').clientHeight - Q('Desk').clientHeight) / 2;
QS('Desk')['margin-top'] = x + 'px';
QS('Desk')['margin-bottom'] = x + 'px';
}
}
function sendSpecialKeys() {
if (xxdialogMode || desktop == null || desktop.State != 3) return;
setDialogMode(3, "Special Keys", 3, deskSendKeys);
}
// Save the desktop image to file
function deskSaveImage() {
setSessionActivity();
if (xxdialogMode || desktop == null || desktop.State != 3) return;
var d = new Date(), n = 'Desktop-' + currentNode.name + '-' + d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2) + '-' + ('0' + d.getHours()).slice(-2) + '-' + ('0' + d.getMinutes()).slice(-2);
Q('Desk')['toBlob'](function (blob) { saveAs(blob, n + '.png'); });
}
function deskSelectScreens() {
if (xxdialogMode || desktop == null || desktop.State != 3) return;
var x = '', info = desktop.m.displays;
for (var i in info) { x += '<option value=' + i + ' ' + ((desktop.m.selectedDisplay == i) ? ' selected' : '') + '>' + info[i] + '</option>'; }
x = addHtmlValue4("Screen", '<select style=width:100% id=deskdisplays>' + x + '</select>');
setDialogMode(2, "Screen Selection", 3, deskSelectScreensEx, x);
}
function deskSelectScreensEx() {
if (desktop == null || desktop.State != 3) return;
desktop.m.SetDisplay(parseInt(Q('deskdisplays').value));
}
function deskDisplayInfo(sender, info, selDisplay, selItem) {
var displayCount = 0;
for (var x in info) { displayCount++; }
QV('DeskScreens', displayCount > 1);
}
function dmousedown(e) { setSessionActivity(); if ((!xxdialogMode && desktop != null)) { if (fullscreen) { e.addx = Q('p10desktop').scrollLeft * (1 / fullscreenzoom); e.addy = Q('p10desktop').scrollTop * (1 / fullscreenzoom); } desktop.m.mousedown(e); } }
function dmouseup(e) { setSessionActivity(); if ((!xxdialogMode && desktop != null)) { if (fullscreen) { e.addx = Q('p10desktop').scrollLeft * (1 / fullscreenzoom); e.addy = Q('p10desktop').scrollTop * (1 / fullscreenzoom); } desktop.m.mouseup(e); } }
function dmousemove(e) { setSessionActivity(); if ((!xxdialogMode && desktop != null)) { if (fullscreen) { e.addx = Q('p10desktop').scrollLeft * (1 / fullscreenzoom); e.addy = Q('p10desktop').scrollTop * (1 / fullscreenzoom); } desktop.m.mousemove(e); } }
function dmousewheel(e) { setSessionActivity(); if ((!xxdialogMode && desktop != null) && desktop.m.mousewheel) { if (fullscreen) { e.addx = Q('p10desktop').scrollLeft * (1 / fullscreenzoom); e.addy = Q('p10desktop').scrollTop * (1 / fullscreenzoom); } desktop.m.mousewheel(e); haltEvent(e); return true; } return false; }
function drotate(x) { if (!xxdialogMode && desktop != null) { desktop.m.setRotation(desktop.m.rotation + x); deskAdjust(); } }
//
// TERMINAL
//
var terminalNode;
function setupTerminal() {
// Setup the terminal
if ((terminalNode != currentNode) && (terminal != null)) { terminal.Stop(); terminal = null; }
terminalNode = currentNode;
updateTerminalButtons();
}
// Show and enable the right buttons
function updateTerminalButtons() {
var mtype = (currentNode.agent == 1) ? 1 : 2;
var termState = ((terminal != null) && (terminal.state != 0));
QE('termFullScreen', (termState != 0));
// If we are looking at a local non-windows device, enable terminal and files capability.
if ((terminalNode.mtype == 3) && (terminalNode.agent != null) && (terminalNode.agent.id > 4) && (features2 & 0x00000200)) { terminalNode.agent.caps = 6; }
// Show the right buttons
QV('disconnectbutton2span', (termState == true));
QV('connectbutton2span', (termState == false) && (terminalNode.agent != null) && (terminalNode.agent.caps & 2) && (terminalNode.mtype != 3));
QV('connectbutton2sspan', (termState == false) && (terminalNode.agent != null) && (terminalNode.agent.caps & 2) && (terminalNode.agent.id != 3) && (terminalNode.agent.id != 4));
// Enable buttons
var online = ((terminalNode.conn & 1) != 0) || (terminalNode.mtype == 3); // If Agent (1) connected, enable Terminal
QE('connectbutton2', online);
QE('connectbutton2s', online);
// Enable action button if mesh type is not "local devices"
QV('termActionsBtn', terminalNode.mtype != 3);
if (((termState == true) && (terminal.contype != 3)) || (terminalNode.agent == null) || (terminalNode.agent.id == 3) || (terminalNode.agent.id == 4)) {
QH('terminalCustomUpperRight', '');
} else {
QH('terminalCustomUpperRight', '<a style=cursor:pointer onclick=cmsshportaction(1,event)>' + format("SSH Port {0}", (terminalNode.sshport ? terminalNode.sshport : 22)) + '</a>');
}
}
function cmsshportaction(action) {
if (xxdialogMode) return;
var x = "SSH remote connection port:" + '<br /><br /><input type=text placeholder="22" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" maxlength=5 id=d10sshport type=text>';
setDialogMode(2, "SSH Connection", 3, function () {
// Save the new SSH port to the server
var sshport = ((Q('d10sshport').value.length > 0) ? parseInt(Q('d10sshport').value) : 22);
meshserver.send({ action: 'changedevice', nodeid: currentNode._id, sshport: sshport });
}, x, currentNode);
Q('d10sshport').focus();
if (currentNode.sshport != null) { Q('d10sshport').value = currentNode.sshport; }
}
// Called when the terminal state changes
function onTerminalStateChange(xterminal, state) {
var xstate = state;
if ((xstate == 3) && (xterminal.contype == 2)) { xstate++; }
var str = StatusStrs[xstate];
if (terminal.webRtcActive == true) { str += ", WebRTC"; }
QH('termstatus', str);
switch (state) {
case 0:
// Disconnected, clear the terminal
xterm.dispose();
xterm = null;
if (terminal != null) { terminal.Stop(); terminal = null; }
break;
case 3:
xterm.focus();
break;
default:
//console.log('Unhandled onTerminalStateChange state', state);
break;
}
updateTerminalButtons();
}
// Handles a tunnel to a remote shell
function CreateRemoteTunnel(onTunnelUpdate, options) {
var obj = { protocol: 1 };
if ((options != null) && (typeof options.protocol == 'number')) { obj.protocol = options.protocol; }
obj.onTunnelUpdate = onTunnelUpdate;
obj.xxStateChange = function (state) { }
obj.ProcessBinaryData = function (data) { obj.onTunnelUpdate(data); }
obj.ProcessData = function (data) { obj.onTunnelUpdate(data); }
obj.terminalEmulation = 1;
obj.fxEmulation = 0;
obj.lineFeed = '\r\n';
return obj;
}
function tunnelUpdate(data) {
if (xterm != null) {
if (xterm.writeUtf8) {
if (typeof data == 'string') { xterm.writeUtf8(data); } else { xterm.writeUtf8(new Uint8Array(data)); }
} else {
if (typeof data == 'string') { xterm.write(data); } else { xterm.write(new Uint8Array(data)); }
}
}
}
//function tunnelUpdate(data) { if (typeof data == 'string') { xterm.writeUtf8(data); } else { xterm.writeUtf8(new Uint8Array(data)); } }
function sshTunnelAuthDialog(j, func) {
var x = '';
if (j.askkeypass) {
x += addHtmlValue("Authentication", '<select id=dp2authmethod style=width:150px onchange=sshAuthUpdate(event)><option value=3 selected>' + "Stored Key" + '</option><option value=1>' + "Username & Password" + '</option><option value=2>' + "Username and Key" + '</option></select>');
} else {
x += addHtmlValue("Authentication", '<select id=dp2authmethod style=width:150px onchange=sshAuthUpdate(event)><option value=1 selected>' + "Username & Password" + '</option><option value=2>' + "Username and Key" + '</option></select>');
}
x += '<div id=d2userauth style=display:none>';
x += addHtmlValue("Username", '<input id=dp2user style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
x += '</div>';
x += '<div id=d2passauth style=display:none>';
x += addHtmlValue("Password", '<input type=password id=dp2pass style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
if ((features2 & 0x00400000) == 0) { x += '<label><input id=dp2keep type=checkbox>' + "Remember credentials" + '</label>'; }
x += '</div><div id=d2keyauth style=display:none>';
x += addHtmlValue("Key File", '<input type=file id=dp2key style=width:150px maxlength=64 autocomplete=off onchange=sshAuthUpdate(event) />' + '<div id=d2badkey style=font-size:x-small>' + "Key file must be in OpenSSH format." + '</div>');
x += addHtmlValue("Key Password", '<input type=password id=dp2keypass style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
if ((features2 & 0x00400000) == 0) {
x += '<label><input id=dp2keep1 type=checkbox onchange=sshAuthUpdate(event)>' + "Remember user & key" + '</label><br/>';
x += '<label><input id=dp2keep2 type=checkbox>' + "Remember password" + '</label>';
}
x += '</div>';
if (j.askkeypass) {
x += '<div id=d2keyauth2 style=display:none>';
x += addHtmlValue("Password", '<input type=password id=dp2keypass2 style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
x += '</div>';
}
setDialogMode(2, "Authentication", 11, func, x, 'ssh');
Q('dp2user').focus();
sshAuthUpdate();
setTimeout(sshAuthUpdate, 50);
}
function sshTunnelUpdate(data) {
if (typeof data == 'string') {
if (data[0] == '{') {
var j = JSON.parse(data);
switch (j.action) {
case 'sshauth': {
sshTunnelAuthDialog(j, sshConnectEx);
/*
var x = '';
x += addHtmlValue("Authentication", '<select id=dp2authmethod style=width:150px onchange=sshAuthUpdate(event)><option value=1 selected>' + "Username & Password" + '</option><option value=2>' + "Username and Key" + '</option></select>')
x += addHtmlValue("Username", '<input id=dp2user style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
x += '<div id=d2passauth>';
x += addHtmlValue("Password", '<input type=password id=dp2pass style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
x += '</div><div id=d2keyauth style=display:none>';
x += addHtmlValue("Key File", '<input type=file id=dp2key style=width:150px maxlength=64 autocomplete=off onchange=sshAuthUpdate(event) />');
x += addHtmlValue("Key Password", '<input type=password id=dp2keypass style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
x += '</div>';
x += '<label><input id=dp2keep type=checkbox>' + "Remember credentials" + '</label>';
x += '<div id=d2keyauth2 style=font-size:x-small><br />' + "Key file must be in OpenSSH format." + '</div>';
setDialogMode(2, "Authentication", 11, sshConnectEx, x, 'ssh');
setTimeout(sshAuthUpdate, 50);
*/
break;
}
case 'sshautoauth': {
terminal.socket.send(JSON.stringify({ action: 'sshautoauth', cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
break;
}
case 'autherror': { p12setConsoleMsg("Authentication Error", 5000); break; }
case 'sessionerror': { p12setConsoleMsg("Session expired", 5000); break; }
case 'sessiontimeout': { p12setConsoleMsg("Session timeout", 5000); break; }
}
} else if (data[0] == '~') { xterm.writeUtf8(data.substring(1)); }
}
}
/*
function sshAuthUpdate(e) {
QV('d2passauth', Q('dp2authmethod').value == 1);
QV('d2keyauth', Q('dp2authmethod').value == 2);
QV('d2keyauth2', Q('dp2authmethod').value == 2);
if (Q('dp2authmethod').value == 1) {
QE('idx_dlgOkButton', (Q('dp2user').value.length > 0) && (Q('dp2pass').value.length > 0));
} else {
QE('idx_dlgOkButton', false);
var ok = (Q('dp2user').value.length > 0) && (Q('dp2key').files != null) && (Q('dp2key').files.length == 1) && (Q('dp2key').files[0].size < 8000);
if (ok == true) {
var reader = new FileReader();
reader.onload = function (e) {
var validkey =
((e.target.result.indexOf('-----BEGIN OPENSSH PRIVATE KEY-----') >= 0) && (e.target.result.indexOf('-----END OPENSSH PRIVATE KEY-----') >= 0)) ||
((e.target.result.indexOf('-----BEGIN RSA PRIVATE KEY-----') >= 0) && (e.target.result.indexOf('-----END RSA PRIVATE KEY-----') >= 0));
QE('idx_dlgOkButton', validkey);
}
reader.readAsText(Q('dp2key').files[0]);
}
}
}
function sshConnectEx(b) {
if (b == 0) {
if (terminal != null) { connectTerminal(); } // Disconnect
} else {
if (Q('dp2authmethod').value == 1) {
terminal.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: Q('dp2keep').checked, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
} else {
var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value, keep = Q('dp2keep').checked;
reader.onload = function (e) { terminal.socket.send(JSON.stringify({ action: 'sshauth', username: username, keypass: keypass, key: e.target.result, keep: keep, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight })); }
reader.readAsText(Q('dp2key').files[0]);
}
}
}
*/
function sshAuthUpdate(e) {
QV('d2userauth', Q('dp2authmethod').value != 3);
QV('d2passauth', Q('dp2authmethod').value == 1);
QV('d2keyauth', Q('dp2authmethod').value == 2);
QV('d2keyauth2', Q('dp2authmethod').value == 3);
if (Q('dp2authmethod').value == 1) {
QE('idx_dlgOkButton', (Q('dp2user').value.length > 0) && (Q('dp2pass').value.length > 0));
} else if (Q('dp2authmethod').value == 3) {
QE('idx_dlgOkButton', Q('dp2keypass2').value.length > 0);
} else {
QE('idx_dlgOkButton', false);
if ((features2 & 0x00400000) == 0) { QE('dp2keep2', Q('dp2keep1').checked); }
var ok = (Q('dp2user').value.length > 0) && (Q('dp2key').files != null) && (Q('dp2key').files.length == 1) && (Q('dp2key').files[0].size < 8000);
if (ok == true) {
var reader = new FileReader();
reader.onload = function (e) {
var validkey =
((e.target.result.indexOf('-----BEGIN OPENSSH PRIVATE KEY-----') >= 0) && (e.target.result.indexOf('-----END OPENSSH PRIVATE KEY-----') >= 0)) ||
((e.target.result.indexOf('-----BEGIN RSA PRIVATE KEY-----') >= 0) && (e.target.result.indexOf('-----END RSA PRIVATE KEY-----') >= 0));
QE('idx_dlgOkButton', validkey);
QS('d2badkey')['color'] = validkey ? '#000' : '#F00';
}
reader.readAsText(Q('dp2key').files[0]);
}
}
// When the enter key is pressed, move to the next field
if (e && (e.keyCode == 13) && (e.target) && (Q('dp2authmethod').value == 1)) {
if (e.target.id == 'dp2user') { Q('dp2pass').focus(); }
if (e.target.id == 'dp2pass') { dialogclose(1); }
}
}
function sshConnectEx(b) {
if (b == 0) {
if (terminal != null) { connectTerminal(); } // Disconnect
} else {
var keep = 0;
if (Q('dp2authmethod').value == 1) {
if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep').checked ? 1 : 0); }
terminal.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: keep, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
} else if (Q('dp2authmethod').value == 3) {
terminal.socket.send(JSON.stringify({ action: 'sshkeyauth', keypass: Q('dp2keypass2').value, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
} else {
if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep1').checked ? 1 : 0); if (keep == 1) { keep += (Q('dp2keep2').checked ? 1 : 0); } } // Keep: 1 = user & key, 2 = User, key and password
var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value;
reader.onload = function (e) { terminal.socket.send(JSON.stringify({ action: 'sshauth', username: username, keypass: keypass, key: e.target.result, keep: keep, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight })); }
reader.readAsText(Q('dp2key').files[0]);
}
}
}
// Send the new terminal size to the agent
function xTermSendResize() {
xtermResizeTimer = null;
if ((xterm != null) && (terminal != null) && (terminal.sendCtrlMsg != null)) {
if (terminal.urlname == 'sshterminalrelay.ashx') {
terminal.socket.send(JSON.stringify({ action: 'resize', cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
} else {
terminal.sendCtrlMsg(JSON.stringify({ ctrlChannel: '102938', type: 'termsize', cols: xterm.cols, rows: xterm.rows }));
}
}
}
function connectTerminal(e, contype, options) {
p12clearConsoleMsg();
if (!terminal) {
// Terminal setup
var termoptions = { protocol: ((options != null) && (typeof options.protocol == 'number')) ? options.protocol : 1 };
if (options && options.requireLogin) { termoptions.requireLogin = true; }
/*
if ([1, 2, 3, 4, 21, 22].indexOf(currentNode.agent.id) == -1) {
if (Q('termSizeList').value == 1) { termoptions.cols = 80; termoptions.rows = 25; termoptions.xterm = true; }
else if (Q('termSizeList').value == 2) { termoptions.cols = 100; termoptions.rows = 30; termoptions.xterm = true; }
else if (Q('termSizeList').value == 3) {
// TODO: Try to improve terminal auto-size.
termoptions.cols = Math.floor((Q('column_l').clientWidth - 60) / 10);
termoptions.rows = Math.floor((Q('column_l').clientHeight - 120) / 20);
termoptions.xterm = true;
}
}
// If shift is pressed
if ((e && (e.shiftKey == true))) {
if (currentNode.agent.id > 4) {
if (termoptions.protocol == 1) { termoptions.protocol = 7; } // Switch to user shell
} else {
if (termoptions.protocol == 1) { termoptions.protocol = 6; } // Switch to Powershell
}
}
// If the server requires a shell type
if ((serverinfo.linuxshell) != null && (currentNode.agent.id > 4)) {
if (serverinfo.linuxshell == 'root') { termoptions.protocol = 1; delete termoptions.requireLogin; }
if (serverinfo.linuxshell == 'user') { termoptions.protocol = 8; delete termoptions.requireLogin; }
if (serverinfo.linuxshell == 'login') { termoptions.protocol = 1; termoptions.requireLogin = true; }
}
*/
// Setup a mesh agent xterm terminal
QV('termarea3xdiv', true);
// Setup the terminal with auto-fit
if (xterm != null) { xterm.dispose(); }
xterm = new Terminal();
xtermfit = new FitAddon.FitAddon();
if (xtermfit) { xterm.loadAddon(xtermfit); }
//xterm.setOption('scrollback', 0);
//xterm.setOption('fontSize', 15);
xterm.open(Q('termarea3xdiv'));
xterm.onData(function (data) { if (terminal.urlname == 'sshterminalrelay.ashx') { terminal.socket.send('~' + data); } else { terminal.sendText(data); } })
if (xtermfit) { xtermfit.fit(); }
xterm.onResize(function (size) {
// Despam resize
if (xtermResizeTimer) clearTimeout(xtermResizeTimer);
xtermResizeTimer = setTimeout(xTermSendResize, 200);
});
// Remove terminal textarea and scrollbar.
document.getElementsByClassName('xterm-helper-textarea')[0].onfocus = () => { xterm.blur(); if (!fullscreen) toggleKeyboard(); };
document.getElementsByClassName('xterm-viewport')[0].style.overflow = 'hidden';
// Setup a terminal tunnel to the agent
terminal = CreateAgentRedirect(meshserver, CreateRemoteTunnel((contype == 3) ? sshTunnelUpdate : tunnelUpdate, termoptions), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
if (contype == 3) { terminal.urlname = 'sshterminalrelay.ashx'; } // If this is a SSH session, change the URL to the SSH application relay.
terminal.debugmode = debugmode;
terminal.m.debugmode = debugmode;
terminal.options = termoptions;
terminal.options = { cols: xterm.cols, rows: xterm.rows };
if (termoptions.requireLogin) { terminal.options.requireLogin = true; }
terminal.Start(terminalNode._id);
terminal.onStateChanged = onTerminalStateChange;
terminal.contype = contype;
terminal.attemptWebRTC = false; // Never do WebRTC on terminal, because of a race condition we can't do it.
terminal.onConsoleMessageChange = function () { p12setConsoleMsg(terminal.consoleMessage ? formatAgentConsoleMessage(terminal.consoleMessage, terminal.consoleMessageId, terminal.consoleMessageArgs) : null, terminal.consoleMessageTimeout); }
} else {
terminal.Stop();
terminal = null;
if (fullscreen) { deskToggleFull(); }
}
Q('connectbutton2').blur(); // Deselect the connect button so the button does not get key presses.
}
//
// Terminal Shortcut Keys
//
function updateTermShortcutKeys() {
var x = '';
for (var i = 64; i <= 95; i++) { x += '<div class="menuButton" style="width:70px" onclick="termMenuButton(' + i + ')">' + "Ctrl + " + String.fromCharCode(i) + '</div>'; }
QH('termButtonMenu', x);
}
function termMenuButton(c) {
toggleMenu(true);
if (terminal.urlname == 'sshterminalrelay.ashx') {
// SSH
terminal.socket.send('~' + String.fromCharCode(c - 64));
} else {
// Agent
terminal.sendText(String.fromCharCode(c - 64));
}
}
//
// FILES
//
var filesNode;
function setupFiles() {
// Setup the files tab
var samenode = (filesNode == currentNode);
filesNode = currentNode;
var online = ((filesNode.conn & 1) != 0) || (filesNode.mtype == 3); // If Agent (1) connected, enable Terminal
QE('p13Connect', online);
QE('p13Connects', online);
QV('p13Connect', (files == null) && (filesNode.mtype == 2));
QV('p13Connects', (files == null) && (filesNode.agent != null) && (filesNode.agent.id != 3) && (filesNode.agent.id != 4));
QV('p13Disconnect', files != null);
if (((samenode == false) || (online == false)) && files) { files.Stop(); files = null; }
p13setActions();
}
function onFilesStateChange(xfiles, state) {
setSessionActivity();
QV('p13Connect', (state == 0) && (filesNode.mtype == 2));
QV('p13Connects', (state == 0) && (filesNode.agent != null) && (filesNode.agent.id != 3) && (filesNode.agent.id != 4));
QV('p13Disconnect', state != 0);
var str = StatusStrs[state];
if (state == 3) {
if (files.contype == 2) { str += ", SFTP"; }
if (files.webRtcActive == true) { str += ", WebRTC"; }
}
Q('p13Status').textContent = str;
switch (state) {
case 0:
// Disconnected, clear the files
QH('p13files', '');
p13filetree = null;
p13filetreelocation = [];
QH('p13currentpath', '');
QE('p13FolderUp', false);
p13setActions();
if (files != null) { files.Stop(); files = null; }
if (uploadFile != null) { p13uploadFileTransferDone(); uploadFile = null; }
break;
case 3:
p13filetreelocation = [];
p13targetpath = '';
if (files) {
var filepaths = [];
try { filepaths = JSON.parse(getstore('_devFilePaths', '[]')); } catch (ex) { }
for (var i = 0; i < filepaths.length; i++) { if (filepaths[i].n == currentNode._id) { p13targetpath = filepaths[i].p; } }
p13filetreelocation = p13targetpath.split('/');
files.sendText({ action: 'ls', reqid: 1, path: p13targetpath });
//if (files.serverIsRecording == true) { QV('filesRecordIcon', true); }
}
break;
default:
//console.log('Unknown onFilesStateChange state', state);
break;
}
}
function CreateRemoteFiles(onFileUpdate) {
var obj = { protocol: 5 };
obj.onFileUpdate = onFileUpdate;
obj.xxStateChange = function (state) { }
obj.ProcessData = function (data) { obj.onFileUpdate(data); }
return obj;
}
// Debug Only
var autoConnectFilesTimer = null;
function autoConnectFiles(e) { if (autoConnectFilesTimer == null) { autoConnectFilesTimer = setInterval(connectFiles, 100); } else { clearInterval(autoConnectFilesTimer); autoConnectFilesTimer = null; } }
function connectFiles(e, contype) {
p13clearConsoleMsg();
if (!files) {
// Setup a mesh agent files
files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
if (contype == 2) { files.urlname = 'sshfilesrelay.ashx'; } // If this is a SSH session, change the URL to the SSH application relay.
files.contype = contype;
files.attemptWebRTC = attemptWebRTC;
files.onStateChanged = onFilesStateChange;
files.onConsoleMessageChange = function () {
if (files.consoleMessage) {
Q('p13FilesConsoleMsg').innerHTML += formatAgentConsoleMessage(files.consoleMessage, files.consoleMessageId, files.consoleMessageArgs);
QV('p13FilesConsoleMsg', true);
if (p13FilesConsoleMsgTimer != null) { clearTimeout(p13FilesConsoleMsgTimer); }
if (files.consoleMessageTimeout) { p13FilesConsoleMsgTimer = setTimeout(p13clearConsoleMsg, files.consoleMessageTimeout * 1000); }
} else {
p13clearConsoleMsg();
}
}
files.Start(filesNode._id);
} else {
//QH('Term', '');
files.Stop();
files = null;
}
p13clipboard = p13clipboardFolder = null;
p13clipboardCut = 0;
p13updateClipview();
}
var p13filetree = null;
var p13targetpath = null;
var p13filetreelocation = [];
function p13gotFiles(data) {
if ((data.length > 0) && (data.charCodeAt(0) != 123)) { p13gotDownloadBinaryData(data); return; } // This is ok because 4 first bytes is a control value.
//console.log('p13gotFiles', data);
try { data = JSON.parse(decode_utf8(data)); } catch (ex) { data = JSON.parse(data); }
if (data.action == 'download') { p13gotDownloadCommand(data); return; }
// Process any SSH actions
switch (data.action) {
case 'sshauth': { sshTunnelAuthDialog(data, p13sshConnectEx); break; }
case 'autherror': { p13setConsoleMsg("Authentication Error", 5000); return; }
case 'connectionerror': { p13setConsoleMsg("Connection Error", 5000); return; }
case 'sessionerror': { p13setConsoleMsg("Session expired", 5000); return; }
case 'sessiontimeout': { p13setConsoleMsg("Session timeout", 5000); return; }
}
// Process file upload commands
if ((data.action != null) && (data.action.startsWith('upload'))) { p13gotUploadData(data); return; }
if (data.path != null) {
if (data.dir == null) {
if (p13targetpath != '') { p13folderup(); }
} else {
data.path = data.path.replace(/\//g, '\\');
if ((p13filetree != null) && (data.path == p13filetree.path)) {
// This is an update to the same folder
var checkedNames = p13getCheckedNames();
p13filetree = data;
p13updateFiles(checkedNames);
} else {
// Make both paths use the same seperator not start with /
var x1 = data.path.replace(/\//g, '\\'), x2 = p13targetpath.replace(/\//g, '\\');
while ((x1.length > 0) && (x1[0] == '\\')) { x1 = x1.substring(1); }
while ((x2.length > 0) && (x2[0] == '\\')) { x2 = x2.substring(1); }
if ((x1 == x2) || ((data.path == '\\') && (p13targetpath == ''))) {
// This is a different folder
p13filetree = data;
p13updateFiles();
}
}
}
}
}
function p13sshConnectEx(b) {
if (b == 0) {
if (files != null) { connectFiles(); } // Disconnect
} else {
var keep = 0;
if (Q('dp2authmethod').value == 1) {
if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep').checked ? 1 : 0); }
files.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: keep }));
} else if (Q('dp2authmethod').value == 3) {
files.socket.send(JSON.stringify({ action: 'sshkeyauth', keypass: Q('dp2keypass2').value }));
} else {
if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep1').checked ? 1 : 0); if (keep == 1) { keep += (Q('dp2keep2').checked ? 1 : 0); } } // Keep: 1 = user & key, 2 = User, key and password
var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value;
reader.onload = function (e) { files.socket.send(JSON.stringify({ action: 'sshauth', username: username, keypass: keypass, key: e.target.result, keep: keep })); }
reader.readAsText(Q('dp2key').files[0]);
}
}
}
function p13getCheckedNames() {
// Save all existing checked boxes
var checkedNames = [], checkboxes = document.getElementsByName('fd');
for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { checkedNames.push(p13filetree.dir[checkboxes[i].value].n) }; }
return checkedNames;
}
function p13updateFiles(checkedNames) {
var html1 = '', html2 = '', displayPath = '<a style=cursor:pointer;color:black onclick=p13folderup(0)>' + "Root" + '</a>', fullPath = 'Root';
// Work on parsing the file path
var x = p13filetree.path.split('\\');
p13filetreelocation = [];
for (var i in x) { if (x[i] != '') { p13filetreelocation.push(x[i]); } } // Remove empty spaces
for (var i in p13filetreelocation) { displayPath += ' / <a style=cursor:pointer;color:black onclick=p13folderup(' + (parseInt(i) + 1) + ')>' + EscapeHtml(p13filetreelocation[i]) + '</a>' } // Setup the path we display
var newlinkpath = p13filetreelocation.join('/');
// Sort the files
var filetreexx = p13sort_files(p13filetree.dir);
// Display all files and folders at this location
for (var i in filetreexx) {
// Figure out the name and shortname
var f = filetreexx[i], name = f.n, shortname;
if (name.length > 40) { shortname = EscapeHtml(name.substring(0, 70)) + "..."; } else { shortname = EscapeHtml(name); }
// Figure out the size
var fsize = '';
if (f.s != null) { fsize = getFileSizeStr(f.s); }
var h = '';
if (f.t < 3) {
var right = '';
h = '<div class=filelist file=999><input file=999 style=float:left name=fd class=fcb type=checkbox onchange=p13setActions() value=\'' + f.nx + '\'>&nbsp;<span style=float:right>' + right + '</span><span><div class=fileIcon' + f.t + '></div><a style=cursor:pointer onclick=p13folderset("' + encodeURIComponent(f.nx) + '")>' + shortname + '</a></span></div>';
} else {
var link = shortname;
if (f.s > 0) { link = '<a rel=\"noreferrer noopener\" target=\"_blank\" style=cursor:pointer onclick=\"p13downloadfile(\'' + encodeURIComponent(newlinkpath + '/' + name) + '\',\'' + encodeURIComponent(name) + '\',' + f.s + ')\">' + shortname + '</a>'; }
h = '<div class=filelist file=3><input file=3 style=float:left name=fd class=fcb type=checkbox onchange=p13setActions() value=\'' + f.nx + '\'>&nbsp;<span style=float:right;padding-right:4px>' + fsize + '</span><span><div class=fileIcon' + f.t + '></div>' + link + '</span></div>';
}
if (f.t < 3) { html1 += h; } else { html2 += h; }
}
// Display the files and path
QH('p13files', html1 + html2);
QH('p13currentpath', displayPath);
QE('p13FolderUp', p13filetreelocation.length != 0);
// Re-check all boxes if needed using names
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; } } }
// Update the actions buttons
p13setActions();
}
function p13folderset(x) {
p13targetpath = joinPaths(p13filetree.path, p13filetree.dir[x].n).split('\\').join('/');
if (files) {
p13storeCurrentPath(p13targetpath);
files.sendText({ action: 'ls', reqid: 1, path: p13targetpath });
}
}
function p13folderup(x) {
if (x == null) { p13filetreelocation.pop(); } else { while (p13filetreelocation.length > x) { p13filetreelocation.pop(); } }
p13targetpath = p13filetreelocation.join('/');
if (files) {
p13storeCurrentPath(p13targetpath);
files.sendText({ action: 'ls', reqid: 1, path: p13targetpath });
}
}
// Store the current path for a given node as browser state.
// This is so, when reconnecting to a device, you go back to the same path.
function p13storeCurrentPath(path) {
var filepaths = [], j = -1;
try { filepaths = JSON.parse(getstore('_devFilePaths', '[]')); } catch (ex) { }
for (var i = 0; i < filepaths.length; i++) { if (filepaths[i].n == currentNode._id) { j = i; } }
if (j >= 0) { filepaths.splice(j, 1); }
filepaths.push({ n: currentNode._id, p: path });
while (filepaths.length > 40) { filepaths.shift(); } // Keep only 40 devices worth of paths.
putstore('_devFilePaths', JSON.stringify(filepaths));
}
var p13sortorder;
function p13sort_filename(a, b) { if (a.ln > b.ln) return (1 * p13sortorder); if (a.ln < b.ln) return (-1 * p13sortorder); return 0; }
function p13sort_timestamp(a, b) { if (a.d > b.d) return (1 * p13sortorder); if (a.d < b.d) return (-1 * p13sortorder); return 0; }
function p13sort_bysize(a, b) { if (a.s == b.s) return p13sort_filename(a, b); return (((a.s - b.s)) * p13sortorder); }
function p13sort_files(files) {
var r = [], sortselection = Q('p13sortdropdown').value;
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]); }
p13sortorder = 1;
if (sortselection > 3) { p13sortorder = -1; sortselection -= 3; }
if (sortselection == 1) { r.sort(p13sort_filename); }
else if (sortselection == 2) { r.sort(p13sort_bysize); }
else if (sortselection == 3) { r.sort(p13sort_timestamp); }
return r;
}
function p13setActions() {
var advancedFeatures = ((currentNode.agent) && (currentNode.agent.id != 14)); // Reduct file feature on some devices.
if (p13filetree == null) {
QE('p13DeleteFileButton', false);
QE('p13NewFolderButton', false);
QE('p13UploadButton', false);
QE('p13RenameFileButton', false);
QE('p13SelectAllButton', false);
Q('p13SelectAllButton').value = "All";
QE('p13RefreshButton', false);
QE('p13CutButton', false);
QE('p13CopyButton', false);
QE('p13PasteButton', false);
} else {
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)
var winAgent = ((currentNode.agent.id > 0) && (currentNode.agent.id < 5)) || (currentNode.agent.id == 14) || (currentNode.agent.id == 34);
QE('p13DeleteFileButton', advancedFeatures && (cc > 0) && ((p13filetreelocation.length > 0) || (winAgent == false)));
QE('p13NewFolderButton', advancedFeatures && ((p13filetreelocation.length > 0) || (winAgent == false)));
QE('p13UploadButton', advancedFeatures && ((p13filetreelocation.length > 0) || (winAgent == false)));
QE('p13RenameFileButton', advancedFeatures && (cc == 1) && ((p13filetreelocation.length > 0) || (winAgent == false)));
QE('p13SelectAllButton', tc > 0);
Q('p13SelectAllButton').value = (cc > 0 ? "None" : "All");
QE('p13RefreshButton', true);
QE('p13CutButton', advancedFeatures && (cc > 0) && (cc == sfc) && (currentNode.mtype != 3) && ((p13filetreelocation.length > 0) || (winAgent == false)));
QE('p13CopyButton', advancedFeatures && (cc > 0) && (cc == sfc) && (currentNode.mtype != 3) && ((p13filetreelocation.length > 0) || (winAgent == false)));
QE('p13PasteButton', advancedFeatures && (currentNode.mtype != 3) && ((p13filetreelocation.length > 0) || (winAgent == false)) && ((p13clipboard != null) && (p13clipboard.length > 0)));
}
var filesState = ((files != null) && (files.state != 0));
if (((filesState == true) && (files.contype != 2)) || (filesNode.agent == null) || (filesNode.agent.id == 3) || (filesNode.agent.id == 4)) {
QH('filesCustomUpperRight', '');
} else {
QH('filesCustomUpperRight', '<a style=cursor:pointer onclick=cmsshportaction(1,event)>' + format("SSH Port {0}", (filesNode.sshport ? filesNode.sshport : 22)) + '</a>');
}
QV('filesActionsBtn', filesNode.mtype != 3);
}
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; }
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; }
function p13getFileCount() { var cc = 0; var checkboxes = document.getElementsByName('fd'); return checkboxes.length; }
function p13selectallfile() { var nv = (p13getFileSelCount() == 0), checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { checkboxes[i].checked = nv; } p13setActions(); }
function p13createfolder() { setDialogMode(2, "New Folder", 3, p13createfolderEx, '<input type=text id=p13renameinput maxlength=64 onkeyup=p13fileNameCheck(event) style=width:100% />'); focusTextBox('p13renameinput'); p13fileNameCheck(); }
function p13createfolderEx() { files.sendText({ action: 'mkdir', reqid: 1, path: p13filetreelocation.join('/') + '/' + Q('p13renameinput').value }); p13folderup(999); }
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)); }
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); }
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(); }
function p13renamefileEx(b, t) { t.newname = Q('p13renameinput').value; files.sendText(t); p13folderup(999); }
function p13fileNameCheck(e) { var x = isFilenameValid(Q('p13renameinput').value); QE('idx_dlgOkButton', x); if ((x == true) && (e != null) && (e.keyCode == 13)) { dialogclose(1); } }
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'); }
function p13uploadFileEx() { p13doUploadFiles(Q('p13uploadinput').files); }
function p13viewfile() {
var checkboxes = document.getElementsByName('fd');
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) {
if (p13filetree.dir[checkboxes[i].value].s <= 204800) {
p13downloadfile(encodeURIComponent(p13filetreelocation.join('/') + '/' + p13filetree.dir[checkboxes[i].value].n), encodeURIComponent(p13filetree.dir[checkboxes[i].value].n), p13filetree.dir[checkboxes[i].value].s, 'viewer');
} else { messagebox("File Editor", "Only files less than 200k can be edited."); }
break;
}
}
}
var p13clipboard = null, p13clipboardFolder = null, p13clipboardCut = 0;
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(); }
function p13pasteFile() {
var x = '';
if ((p13clipboard != null) && (p13clipboard.length > 0)) {
if (p13clipboardCut == 0) {
if (p13clipboard.length > 1) { x = format("Confirm copy of {0} entries's to this location?", p13clipboard.length); } else { x = format("Confirm copy of 1 entrie to this location?"); }
} else {
if (p13clipboard.length > 1) { x = format("Confirm move of {0} entries's to this location?", p13clipboard.length); } else { x = format("Confirm move of 1 entrie to this location?"); }
}
}
setDialogMode(2, "Paste", 3, p13pasteFileEx, x);
}
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(); } }
function p13updateClipview() {
var x = '';
if ((p13clipboard != null) && (p13clipboard.length > 0)) {
if (p13clipboardCut == 0) {
if (p13clipboard.length > 1) {
x = format("Holding {0} entries for copy" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.', p13clipboard.length);
} else {
x = format("Holding 1 entrie for copy" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.');
}
} else {
if (p13clipboard.length > 1) {
x = format("Holding {0} entries for move" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.', p13clipboard.length);
} else {
x = format("Holding 1 entrie for move" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.');
}
}
}
QH('p13bottomstatus', x);
p13setActions();
}
function p13clearClip() { p13clipboard = null; p13clipboardFolder = null; p13clipboardCut = 0; p13updateClipview(); return false; } function updateUploadDialogOk(x) { QE('idx_dlgOkButton', Q(x).value != ''); }
function getFileSelCount(includeDirs) { var cc = 0; var checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && ((includeDirs != false) || (checkboxes[i].attributes.file.value == "3"))) cc++; } return cc; }
function getFileCount() { var cc = 0; var checkboxes = document.getElementsByName('fc'); return checkboxes.length; }
//
// FILES DOWNLOAD
//
var downloadFile; // Global state for file download
// Called by the html page to start a download, arguments are: path, file name and file size.
function p13downloadfile(x, y, z) {
if (xxdialogMode || downloadFile || !files) return;
downloadFile = { path: decodeURIComponent(x), file: decodeURIComponent(y), size: z, tsize: 0, data: '', state: 0, id: Math.random() }
//console.log('p13downloadFileCancel', downloadFile);
files.sendText({ action: 'download', sub: 'start', id: downloadFile.id, path: downloadFile.path });
setDialogMode(2, "Download File", 10, p13downloadFileCancel, '<div>' + downloadFile.file + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=' + z + ' />');
}
// Called by the html page to cancel the download
function p13downloadFileCancel() { setDialogMode(0); files.sendText({ action: 'download', sub: 'cancel', id: downloadFile.id }); downloadFile = null; }
// Called by the transport when download control command is received
function p13gotDownloadCommand(cmd) {
//console.log('p13gotDownloadCommand', cmd);
if ((downloadFile == null) || (cmd.id != downloadFile.id)) return;
if (cmd.sub == 'start') { downloadFile.state = 1; files.sendText({ action: 'download', sub: 'startack', id: downloadFile.id }); }
else if (cmd.sub == 'cancel') { downloadFile = null; setDialogMode(0); }
}
// Called by the transport when binary data is received
function p13gotDownloadBinaryData(data) {
if (!downloadFile || downloadFile.state == 0) return;
if (data.length > 4) {
downloadFile.tsize += (data.length - 4); // Add to the total bytes received
downloadFile.data += data.substring(4); // Append the data
Q('d2progressBar').value = downloadFile.tsize; // Change the progress bar
}
if ((ReadInt(data, 0) & 1) != 0) { // Check end flag
saveAs(data2blob(downloadFile.data), downloadFile.file); downloadFile = null; setDialogMode(0); // Save the file
} else {
files.sendText({ action: 'download', sub: 'ack', id: downloadFile.id }); // Send the ACK
}
}
/*
var downloadFile; // Global state for file download
// Called by the html page to start a download, arguments are: path, file name and file size.
function p13downloadfile(x, y, z) {
if (xxdialogMode) return;
downloadFile = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotDownloadData), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); // Create our websocket file transport
downloadFile.ctrlMsgAllowed = false;
downloadFile.onStateChanged = onFileDownloadStateChange;
downloadFile.xpath = decodeURIComponent(x);
downloadFile.xfile = decodeURIComponent(y);
downloadFile.xsize = z;
downloadFile.xtsize = 0;
downloadFile.xstate = 0;
downloadFile.Start(filesNode._id);
setDialogMode(2, "Download File", 10, p13downloadFileCancel, '<div>' + downloadFile.xfile + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=' + z + ' />');
}
// Called by the html page to cancel the download
function p13downloadFileCancel(button, tag) {
//console.log('p13downloadFileCancel');
downloadFile.Stop();
delete downloadFile;
downloadFile = null;
}
// Called by the file transport to indicate when the transport connection state has changed
function onFileDownloadStateChange(xdownloadFile, state) {
switch (state) {
case 0: // Transport as disconnected. If this is not part of an abort, we need to save the file
setDialogMode(0); // Close any dialog boxes if present
if ((downloadFile != null) && (downloadFile.xstate == 1)) { saveAs(data2blob(downloadFile.xdata), downloadFile.xfile); } // Save the file
break;
case 3: // Transport as connected, send a command to indicate we want to start a file download
downloadFile.send(JSON.stringify({ action: 'download', reqid: 1, path: downloadFile.xpath }));
break;
default:
console.log('Unknown onFileDownloadStateChange state', state);
break;
}
}
// Called by the transport when data is received
function p13gotDownloadData(data) {
if (downloadFile.xstate == 0) { // If state is 0, this is a command confirming if the file will be transfered.
var cmd = JSON.parse(data);
if (cmd.action == 'downloadstart') { // Yes, the file is about to start
downloadFile.xstate = 1; // Switch to state 1, we will start receiving the file data
downloadFile.xdata = ''; // Start with empty data
downloadFile.send('a'); // Send the first ACK
} else if (cmd.action == 'downloaderror') { // Problem opening this file, cancel
p13downloadFileCancel();
}
} else { // We are in the process of receiving the file
downloadFile.xtsize += (data.length); // Add to the total bytes received
downloadFile.xdata += data; // Append the data
Q('d2progressBar').value = downloadFile.xtsize; // Change the progress bar
downloadFile.send('a'); // Send the ACK
}
}
*/
//
// FILES UPLOAD
//
var uploadFile;
function p13doUploadFiles(files) {
if (xxdialogMode) return;
// Check if we are going to overwrite any files
var winAgent = ((currentNode.agent.id > 0) && (currentNode.agent.id < 5)) || (currentNode.agent.id == 14) || (currentNode.agent.id == 34);
var targetFiles = [], overWriteCount = 0;
for (var i in p13filetree.dir) { if (winAgent) { targetFiles.push(p13filetree.dir[i].n.toLowerCase()); } else { targetFiles.push(p13filetree.dir[i].n); } }
for (var i = 0; i < files.length; i++) {
if (winAgent) {
if (targetFiles.indexOf(files[i].name.toLowerCase()) >= 0) { overWriteCount++; }
} else {
if (targetFiles.indexOf(files[i].name) >= 0) { overWriteCount++; }
}
}
if (overWriteCount == 0) {
// If no overwrite, go ahead with upload
p13uploadFileContinue(1, files);
} else {
// Otherwise, prompt for confirmation
setDialogMode(2, "Upload File", 3, p13uploadFileContinue, format((overWriteCount == 1) ? "Upload will overwrite 1 file. Continue?" : "Upload will overwrite {0} files. Continue?", overWriteCount), files);
}
}
function p13uploadFileContinue(b, files) {
uploadFile = {};
uploadFile.xpath = p13filetreelocation.join('/');
uploadFile.xfiles = files;
uploadFile.xfilePtr = -1;
setDialogMode(2, "Upload File", 10, p13uploadFileCancel, '<div id=p13dfileName>' + "Connecting..." + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=0 />');
p13uploadNextFile();
}
// Perform SHA-384 hashing
const byteToHex = [];
for (var n = 0; n <= 0xff; ++n) { var hexOctet = n.toString(16).padStart(2, '0'); byteToHex.push(hexOctet); }
function arrayBufferToHex(arrayBuffer) { return Array.prototype.map.call(new Uint8Array(arrayBuffer), n => byteToHex[n]).join(''); }
function performHash(data, f) { window.crypto.subtle.digest('SHA-384', data).then(function (v) { f(arrayBufferToHex(v)); }, function () { f(null); }); }
function performHashOnFile(file, f) {
// TODO: At some point, try to make this work for files of unlimited size using a digest stream
var reader = new FileReader();
reader.onerror = function (err) { f(null); }
reader.onload = function () { window.crypto.subtle.digest('SHA-384', reader.result).then(function (v) { f(arrayBufferToHex(v)); }, function () { f(null); }); };
reader.readAsArrayBuffer(file);
}
// Push the next file
function p13uploadNextFile() {
uploadFile.xfilePtr++;
if (uploadFile.xfiles.length > uploadFile.xfilePtr) {
uploadFile.xptr = 0;
var file = uploadFile.xfiles[uploadFile.xfilePtr];
QH('p13dfileName', file.name);
Q('d2progressBar').max = file.size;
Q('d2progressBar').value = 0;
if (file.xdata == null) {
uploadFile.xfile = file;
// If the remote file already exists and is smaller then our file, see if we can resume the trasfer
var f = null;
for (var i in p13filetree.dir) { if (p13filetree.dir[i].n == file.name) { f = p13filetree.dir[i]; } }
if ((f != null) && (f.s <= uploadFile.xfile.size)) {
performHashOnFile(uploadFile.xfile, function (hash) { 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.xfile.size } })); });
} else {
files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xfile.size }));
}
} else {
// Data already loaded
uploadFile.xdata = file.xdata;
files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength }));
}
} else {
p13uploadFileTransferDone();
}
}
// Used to cancel the entire transfer.
function p13uploadFileCancel(button, tag) {
if (uploadFile != null) { files.sendText(JSON.stringify({ action: 'uploadcancel', reqid: uploadFile.xfilePtr })); uploadFile = null; }
p13uploadFileTransferDone();
}
// Used to cancel the entire transfer.
function p13uploadFileTransferDone() {
uploadFile = null; // No more files to upload, clean up.
setDialogMode(0); // Close the dialog box
p13folderup(9999); // Refresh the current folder
}
// Receive upload ack from the mesh agent, use this to keep sending more data
function p13gotUploadData(cmd) {
if ((uploadFile == null) || (parseInt(uploadFile.xfilePtr) != parseInt(cmd.reqid))) { return; }
switch (cmd.action) {
case 'uploadstart': { uploadFile.xdataPriming = 8; p13uploadNextPart(false); break; } // Send 8 more blocks of 16k to fill the websocket.
case 'uploadack': { p13uploadNextPart(false); break; }
case 'uploaddone': { if (uploadFile.xfiles.length > uploadFile.xfilePtr + 1) { p13uploadNextFile(); } else { p13uploadFileTransferDone(); } break; }
case 'uploaderror': { p13uploadFileCancel(); break; }
case 'uploadhash': {
var file = uploadFile.xfiles[uploadFile.xfilePtr];
if (file) {
if (cmd.tag.h === cmd.hash) {
if (cmd.tag.skip) {
p13uploadNextFile();
} else {
uploadFile.xptr = cmd.tag.s;
files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xfile.size, append: true }));
}
} else {
files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xfile.size, append: false }));
}
}
break;
}
}
}
// 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.
function p13uploadNextPart(dataPriming) {
if (uploadFile.xdata) {
var data = uploadFile.xdata, start = uploadFile.xptr;
if (start >= data.byteLength) {
files.sendText(JSON.stringify({ action: 'uploaddone', reqid: uploadFile.xfilePtr }));
} else {
var end = uploadFile.xptr + 16384;
if (end > data.byteLength) { if (dataPriming == true) { return; } end = data.byteLength; }
var dataslice = new Uint8Array(data.slice(start, end))
if ((dataslice[0] == 123) || (dataslice[0] == 0)) {
var datapart = new Uint8Array(end - start + 1);
datapart.set(dataslice, 1); // Add a zero char at the start of the send, this will indicate that it's not a JSON command.
files.send(datapart);
} else {
files.send(dataslice); // The data does not start with 0 or 123 "{" so it can't be confused for JSON.
}
uploadFile.xptr = end;
Q('d2progressBar').value = end;
}
} else if (uploadFile.xfile) {
if (uploadFile.xreader != null) return; // Data reading already in process
if (uploadFile.xptr >= uploadFile.xfile.size) return;
var end = uploadFile.xptr + 16384;
if (end > uploadFile.xfile.size) { if (dataPriming == true) { return; } end = uploadFile.xfile.size; }
uploadFile.xreader = new FileReader();
uploadFile.xreader.onerror = function (err) { console.log(err); }
uploadFile.xreader.onload = function () {
var data = uploadFile.xreader.result;
delete uploadFile.xreader;
if (data == null) return;
var dataslice = new Uint8Array(data)
if ((dataslice[0] == 123) || (dataslice[0] == 0)) {
var datapart = new Uint8Array(data.byteLength + 1);
datapart.set(dataslice, 1); // Add a zero char at the start of the send, this will indicate that it's not a JSON command.
files.send(datapart);
} else {
files.send(dataslice); // The data does not start with 0 or 123 "{" so it can't be confused for JSON.
}
uploadFile.xptr = end;
Q('d2progressBar').value = end;
if (uploadFile.xptr >= uploadFile.xfile.size) {
files.sendText(JSON.stringify({ action: 'uploaddone', reqid: uploadFile.xfilePtr }));
} else {
if (uploadFile.xdataPriming > 0) { uploadFile.xdataPriming--; p13uploadNextPart(true); }
}
};
uploadFile.xreader.readAsArrayBuffer(uploadFile.xfile.slice(uploadFile.xptr, end));
}
}
//
// DEVICE DETAILS
//
var DeviceDetailsHardware = null;
var DeviceDetailsNetwork = null;
var DeviceDetailsNodeId = null;
function updateDeviceDetails(node, hardware, network) {
if (currentNode == null) return;
if (node == null) { node = currentNode; }
if (currentNode._id != node._id) return;
if (DeviceDetailsNodeId != node._id) { DeviceDetailsHardware = null; DeviceDetailsNetwork = null; DeviceDetailsNodeId = node._id; }
if (hardware != null) { DeviceDetailsHardware = hardware; }
if (network != null) { DeviceDetailsNetwork = network; }
hardware = DeviceDetailsHardware;
network = DeviceDetailsNetwork;
if (hardware == null) { hardware = {}; }
if (network == null) { network = {}; }
var sections = [], s = {};
// Operating System
if ((hardware.windows && hardware.windows.osinfo) || node.osdesc) {
var x = '';
if (node.rname) { x += addDetailItem("Name", EscapeHtml(node.rname), s); }
if (node.osdesc) { x += addDetailItem("Version", EscapeHtml(node.osdesc), s); }
if (hardware.windows && hardware.windows.osinfo) {
var m = hardware.windows.osinfo;
if (m.OSArchitecture) {
if (m.OSArchitecture.startsWith('32')) { x += addDetailItem("Architecture", "32-bit", s); }
else if (m.OSArchitecture.startsWith('64')) { x += addDetailItem("Architecture", "64-bit", s); }
else { x += addDetailItem("Architecture", EscapeHtml(m.OSArchitecture), s); }
}
}
// Windows Security Central
if (node.wsc) {
var y = [];
if (node.wsc.antiVirus != null) { if (node.wsc.antiVirus == 'OK') { y.push("AV" + ' - <span style=color:green>' + "OK" + '</span>'); } else { y.push("AV" + ' - <span style=color:red>' + "BAD" + '</span>'); } }
if (node.wsc.autoUpdate != null) { if (node.wsc.autoUpdate == 'OK') { y.push("Update" + ' - <span style=color:green>' + "OK" + '</span>'); } else { y.push("Update" + ' - <span style=color:red>' + "BAD" + '</span>'); } }
if (node.wsc.firewall != null) { if (node.wsc.firewall == 'OK') { y.push("Firewall" + ' - <span style=color:green>' + "OK" + '</span>'); } else { y.push("Firewall" + ' - <span style=color:red>' + "BAD" + '</span>'); } }
x += addDetailItem("Windows Security", y.join(', '));
}
// Antivirus
if (node.av && node.av.length > 0) {
var y = [];
for (var i in node.av) {
if (node.av[i].product) {
var avx = EscapeHtml(node.av[i].product);
if (node.av[i].enabled !== true) { avx += ' - <span style=color:red>' + "Disabled" + '</span>'; }
if (node.av[i].updated !== true) { avx += ' - <span style=color:red>' + "Out of date" + '</span>'; }
if ((node.av[i].enabled == true) && (node.av[i].updated == true)) { avx += ' - <span style=color:green>' + "OK" + '</span>'; }
y.push(avx);
}
}
x += addDetailItem("Antivirus", y.join('<br />'));
}
if (x != '') { sections.push({ name: "Operating System", html: x, img: 'software' }); }
}
// MeshAgent
if (node.agent) {
var x = '';
if ((node.agent != null) && (node.agent.id != null) && (node.agent.ver != null)) {
var str = '';
if (node.agent.id <= agentsStr.length) { str = agentsStr[node.agent.id]; } else { str = agentsStr[0]; }
if (node.agent.ver != 0) { str += ' v' + node.agent.ver; }
if (node.agent.id == 14) { str = node.agent.core; }
x += addDetailItem("Mesh Agent", str);
}
if ((node.conn & 1) != 0) {
x += addDetailItem("Last agent connection", "Connected now");
} else {
if (node.lastconnect) { x += addDetailItem("Last agent connection", printDateTime(new Date(node.lastconnect))); }
}
if (node.lastaddr) {
var splitip = node.lastaddr.split(':');
if (splitip.length > 2) {
// IPv6
x += addDetailItem("Last agent address", node.lastaddr);
} else {
// IPv4
if (isPrivateIP(node.lastaddr)) {
x += addDetailItem("Last agent address", splitip[0]);
} else {
x += addDetailItem("Last agent address", '<a href="https://iplocation.com/?ip=' + splitip[0] + '" rel="noreferrer noopener" target="MeshIPLoopup">' + splitip[0] + '</a>');
}
}
}
if (hardware.agentvers != null) {
if (hardware.agentvers.compileTime) {
try {
var d = Date.parse(hardware.agentvers.compileTime)
x += addDetailItem("Compile time", printDateTime(new Date(d)));
} catch (ex) { }
}
}
if (x != '') { sections.push({ name: "Mesh Agent", html: x, img: 'meshagent' }); }
}
// Mobile
if (hardware.mobile) {
var x = '';
if (hardware.mobile.brand && hardware.mobile.model) { x += addDetailItem("Model", EscapeHtml(hardware.mobile.brand + ', ' + hardware.mobile.model), s); }
if (hardware.mobile.device) { x += addDetailItem("Device", EscapeHtml(hardware.mobile.device), s); }
if (hardware.mobile.bootloader) { x += addDetailItem("Bootloader", EscapeHtml(hardware.mobile.bootloader), s); }
if (hardware.mobile.id) { x += addDetailItem("Identifier", EscapeHtml(hardware.mobile.id), s); }
if (hardware.mobile.host) { x += addDetailItem("Hostname", EscapeHtml(hardware.mobile.host), s); }
if (x != '') { sections.push({ name: "Mobile Device", html: x, img: 'mobile' }); }
}
// Networking
if (network.netif2 != null) {
// Display one network interface for each MAC address
var x = '';
x += '<table style=width:100%>';
for (var i in network.netif2) {
var m = network.netif2[i];
if ((Array.isArray(m) == false) || (m.length < 1) || (m[0] == null) || ((typeof m[0].mac == 'string') && (m[0].mac.startsWith('00:00:00:00')))) continue;
x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
x += '<div style=margin-bottom:3px><b>' + EscapeHtml(i + (m[0].fqdn ? (', ' + m[0].fqdn) : '')) + '</b></div>';
if (m.desc) { x += addDetailItem("Description", EscapeHtml(m.desc).split('(R)').join('&reg;')); }
//if (m.dnssuffix) { x += addDetailItem("DNS Suffix", m.dnssuffix); }
if (typeof m[0].mac == 'string') {
if (m[0].gatewaymac) {
x += addDetailItem("MAC Layer", format("MAC: {0}, Gateway: {1}", EscapeHtml(m[0].mac), EscapeHtml(m[0].gatewaymac)));
} else {
x += addDetailItem("MAC Layer", format("MAC: {0}", EscapeHtml(m[0].mac)));
}
}
for (var j = 0; j < m.length; j++) {
var iplayer = m[j], items = [];
if (iplayer.address) { items.push(format("IP: {0}", EscapeHtml(iplayer.address))); }
if (iplayer.netmask) { items.push(format("Mask: {0}", EscapeHtml(iplayer.netmask))); }
if (iplayer.gateway) { items.push(format("Gateway: {0}", EscapeHtml(iplayer.gateway))); }
if (items.length > 0) {
if (iplayer.family == 'IPv4') { x += addDetailItem("IPv4 Layer", items.join(", ")); }
if (iplayer.family == 'IPv6') { x += addDetailItem("IPv6 Layer", items.join(", ")); }
}
}
x += '</div>';
}
x += '</table>';
if (x != '') { sections.push({ name: "Networking", html: x, img: 'networking' }); }
}
// Attribute: Intel AMT
if (node.intelamt != null) {
var x = '';
x += addDetailItem("Version", (node.intelamt.ver) ? ('v' + EscapeHtml(node.intelamt.ver)) : ('<i>' + "Unknown" + '</i>'), s);
var provisioningStates = { 0: nobreak("Not Activated (Pre)"), 1: nobreak("Not Activated (In)"), 2: nobreak("Activated") };
var provisioningMode = '';
if ((node.intelamt.state == 2) && node.intelamt.flags) { if (node.intelamt.flags & 2) { provisioningMode = (', ' + "Client Control Mode (CCM)"); } else if (node.intelamt.flags & 4) { provisioningMode = (', ' + "Admin Control Mode (ACM)"); } }
x += addDetailItem("Provisioning State", ((node.intelamt.state) ? (provisioningStates[node.intelamt.state]) : ('<i>' + "Unknown" + '</i>')) + provisioningMode, s);
x += addDetailItem("Security", (node.intelamt.tls == 1) ? "Secured using TLS" : "TLS is not setup", s);
// Check that the Intel AMT user is setup and there is no warnings (1 = invalid credentials, 8 = trying)
x += addDetailItem("Admin Credentials", ((node.intelamt.user) == null || (node.intelamt.user == '') || ((node.intelamt.warn != null) && ((node.intelamt.warn & 9) != 0))) ? "Not Known" : "Known", s);
if (x != '') {
if ((typeof node.intelamt.sku == 'number') && ((node.intelamt.sku & 16) != 0)) {
sections.push({ name: "Intel&reg; Standard Manageability (Intel&reg; SM)", html: x, img: 'amt' });
} else {
sections.push({ name: "Intel&reg; Active Management Technology (Intel&reg; AMT)", html: x, img: 'amt' });
}
}
}
if (hardware.identifiers) {
var x = '', ident = hardware.identifiers;
// BIOS
if (ident.bios_vendor) { x += addDetailItem("Vendor", EscapeHtml(ident.bios_vendor), s); }
if (ident.bios_version) { x += addDetailItem("Version", EscapeHtml(ident.bios_version), s); }
if (x != '') { sections.push({ name: "BIOS", html: x, img: 'chip' }); }
// Motherboard
x = '';
if (ident.board_vendor) { x += addDetailItem("Vendor", EscapeHtml(ident.board_vendor), s); }
if (ident.board_name) { x += addDetailItem("Name", EscapeHtml(ident.board_name), s); }
if (ident.board_serial && (ident.board_serial != '')) { x += addDetailItem("Serial", EscapeHtml(ident.board_serial), s); }
if (ident.board_version) { x += addDetailItem("Version", EscapeHtml(ident.board_version), s); }
if (ident.product_uuid) { x += addDetailItem("Identifier", EscapeHtml(ident.product_uuid), s); }
if (ident.cpu_name) { x += addDetailItem("CPU", EscapeHtml(ident.cpu_name).split('(TM)').join('&trade;').split('(R)').join('&reg;'), s); }
if (ident.gpu_name) { for (var i in ident.gpu_name) { x += addDetailItem("GPU", EscapeHtml(ident.gpu_name[i]).split('(TM)').join('&trade;').split('(R)').join('&reg;'), s); } }
if (x != '') { sections.push({ name: "Motherboard", html: x, img: 'motherboard' }); }
}
if (hardware.windows) {
if (hardware.windows.memory && (hardware.windows.memory.length > 0)) {
var x = '';
// Sort Memory
hardware.windows.memory.sort(function (a, b) { if (a.BankLabel > b.BankLabel) return 1; if (a.BankLabel < b.BankLabel) return -1; return 0; });
x += '<table style=width:100%>';
for (var i in hardware.windows.memory) {
var m = hardware.windows.memory[i];
x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
x += '<div style=margin-bottom:3px><b>' + EscapeHtml(m.BankLabel) + '</b></div>';
if (m.Capacity && m.Speed) { x += addDetailItem("Capacity / Speed", format("{0} Mb, {1} Mhz", (m.Capacity / 1024 / 1024), m.Speed), s); }
else if (m.Capacity) { x += addDetailItem("Capacity", format("{0} Mb", (m.Capacity / 1024 / 1024)), s); }
if (m.PartNumber) { x += addDetailItem("Part Number", EscapeHtml((m.Manufacturer && m.Manufacturer != 'Undefined') ? (m.Manufacturer + ', ') : '') + EscapeHtml(m.PartNumber), s); }
x += '</div>';
}
x += '</table>';
if (x != '') { sections.push({ name: "Memory", html: x, img: 'ram' }); }
}
}
// Storage
if (hardware.identifiers && ident.storage_devices) {
var x = '';
// Sort Storage
ident.storage_devices.sort(function (a, b) { if (a.Caption > b.Caption) return 1; if (a.Caption < b.Caption) return -1; return 0; });
x += '<table style=width:100%>';
for (var i in ident.storage_devices) {
var m = ident.storage_devices[i];
if (m.Size) {
x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
x += '<div style=margin-bottom:3px><b>' + EscapeHtml(m.Caption) + '</b></div>';
if (m.Model && (m.Model != m.Caption)) { x += addDetailItem("Model", EscapeHtml(m.Model), s); }
if (m.Size) {
if ((typeof m.Size == 'string') && (parseInt(m.Size) == m.Size)) { m.Size = parseInt(m.Size); }
if (typeof m.Size == 'number') { x += addDetailItem("Capacity", format("{0} Mb", Math.floor(m.Size / 1024 / 1024)), s); }
if (typeof m.Size == 'string') { x += addDetailItem("Capacity", EscapeHtml(m.Size), s); }
}
x += '</div>';
}
}
x += '</table>';
if (x != '') { sections.push({ name: "Storage", html: x, img: 'storage' }); }
}
// Render the sections
var x = '';
for (var i in sections) {
if (sections[i].img == null) {
x += '<div class=DevSt style=margin-bottom:3px;margin-left:4px><b>' + sections[i].name + '</b></div><div style=margin-bottom:10px;margin-left:4px>' + sections[i].html + '</div>';
} else {
x += '<table style=width:100%><tr>';
x += '<td style=width:32px;vertical-align:top><img src=images/details/' + sections[i].img + '32.png srcset="images/details/' + sections[i].img + '64.png 2x" border=0 width=32 /></td>'; // height=12
x += '<td><div class=DevSt style=margin-bottom:3px;margin-left:4px><b>' + sections[i].name + '</b></div><div style=margin-bottom:10px;margin-left:4px>' + sections[i].html + '</div></td>';
x += '</tr></table>';
}
}
if (x == '') {
QH('p10detailshtml', "No information for this device.");
} else {
QH('p10detailshtml', x);
}
}
//
// CONSOLE
//
/*
function agentConsoleHandleKeys(e) {
if ((e.ctrlKey) || (e.altKey)) { return true; }
var processed = 0, box = Q('p15consoleText');
if (e.key) {
if (e.keyCode == 13 && consoleFocus == 0) { p15consoleSend(e); processed = 1; }
else if (e.keyCode == 8 && consoleFocus == 0) { var x = box.value; box.value = x.substring(0, x.length - 1); processed = 1; }
else if (e.keyCode == 27) { box.value = ''; processed = 1; }
else if ((e.keyCode == 38) || (e.keyCode == 40)) { // Arrow up || Arrow down
var hindex = consoleHistory.indexOf(box.value);
//console.log(hindex, consoleHistory);
if ((e.keyCode == 38) && ((consoleHistory.length - 1) > hindex)) { box.value = consoleHistory[hindex + 1]; }
else if ((e.keyCode == 40) && (hindex > 0)) { box.value = consoleHistory[hindex - 1]; }
else if ((e.keyCode == 40) && (hindex == 0)) { box.value = ''; }
processed = 1;
}
else if (e.key.length === 1) {
//box.value = ((box.value + e.key));
insertTextAtCursor(box, e.key);
processed = 1;
}
} else {
if (e.charCode != 0 && consoleFocus == 0) { box.value = ((box.value + String.fromCharCode(e.charCode))); processed = 1; }
}
if (processed > 0) { return haltEvent(e); }
}
*/
// Insert text at the cursor location on the
function insertTextAtCursor(ctrl, val) {
if (document.selection) { ctrl.focus(); sel = document.selection.createRange(); sel.text = val; }
else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
var start = ctrl.selectionStart, end = ctrl.selectionEnd;
ctrl.value = ctrl.value.substring(0, start) + val + ctrl.value.substring(end, ctrl.value.length);
ctrl.setSelectionRange(end + 1, end + 1);
} else { ctrl.value += myValue; }
}
var consoleNode;
var consoleServerText = '';
function setupConsole() {
// Setup the console
var samenode = (consoleNode == currentNode);
consoleNode = currentNode;
var mesh = meshes[consoleNode.meshid];
var rights = GetNodeRights(currentNode);
if ((rights & 16) != 0) {
if (consoleNode.consoleText == null) { consoleNode.consoleText = ''; }
if (samenode == false) {
QH('p15agentConsoleText', consoleNode.consoleText);
Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
}
var online = (((consoleNode.conn & 1) != 0) || ((consoleNode.conn & 16) != 0)) ? true : false;
var onlineText = ((consoleNode.conn & 1) != 0) ? "Agent is online" : "Agent is offline"
if ((consoleNode.conn & 16) != 0) { onlineText += ", MQTT is online" }
QH('p15statetext', onlineText);
QE('p15uploadCore', ((consoleNode.conn & 1) != 0));
QV('p15outputselecttd', ((consoleNode.conn & 16) != 0) || ((currentNode.pmt == 1) && ((features2 & 2) != 0)));
QV('p15outputselect2', ((consoleNode.conn & 16) != 0)); // MQTT channel
QV('p15outputselect3', ((currentNode.pmt == 1) && ((features2 & 2) != 0))); // Push Notification channel
var c = Q('p15outputselect').value;
if (((consoleNode.conn & 16) == 0) && (c == 2)) { c = 1; Q('p15outputselect').value = 1; }
if (((currentNode.pmt != 1) || ((features2 & 2) == 0)) && (c == 3)) { c = 1; Q('p15outputselect').value = 1; }
var active = false;
if (((consoleNode.conn & 1) != 0) && (c == 1)) { active = true; } // Agent
if (((consoleNode.conn & 16) != 0) && (c == 2)) { active = true; } // MQTT
if (((currentNode.pmt == 1) && ((features2 & 2) != 0)) && (c == 3)) { active = true; } // Push
QE('p15consoleText', active);
} else {
QH('p15statetext', "Access Denied");
QE('p15consoleText', false);
QE('p15uploadCore', false);
QV('p15outputselecttd', false);
}
QV('devListToolbarViewIcons3', ((consoleNode.conn & 1) != 0));
}
// Clear the console for this node
function p15consoleClear() {
QH('p15agentConsoleText', '');
Q('id_p15consoleClear').blur();
consoleNode.consoleText = '';
}
// Send a command to the agent
var consoleHistory = [];
function p15consoleSend(e) {
if (e && e.keyCode != 13) return;
var v = Q('p15consoleText').value, t = '<div style=color:green>&gt; ' + EscapeHtml(v) + '<br/></div>';
if (((consoleNode.conn & 16) != 0) && (Q('p15outputselect').value == 2)) {
// Send the command to MQTT
t = '<div style=color:orange>' + "MQTT" + '&gt; ' + EscapeHtml(v) + '<br/></div>';
consoleNode.consoleText += t;
meshserver.send({ action: 'sendmqttmsg', topic: 'console', nodeids: [consoleNode._id], msg: v });
} else if ((consoleNode.pmt == 1) && (Q('p15outputselect').value == 3) && ((features2 & 2) != 0)) {
// Send the command using push notification
t = '<div style=color:violet>' + "PUSH" + '&gt; ' + EscapeHtml(v) + '<br/></div>';
consoleNode.consoleText += t;
meshserver.send({ action: 'pushconsole', nodeid: consoleNode._id, console: v });
} else if ((consoleNode.conn & 1) != 0) {
// Send the command to the mesh agent
consoleNode.consoleText += t;
meshserver.send({ action: 'msg', type: 'console', nodeid: consoleNode._id, value: v });
}
Q('p15agentConsoleText').innerHTML += t;
Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
Q('p15consoleText').value = '';
// Add command to history list
if (v.length > 0) {
// Move this command to the top if it already exists
var j = consoleHistory.indexOf(v);
if (j >= 0) { consoleHistory.splice(j, 1); }
consoleHistory.unshift(v);
consoleHistory.splice(10);
}
}
// Handle Mesh Agent console data
function p15consoleReceive(node, data, source) {
if (node === 'serverconsole') {
// Server console data
data = '<div>' + EscapeHtml(data) + '</div>'
consoleServerText += data;
if (consoleNode == 'server') {
Q('p15agentConsoleText').innerHTML += data;
Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
}
} else {
// Agent console data
if (source == 'MQTT') { data = '<div style=color:red>' + "MQTT" + '&gt; ' + EscapeHtml(data) + '<br/></div>'; } else { data = '<div>' + EscapeHtml(data) + '</div>' }
if (node.consoleText == null) { node.consoleText = data; } else { node.consoleText += data; }
if (consoleNode == node) {
Q('p15agentConsoleText').innerHTML += data;
Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
}
}
}
// Save console text to file
function p15downloadConsoleText() {
saveAs(new Blob([Q('p15agentConsoleText').innerText], { type: 'application/octet-stream' }), "console.txt");
}
// Called then user presses the "Change Core" button
function p15uploadCore(e) {
if (xxdialogMode) return;
if (e.shiftKey == true) { meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'default' }); } // Upload default core
else if (e.altKey == true) { meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'clear' }); } // Clear the core
else if (e.ctrlKey == true) { p15uploadCore2(); } // Upload the core from a file
else { setDialogMode(2, "Perform Agent Action", 3, p15uploadCoreEx, addHtmlValue("Action", '<select id=d3coreMode style=width:230px><option value=1>' + "Upload default server core" + '</option><option value=2>' + "Clear the core" + '</option><option value=6>' + "Upload recovery core" + '</option><option value=7>' + "Upload tiny core" + '</option><option value=3>' + "Upload a core file" + '</option><option value=4>' + "Soft disconnect agent" + '</option><option value=5>' + "Hard disconnect agent" + '</option></select>')); }
}
function p15uploadCoreEx() {
if (Q('d3coreMode').value == 1) {
// Upload default core
meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'default' });
} else if (Q('d3coreMode').value == 2) {
// Clear the core
meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'clear' });
} else if (Q('d3coreMode').value == 3) {
// Upload file as core
p15uploadCore2();
} else if (Q('d3coreMode').value == 4) {
// Soft disconnect the mesh agent
meshserver.send({ action: 'agentdisconnect', nodeid: consoleNode._id, disconnectMode: 1 });
} else if (Q('d3coreMode').value == 5) {
// Hard disconnect the mesh agent
meshserver.send({ action: 'agentdisconnect', nodeid: consoleNode._id, disconnectMode: 2 });
} else if (Q('d3coreMode').value == 6) {
// Upload a recovery core
meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'recovery' });
} else if (Q('d3coreMode').value == 7) {
// Upload a tiny core
meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'tiny' });
}
}
// Called then user opts to upload a file as core
function p15uploadCore2() {
if (xxdialogMode) return;
Q('d3localmodeform').action = 'uploadmeshcorefile.ashx';
Q('d3auth').value = authCookie;
Q('d3attrib').value = currentNode._id;
setDialogMode(3, "Upload Mesh Agent Core", 3, p15uploadCoreEx2);
d3init();
}
function p15uploadCoreEx2() {
var mode = Q('d3uploadMode').value;
if (mode == 1) {
// Upload local mesh agent core
Q('d3submit').click();
} else {
// Upload server mesh agent code
var files = d3getFileSel();
if (files.length == 1) { meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'custom', path: d3filetreelocation.join('/') + '/' + files[0] }); }
}
}
//
// MY MESHS
//
var currentMesh;
function p20updateMesh() {
if (currentMesh == null) return;
QH('p20meshName', EscapeHtml(currentMesh.name));
var meshtype = format("Unknown #{0}", currentMesh.mtype);
var meshrights = GetMeshRights(currentMesh);
if (currentMesh.mtype == 1) meshtype = "Intel&reg; AMT only, no agent";
if (currentMesh.mtype == 2) meshtype = "Managed using a software agent";
if (currentMesh.mtype == 3) { if (currentMesh.relayid == null) { meshtype = "Local devices, no agent"; } else { meshtype = "No agent devices relayed thru agent"; } }
if (currentMesh.mtype == 4) { if (currentMesh.relayid == null) { meshtype = "IP-KVM device"; } else { meshtype = "IP-KVM device relayed thru agent"; } if (currentMesh.kvm.model == 1) { meshtype += ', ' + 'Raritan KX III'; } }
var x = '';
x += addHtmlValue("Name", addLinkConditional(EscapeHtml(currentMesh.name), 'p20editmesh(1)', (meshrights & 1) != 0));
x += addHtmlValue("Description", addLinkConditional(((currentMesh.desc && currentMesh.desc != '') ? EscapeHtml(currentMesh.desc) : ('<i>' + "None" + '</i>')), 'p20editmesh(2)', (meshrights & 1) != 0));
x += addHtmlValue("Type", meshtype);
//x += addHtmlValue('Identifier', currentMesh._id.split('/')[2]);
// Display the relay device if applicable
if (((currentMesh.mtype == 3) || (currentMesh.mtype == 4)) && (currentMesh.relayid != null)) {
var relayName = '<i>' + "Unknown" + '</i>';
var relayNode = getNodeFromId(currentMesh.relayid);
if (relayNode != null) { relayName = EscapeHtml(relayNode.name); }
x += addHtmlValue("Relay Device", addLinkConditional(relayName, 'p20editmeshrelay()', (meshrights & 1) != 0));
}
// Display IP-KVM information if needed
if (currentMesh.mtype == 4) {
x += addHtmlValue("Hostname", currentMesh.kvm.host);
x += addHtmlValue("Username", currentMesh.kvm.user);
}
x += '<br><input type=button value="' + "Notes" + '" onclick=showNotes(false,"' + encodeURIComponent(currentMesh._id) + '") />';
x += '<br style=clear:both><br>';
var currentMeshLinks = currentMesh.links[userinfo._id];
if (currentMeshLinks && ((currentMeshLinks.rights & 2) != 0)) { x += '<div style=margin-bottom:6px;float:left;margin-right:10px><a onclick=p20showAddMeshUserDialog() style=cursor:pointer><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add User" + '</a></div>'; }
if (navigator.userAgent.toLowerCase().indexOf('android') >= 0) {
x += '<div style=margin-bottom:6px;float:left;margin-right:10px><a onclick=p20installAndroidDialog() style=cursor:pointer><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Install on this device" + '</a></div>';
}
/*
if ((meshrights & 4) != 0) {
if (currentMesh.mtype == 1) {
x += '<a onclick=addCiraDeviceToMesh("' + currentMesh._id + '") style=cursor:pointer;margin-right:10px><img src=images/icon-installmesh.png border=0 height=12 width=12> Install CIRA</a>';
x += '<a onclick=addDeviceToMesh("' + currentMesh._id + '") style=cursor:pointer;margin-right:10px><img src=images/icon-installmesh.png border=0 height=12 width=12> Install local</a>';
}
if (currentMesh.mtype == 2) {
x += '<a onclick=addAgentToMesh("' + currentMesh._id + '") style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> Install</a>';
}
}
*/
/*
function getMeshActions(mesh, meshrights) {
if ((meshrights & 4) == 0) return '';
var r = '';
if (mesh.mtype == 1) {
r += ' <a style=cursor:pointer;font-size:10px onclick=addCiraDeviceToMesh("' + mesh._id + '")>Add CIRA</a>';
r += ' <a style=cursor:pointer;font-size:10px onclick=addDeviceToMesh("' + mesh._id + '")>Add Local</a>';
}
if (mesh.mtype == 2) {
r += ' <a style=cursor:pointer;font-size:10px onclick=addAgentToMesh("' + mesh._id + '")>Add Agent</a>';
}
return r;
}
*/
x += '<table style="color:black;background-color:#EEE;border-color:#AAA;border-width:1px;border-style:solid;border-collapse:collapse" border=0 cellpadding=2 cellspacing=0 width=100%><tbody><tr style=background-color:#AAAAAA;font-weight:bold><th scope=col style=text-align:left;width:430px>' + "User Authorizations" + '</th></tr>';
// Sort the users for this mesh
var count = 1, sortedusers = [];
for (var i in currentMesh.links) {
var uname = i.split('/')[2];
if (currentMesh.links[i].name) { uname = currentMesh.links[i].name; }
if (i == userinfo._id) { uname = userinfo.name; }
if ((usergroups != null) && (usergroups[i] != null)) { uname = usergroups[i].name; }
sortedusers.push({ id: i, name: uname, rights: currentMesh.links[i].rights });
}
sortedusers.sort(function (a, b) { if (a.name > b.name) return 1; if (a.name < b.name) return -1; return 0; });
// Display all users for this mesh
for (var i in sortedusers) {
var trash = '', rights = "Partial Rights", r = sortedusers[i].rights, icon = 2;
if (r == 0xFFFFFFFF) rights = "Full Administrator"; else if (r == 0) rights = "No Rights";
if ((i != userinfo._id) && (meshrights == 0xFFFFFFFF || (((meshrights & 2) != 0)))) { trash = '<a onclick=p20deleteUser(event,"' + encodeURIComponent(sortedusers[i].id) + '") style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
if (sortedusers[i].id.startsWith('ugrp/')) { icon = 4; }
x += '<tr onclick=p20viewuser("' + encodeURIComponent(sortedusers[i].id) + '") style=height:32px;cursor:pointer' + (((count % 2) == 0) ? ';background-color:#DDD' : '') + '><td>';
x += '<div style=float:right>' + trash + '</div><div style=float:right;padding-right:4px>' + rights + '</div><div class=m' + icon + '></div><div>&nbsp;' + EscapeHtml(decodeURIComponent(sortedusers[i].name)) + '<div></div></div>';
x += '</td></tr>';
++count;
}
x += '</tbody></table>';
// If we are full administrator on this mesh, allow deletion of the mesh
if (meshrights == 0xFFFFFFFF) { x += '<div style=font-size:small;text-align:right;margin-top:6px><span><a onclick=p20showDeleteMeshDialog() style=cursor:pointer>' + "Delete Group" + '</a></span></div>'; }
QH('p20info', x);
}
function p20showDeleteMeshDialog() {
if (xxdialogMode) return false;
var x = format("Are you sure you want to delete group {0}? Deleting the device group will also delete all information about devices within this group.", EscapeHtml(currentMesh.name)) + '<br /><br />';
x += '<label><input id=p20check type=checkbox onchange=p20validateDeleteMeshDialog() />' + "Confirm" + '</label>';
setDialogMode(2, "Delete Group", 3, p20showDeleteMeshDialogEx, x);
p20validateDeleteMeshDialog();
return false;
}
function p20validateDeleteMeshDialog() {
QE('idx_dlgOkButton', Q('p20check').checked);
}
function p20showDeleteMeshDialogEx(buttons, tag) {
meshserver.send({ action: 'deletemesh', meshid: currentMesh._id, meshname: currentMesh.name });
}
function p20editmeshrelay() {
if (xxdialogMode) return;
// Look for all relay devices
var relayDevices = [];
if ((features & 2) == 0) { for (var i in nodes) { var node = nodes[i]; if ((node.mtype == 2) && (node.agent != null) && (GetNodeRights(node) == 0xFFFFFFFF)) { relayDevices.push(node); } } }
relayDevices.sort(nameSort);
if (relayDevices.length == 0) {
// Relay relay devices available
setDialogMode(2, "Edit Device Group", 1, null, "No relay devices available.");
} else {
var relayDevices2 = [];
for (var i in relayDevices) { relayDevices2.push('<option value="' + (relayDevices[i]._id + '"' + ((currentMesh.relayid == relayDevices[i]._id) ? ' selected' : '')) + '>' + EscapeHtml(relayDevices[i].name) + '</option>'); }
var x = addHtmlValue("Relay Device", '<div style=width:170px><select id=d2devrelay style=width:100%>' + relayDevices2.join('') + '</select></div>');
setDialogMode(2, "Edit Device Group", 3, p20editmeshrelayEx, x);
}
}
function p20editmeshrelayEx() {
meshserver.send({ action: 'editmesh', meshid: currentMesh._id, relayid: Q('d2devrelay').value });
}
function p20editmesh(focus) {
if (xxdialogMode) return;
var x = addHtmlValue("Name", '<input id=dp20meshname style=width:170px maxlength=32 onchange=p20editmeshValidate() onkeyup=p20editmeshValidate() />');
x += addHtmlValue("Description", '<input id=dp20meshdesc style=width:170px maxlength=1024 onkeyup=p20editmeshValidate() />');
setDialogMode(2, "Edit Device Group", 3, p20editmeshEx, x);
Q('dp20meshname').value = currentMesh.name;
if (currentMesh.desc) Q('dp20meshdesc').value = currentMesh.desc;
p20editmeshValidate();
if (focus == 2) { Q('dp20meshdesc').focus(); } else { Q('dp20meshname').focus(); }
}
function p20editmeshEx() {
meshserver.send({ action: 'editmesh', meshid: currentMesh._id, meshname: Q('dp20meshname').value, desc: Q('dp20meshdesc').value });
}
function p20editmeshValidate() {
QE('idx_dlgOkButton', Q('dp20meshname').value.length > 0);
}
function p20installAndroidDialog() {
if (xxdialogMode) return;
var x = '<div>' + "Install the MeshCentral Agent on your Android device. Once installed, click the pairing link to connect your device to this server." + '</div>';
x += '<br /><div><a href="https://play.google.com/store/apps/details?id=com.meshcentral.agent2"><b>' + "MeshCentral Agent for Android" + '</b></a><div>';
x += '<br /><div><a href="' + serverinfo.magenturl + ',' + serverinfo.agentCertHash + ',' + currentMesh._id.split('/')[2] + '"><b>' + "Device Pairing Link" + '</b></a><div>';
setDialogMode(2, "Android Installation", 1, null, x);
}
function p20showAddMeshUserDialog() {
if (xxdialogMode) return;
var x = addHtmlValue('User ID', '<input id=dp20username style=width:170px maxlength=256 onchange=p20validateAddMeshUserDialog() onkeyup=p20validateAddMeshUserDialog() />');
x += '<div style="border:2px groove gray;background-color:white;max-height:120px;overflow-y:scroll">';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20fulladmin>' + "Full Administrator" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20editmesh>' + "Edit Device Group" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20manageusers>' + "Manage Device Group Users" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20managecomputers>' + "Manage Device Group Computers" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20remotecontrol>' + "Remote Control" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20remoteview style=margin-left:12px>' + "Remote View Only" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20remotelimitedinput style=margin-left:12px>' + "Limited Input Only" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20noterminal style=margin-left:12px>' + "No Terminal Access" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20nofiles style=margin-left:12px>' + "No File Access" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20noamt style=margin-left:12px>' + "No Intel&reg; AMT" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20meshagentconsole>' + "Mesh Agent Console" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20meshserverfiles>' + "Server Files" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20wakedevices>' + "Wake Devices" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20editnotes>' + "Edit Device Notes" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20limitevents>' + "Show Only Own Events" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20chatnotify>' + "Chat & Notify" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20uninstall>' + "Uninstall Agent" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20commands>' + "Remote Commands" + '</label><br>';
x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20resetoff>' + "Reset / Power Off" + '</label><br>';
x += '</div>';
setDialogMode(2, "Add User to Device Group", 3, p20showAddMeshUserDialogEx, x);
p20validateAddMeshUserDialog();
Q('dp20username').focus();
}
function p20validateAddMeshUserDialog() {
var meshrights = GetMeshRights(currentMesh);
var nc = !Q('p20fulladmin').checked;
QE('p20fulladmin', meshrights == 0xFFFFFFFF);
QE('p20editmesh', nc && (meshrights == 0xFFFFFFFF));
QE('p20manageusers', nc);
QE('p20managecomputers', nc);
QE('p20remotecontrol', nc);
QE('p20meshagentconsole', nc);
QE('p20meshserverfiles', nc);
QE('p20wakedevices', nc);
QE('p20editnotes', nc);
QE('p20limitevents', nc);
QE('p20remoteview', nc && Q('p20remotecontrol').checked);
QE('p20remotelimitedinput', nc && Q('p20remotecontrol').checked && !Q('p20remoteview').checked);
QE('p20noterminal', nc && Q('p20remotecontrol').checked);
QE('p20nofiles', nc && Q('p20remotecontrol').checked);
QE('p20noamt', nc && Q('p20remotecontrol').checked);
QE('p20chatnotify', nc);
QE('p20uninstall', nc);
QE('p20commands', nc);
QE('p20resetoff', nc);
}
function p20showAddMeshUserDialogEx() {
var meshadmin = 0;
if (Q('p20fulladmin').checked == true) { meshadmin = 0xFFFFFFFF; } else {
if (Q('p20editmesh').checked == true) meshadmin += 1;
if (Q('p20manageusers').checked == true) meshadmin += 2;
if (Q('p20managecomputers').checked == true) meshadmin += 4;
if (Q('p20remotecontrol').checked == true) meshadmin += 8;
if (Q('p20meshagentconsole').checked == true) meshadmin += 16;
if (Q('p20meshserverfiles').checked == true) meshadmin += 32;
if (Q('p20wakedevices').checked == true) meshadmin += 64;
if (Q('p20editnotes').checked == true) meshadmin += 128;
if (Q('p20remoteview').checked == true) meshadmin += 256;
if (Q('p20noterminal').checked == true) meshadmin += 512;
if (Q('p20nofiles').checked == true) meshadmin += 1024;
if (Q('p20noamt').checked == true) meshadmin += 2048;
if (Q('p20remotelimitedinput').checked == true) meshadmin += 4096;
if (Q('p20limitevents').checked == true) meshadmin += 8192;
if (Q('p20chatnotify').checked == true) meshadmin += 16384;
if (Q('p20uninstall').checked == true) meshadmin += 32768;
if (Q('p20commands').checked == true) meshadmin += 131072;
if (Q('p20resetoff').checked == true) meshadmin += 262144;
}
var users = Q('dp20username').value.split(','), users2 = [];
for (var i in users) { users2.push(users[i].trim()); }
meshserver.send({ action: 'addmeshuser', meshid: currentMesh._id, meshname: currentMesh.name, usernames: users2, meshadmin: meshadmin });
}
function p20viewuser(userid) {
if (xxdialogMode) return;
userid = decodeURIComponent(userid);
var r = [], cmeshrights = GetMeshRights(currentMesh), meshrights = GetMeshRights(currentMesh, userid);
if (meshrights == 0xFFFFFFFF) r.push("Full Administrator"); else {
if ((meshrights & 1) != 0) r.push("Edit Device Group");
if ((meshrights & 2) != 0) r.push("Manage Device Group Users");
if ((meshrights & 4) != 0) r.push("Manage Device Group Computers");
if ((meshrights & 8) != 0) r.push("Remote Control");
if ((meshrights & 16) != 0) r.push("Agent Console");
if ((meshrights & 32) != 0) r.push("Server Files");
if ((meshrights & 64) != 0) r.push("Wake Devices");
if ((meshrights & 128) != 0) r.push("Edit Notes");
if ((meshrights & 256) != 0) r.push("Remote View Only");
if ((meshrights & 512) != 0) r.push("No Terminal");
if ((meshrights & 1024) != 0) r.push("No Files");
if ((meshrights & 2048) != 0) r.push("No Intel&reg; AMT");
if (((meshrights & 8) != 0) && ((meshrights & 4096) != 0) && ((meshrights & 256) == 0)) r.push("Limited Input");
if ((meshrights & 8192) != 0) r.push("Self Events Only");
if ((meshrights & 16384) != 0) r.push("Chat & Notify");
if ((meshrights & 32768) != 0) r.push("Uninstall");
if ((meshrights & 131072) != 0) r.push("Commands");
if ((meshrights & 262144) != 0) r.push("Reset/Off");
}
if (r.length == 0) { r.push("No Rights"); }
var buttons = 1, uname = userid.split('/')[2];
if (currentMesh.links[userid].name) { uname = currentMesh.links[userid].name; }
var x = addHtmlValue("User Name", EscapeHtml(uname));
if (uname != userid.split('/')[2]) { x += addHtmlValue("User ID", EscapeHtml(userid.split('/')[2])); }
x += addHtmlValue("Permissions", r.join(", "));
if (((userinfo._id) != userid) && (cmeshrights == 0xFFFFFFFF || (((cmeshrights & 2) != 0) && (meshrights != 0xFFFFFFFF)))) buttons += 4;
setDialogMode(2, "Device Group User", buttons, p20viewuserEx, x, userid);
}
function p20viewuserEx(button, userid) {
if (button != 2) return;
var uname = userid.split('/')[2];
if (users && users[userid]) { uname = users[userid].name; }
if (usergroups && usergroups[userid]) { uname = usergroups[userid].name; }
if (userinfo._id == userid) { uname = userinfo.name; }
setDialogMode(2, "Remote Mesh User", 3, p20viewuserEx2, format("Confirm removal of user {0}?", uname), userid);
}
function p20deleteUser(e, userid) { haltEvent(e); p20viewuserEx(2, decodeURIComponent(userid)); }
function p20viewuserEx2(button, userid) { meshserver.send({ action: 'removemeshuser', meshid: currentMesh._id, meshname: currentMesh.name, userid: userid }); }
//
// NOTIFICATIONS
//
var notifications = [];
// Toggle showing notifications
function clickNotificationIcon(show) {
//addNotification({ icon:0, text:'test' });
if (show == true) { QV('notifiyBox', true); } else if (show == false) { QV('notifiyBox', false); } else { QV('notifiyBox', QS('notifiyBox')['display'] == 'none'); }
drawNotifications();
}
// Set the notification count on the upper right oft he screen
function setNotificationCount(c) {
if (parseInt(Q('notificationCount').innerHTML) == c) return; // If the count did not change, exit now.
QH('notificationCount2', c);
QV('notificationCount', c > 0);
}
// Refresh the notification box
function drawNotifications() {
var notifySettings = getstore('notifications', 0);
var r = '';
if (notifications.length == 0) {
r = '<div style=margin:5px>' + "There are currently no notifications" + '</div>';
} else {
for (var i in notifications) {
var n = notifications[i], t = '', d = new Date(n.time), icon = 0;
if (n.title != null) { t = '<b>' + EscapeHtml(n.title) + '</b>: ' }
if (n.nodeid != null) {
var node = getNodeFromId(n.nodeid);
if (node != null) {
icon = node.icon;
if (notifySettings & 16) { t = '<b>' + EscapeHtml(meshes[node.meshid].name) + ' / ' + EscapeHtml(node.name) + '</b>: '; } else { t = '<b>' + EscapeHtml(node.name) + '</b>: '; } // Display with or without group name
}
}
r += '<div title="' + format("Occured at {0}", printDateTime(d)) + '" id="notifyx' + n.id + '" class=notification style="cursor:pointer;border-top:1px solid ' + ((r == '') ? 'transparent' : 'orange') + '">';
if (icon) { r += '<div class=j' + icon + ' onclick="notificationSelected(' + n.id + ')" style=margin:5px;float:left></div>'; }
r += '<div onclick="notificationDelete(' + n.id + ')" class=unselectable title="' + "Clear this notification" + '" style=margin:5px;float:right;color:orange><b>X</b></div><div onclick="notificationSelected(' + n.id + ')" style=margin:5px>' + t + EscapeHtml(n.text) + '</div><div style=margin-left:5px;margin-bottom:5px;color:gray;font-size:10px>' + printDateTime(d) + '</div></div>';
}
}
var deleteall = '';
if (notifications.length > 1) { deleteall = '<div id="notifyRemoveAll" onclick="deleteAllNotifications()" style="cursor:pointer;border-top:1px solid orange;margin:5px;color:orange;text-align:right;padding-right:3px">' + "Clear all" + '</div>'; }
QH('notifiyBox', '<div class=customScroll style="max-height:170px;overflow-y:auto;margin:5px">' + r + deleteall + '</div>');
}
// A notification was selected
function notificationSelected(id, del) {
var j = -1;
for (var i in notifications) { if (notifications[i].id == id) { j = i; } }
if (j != -1) {
notificationSelectedEx(notifications[j], id);
if (del && notifications[j]) {
if (notifications[j].notification) { notifications[j].notification.close(); delete notifications[j].notification; }
notificationDelete(id);
}
}
}
function notificationSelectedEx(n, id) {
if (n.nodeid != null) {
if (n.tag == 'desktop') gotoDevice(n.nodeid, 12); // Desktop
else if (n.tag == 'terminal') gotoDevice(n.nodeid, 11); // Terminal
else if (n.tag == 'files') gotoDevice(n.nodeid, 13); // Files
else if (n.tag == 'intelamt') gotoDevice(n.nodeid, 14); // Intel AMT
else if (n.tag == 'console') gotoDevice(n.nodeid, 15); // Files
else gotoDevice(n.nodeid, 10); // General
} else {
if ((n.tag == 'backupcodes') && !xxdialogMode) { account_manageOtp(0); notificationDelete(id); } // 2FA backup codes
else if ((n.tag != null) && n.tag.startsWith('meshmessenger/')) {
safeNewWindow('/messenger?id=' + n.tag + '&title=' + encodeURIComponentEx(n.username), n.tag.split('/')[2]);
notificationDelete(id);
} else if (n.url != null) {
safeNewWindow(n.url);
notificationDelete(id);
}
}
}
// Remove one notification
function notificationDelete(id) {
var j = -1, e = Q('notifyx' + id);
if (e != null) {
for (var i in notifications) { if (notifications[i].id == id) { j = i; } }
if (j != -1) {
meshserver.send({ action: 'intersession', subaction: 'removeNotify', id: id }); // Remove the notification in other sessions of the same user.
if (notifications[j].notification) { notifications[j].notification.close(); delete notifications[j].notification; }
notifications.splice(j, 1);
e.parentNode.removeChild(e);
setNotificationCount(notifications.length);
if (notifications.length == 0) { QV('notifiyBox', false); }
if (notifications.length == 1) { QV('notifyRemoveAll', false); }
if ((notifications.length > 0) && (j == 0)) {
var n = notifications[0];
QS('notifyx' + n.id)['border-top'] = '1px solid transparent';
}
}
}
}
// Add a new notification and play the notification sound
function addNotification(n) {
// Perform message translation
var translatedTitles = [
null,
"New Account", // 1
"Server Limit",
"Security Warning",
"Account Settings",
"Device Group",
"Invite Codes"
];
var translatedMessages = [
null,
"Permission denied", // 1
"Invalid username",
"Invalid password",
"Invalid email",
"Invalid domain",
"Invalid site permissions",
"User already exists",
"Unable to add user in this mode",
"Validation exception",
"Account limit reached.", // 10
"Chat Request, Click here to accept.",
"There has been {0} failed login attempts on this account since the last login.",
"Failed to change email address, another account already using: {0}.",
"Email sent.",
"User {0} not found.",
"Users {0} not found.",
"Error, unable to change to previously used password.",
"Error, unable to change to commonly used password.",
"Error, password not changed.",
"Password changed.", // 20
"Current password not correct.",
"Error, invite code \"{0}\" already in use.",
"SMS gateway not enabled",
"No user management rights",
"Invalid SMS message",
"No phone number for this user",
"SMS succesfuly sent.",
"SMS error",
"SMS error: {0}",
"Email domain \"{0}\" is not allowed. Only ({1}) are allowed" // 30
];
if (typeof n.titleid == 'number') { try { n.title = translatedTitles[n.titleid]; } catch (ex) { } }
if (typeof n.msgid == 'number') { try { n.text = translatedMessages[n.msgid]; if (Array.isArray(n.args)) { n.text = format(n.text, n.args[0], n.args[1], n.args[2], n.args[3], n.args[4], n.args[5]); } } catch (ex) { } }
// Show notification within the web page.
if (n.time == null) { n.time = Date.now(); }
if (n.id == null) { n.id = Math.random(); }
notifications.unshift(n);
setNotificationCount(notifications.length);
clickNotificationIcon(true);
var notifySettings = getstore('notifications', 0);
if (notifySettings & 1) { Q('chimes').play(); }
// If web notifications are granted, use it.
var notification = null;
if (Notification && (Notification.permission == 'granted')) {
var text = n.text.split('&reg;').join('').split('<b>').join('').split('</b>').join('').split('<br />').join('\r\n'); // Clean up any HTML codes
if (n.nodeid) {
var node = getNodeFromId(n.nodeid);
if (node) {
if (notifySettings & 16) { // Notify with group name
notification = new Notification(decodeURIComponent('{{{extitle}}}') + ' - ' + meshes[node.meshid].name + ' - ' + node.name, { tag: n.tag, body: text, icon: '/images/notify/icons128-' + node.icon + '.png' });
} else {
notification = new Notification(decodeURIComponent('{{{extitle}}}') + ' - ' + node.name, { tag: n.tag, body: text, icon: '/images/notify/icons128-' + node.icon + '.png' });
}
}
} else {
if (n.icon == null) { n.icon = 0; }
var title = n.title;
if (title == null) { title = ''; } else { title = ' - ' + n.title; }
notification = new Notification(decodeURIComponent('{{{extitle}}}') + title, { tag: n.tag, body: text, icon: '/images/notify/icons128-' + n.icon + '.png' });
}
notification.id = n.id;
notification.xtag = n.tag;
notification.url = n.url;
notification.nodeid = n.nodeid;
notification.username = n.username;
notification.onclick = function (e) { notificationSelected(e.target.id, true); }
n.notification = notification;
}
// If the notification has a max time, setup the timer here.
if ((typeof n.maxtime == 'number') && (n.maxtime > 0)) { var trigger = function notifyRemoveTrigger() { notificationDelete(notifyRemoveTrigger.xid); }; trigger.xid = n.id; setTimeout(trigger, n.maxtime * 1000); }
}
// Remove all notifications
function deleteAllNotifications() {
notifications = [];
setNotificationCount(0);
drawNotifications();
QV('notifiyBox', false);
}
//
// PANELS
//
var xxcurrentView = -1;
function go(x) {
setSessionActivity();
if (xxdialogMode || xxcurrentView == x) return;
updateFooterMenu();
setDialogMode(0);
// Edit this line when adding a new screen
for (var i = 0; i < 32; i++) { QV('p' + i, i == x); }
xxcurrentView = x;
updateCurrentUrl();
}
// Change the URL
function updateCurrentUrl() {
if (((features & 0x10000000) == 0) && (xxcurrentView > 0)) {
var urlviewmode = '';
if ((xxcurrentView >= 10) && (xxcurrentView <= 19)) { // Device Link
if (currentNode != null) { urlviewmode = '?viewmode=' + xxcurrentView + '&gotonode=' + currentNode._id.split('/')[2] + ((currentDevicePanel > 0)?('&panel=' + currentDevicePanel):''); }
} else if ((xxcurrentView >= 20) && (xxcurrentView <= 29)) { // Device Group Link
if (currentMesh != null) { urlviewmode = '?viewmode=' + xxcurrentView + '&gotomesh=' + currentMesh._id.split('/')[2]; }
} else if (xxcurrentView > 1) { urlviewmode = '?viewmode=' + xxcurrentView; }
for (var i in urlargs) { urlviewmode += (((urlviewmode == '') ? '?' : '&') + i + '=' + urlargs[i]); }
try { window.history.replaceState({}, document.title, window.location.pathname + urlviewmode); } catch (ex) { }
}
}
//
// POPUP DIALOG
//
// undefined = Hidden, 1 = Generic Message
var xxdialogMode;
var xxdialogFunc;
var xxdialogButtons;
var xxdialogTag;
// Display a dialog box
// 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)
function setDialogMode(x, y, b, f, c, tag) {
setSessionActivity();
xxdialogMode = x;
xxdialogFunc = f;
xxdialogButtons = b;
xxdialogTag = tag;
QE('idx_dlgOkButton', true);
QV('idx_dlgOkButton', b & 1);
QV('idx_dlgCancelButton', b & 2);
QV('id_dialogclose', (b & 2) || (b & 8));
QV('idx_dlgDeleteButton', b & 4);
QV('idx_dlgButtonBar', b & 7);
if (y) QH('id_dialogtitle', y);
for (var i = 1; i < 24; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
QV('dialog', x);
if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
}
function dialogclose(x) {
setSessionActivity();
var f = xxdialogFunc;
var b = xxdialogButtons;
var t = xxdialogTag;
setDialogMode();
if (((b & 8) || x) && f) f(x, t);
}
//
// Access Control Functions
// These must match server
//
// Remove user rights
function removeUserRights(rights, userid) {
if ((userid != userinfo._id) || (userinfo.removeRights == null)) return rights;
var add = 0, substract = 0;
if ((userinfo.removeRights & 0x00000008) != 0) { substract += 0x00000008; } // No Remote Control
if ((userinfo.removeRights & 0x00010000) != 0) { add += 0x00010000; } // No Desktop
if ((userinfo.removeRights & 0x00000100) != 0) { add += 0x00000100; } // Desktop View Only
if ((userinfo.removeRights & 0x00000200) != 0) { add += 0x00000200; } // No Terminal
if ((userinfo.removeRights & 0x00000400) != 0) { add += 0x00000400; } // No Files
if ((userinfo.removeRights & 0x00000010) != 0) { substract += 0x00000010; } // No Console
if ((userinfo.removeRights & 0x00008000) != 0) { substract += 0x00008000; } // No Uninstall
if ((userinfo.removeRights & 0x00020000) != 0) { substract += 0x00020000; } // No Remote Command
if ((userinfo.removeRights & 0x00000040) != 0) { substract += 0x00000040; } // No Wake
if ((userinfo.removeRights & 0x00040000) != 0) { substract += 0x00040000; } // No Reset/Off
if (rights != 0xFFFFFFFF) {
// If not administrator, add and subsctract restrictions
rights |= add;
rights &= (0xFFFFFFFF - substract);
} else {
// If administrator for a device group, start with permissions and add and subsctract restrictions
rights = 1 + 2 + 4 + 8 + 32 + 64 + 128 + 16384 + 32768 + 131072 + 262144 + 524288 + 1048576;
rights |= add;
rights &= (0xFFFFFFFF - substract);
}
return rights;
}
// Get the right of a user on a given device group
function GetMeshRights(mesh, userid) {
if (mesh == null) { return 0; }
if (userid == null) { userid = userinfo._id; }
if (typeof mesh == 'string') { mesh = meshes[mesh] }
if ((mesh == null) || (mesh.links == null)) { return 0; }
// Check if super user
if (serverinfo.manageAllDeviceGroups && (userid == userinfo._id)) return removeUserRights(0xFFFFFFFF, userid);
// Check device group link permission
var rights = 0, r = mesh.links[userid];
if (r != null) {
if (r.rights == 0xFFFFFFFF) { return removeUserRights(0xFFFFFFFF, userid); } // User has full rights thru a device group link, stop here.
rights = r.rights;
}
// Check permissions thru user groups
var user = null;
if (userid == userinfo._id) { user = userinfo; } else { if (users != null) { user = users[userid]; } }
if (user != null) {
for (var i in user.links) {
if (i.startsWith('ugrp/')) {
r = mesh.links[i];
if (r != null) {
if (r.rights == 0xFFFFFFFF) { return removeUserRights(0xFFFFFFFF, userid); } // User has full rights thru a user group, stop here.
rights |= r.rights; // TODO: Deal with reverse permissions
}
}
}
}
return removeUserRights(rights, userid);
}
// Returns true if the user can view the given device group
function IsMeshViewable(mesh, userid) {
if (mesh == null) { return false; }
if (userid == null) { userid = userinfo._id; }
if (typeof mesh == 'string') { mesh = meshes[mesh] }
if ((mesh == null) || (mesh.links == null)) { return false; }
if (mesh.links[userid] != null) { return true; } // User has visilibity thru a direct link
// Check if user user
if (serverinfo.manageAllDeviceGroups && (userid == userinfo._id)) return true;
// Check permissions thru user groups
var user = null;
if (userid == userinfo._id) { user = userinfo; } else { if (users != null) { user = users[userid]; } }
if (user != null) {
for (var i in user.links) {
if ((i.startsWith('ugrp/')) && (mesh.links[i] != null)) { return true; } // User has visilibity thru a user group
}
}
return false;
}
// Return the user rights for a given node
function GetNodeRights(node, userid) {
if (node == null) { return 0; }
if (userid == null) { userid = userinfo._id; }
if (typeof node == 'string') { node = getNodeFromId(node); if (node == null) { return 0; } }
var r = GetMeshRights(node.meshid, userid);
if (r == 0xFFFFFFFF) return removeUserRights(r, userid);
// Check direct device rights using device data
if ((node.links != null) && (node.links[userid] != null)) { r |= node.links[userid].rights; } // TODO: Deal with reverse permissions
// Check direct device rights thru user groups
if ((node.links != null) && (userinfo.links != null)) {
for (var i in node.links) {
if (i.startsWith('ugrp/') && (userinfo.links[i] != null) && (node.links[i].rights != null)) { r |= node.links[i].rights; }
}
}
// Check direct device rights using user data
/*
var user = null;
if (userid == userinfo._id) { user = userinfo; } else { if (users != null) { user = users[userid]; } }
if ((user != null) && (user.links != null)) {
var r2 = user.links[node._id];
if (r2 != null) {
if (r2.rights == 0xFFFFFFFF) { return 0xFFFFFFFF; } // User has full rights thru a device link, stop here.
r |= r2.rights; // TODO: Deal with reverse permissions
}
}
*/
return removeUserRights(r, userid);
}
// Return true if the device is visible to the user
function IsNodeViewable(node, userid) {
if (node == null) { return false; }
if (userid == null) { userid = userinfo._id; }
if (typeof node == 'string') { node = getNodeFromId(node); if (node == null) { return false; } }
if (IsMeshViewable(node.meshid, userid)) return true;
// Check direct device visibility using device data
if ((node.links != null) && (node.links[userid] != null)) { return true; }
// Check direct device visibility thru user groups
if ((node.links != null) && (userinfo.links != null)) {
for (var i in node.links) { if (i.startsWith('ugrp/') && (userinfo.links[i] != null) && (node.links[i].rights != null)) { return true; } }
}
return false;
}
//
// Generic Methods
//
function nameSort(a, b) { var aa = a.name.toLowerCase(), bb = b.name.toLowerCase(); return sortCollator.compare(aa, bb); }
function getNodeAmtVersion(node) { if ((node == null) || (node.intelamt == null) || (typeof node.intelamt.ver != 'string')) return 0; var verSplit = node.intelamt.ver.split('.'); if (verSplit.length < 2) return 0; return parseInt(verSplit[0]) + (parseInt(verSplit[1]) / 100); }
function putstore(name, val) { try { if ((typeof (localStorage) === 'undefined') || (localStorage.getItem(name) == val)) return; if (val == null) { localStorage.removeItem(name); } else { localStorage.setItem(name, val); } } catch (e) { } if (name[0] != '_') { var s = {}; for (var i = 0, len = localStorage.length; i < len; ++i) { var k = localStorage.key(i); if (k[0] != '_') { s[k] = localStorage.getItem(k); } } meshserver.send({ action: 'userWebState', state: JSON.stringify(s) }); } }
function getstore(name, val) { try { if (typeof (localStorage) === 'undefined') return val; var v = localStorage.getItem(name); if ((v == null) || (v == null)) return val; return v; } catch (e) { return val; } }
function center() { if (xtermfit) xtermfit.fit(); onDevicesScroll(); QS('dialog').left = ((((getDocWidth() - 300) / 2)) + 'px'); deskAdjust(); if (currentNode != null) { drawDeviceTimeline(); } }
function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
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; }
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
function haltReturn(e) { if (e.keyCode == 13) { haltEvent(e); } }
function validateEmail(v) { var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(v); }
function reload() { window.location.href = window.location.href; }
function getNodeFromId(id) { for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; } return null; }
function addHtmlValue(t, v) { return '<table><td style=width:120px>' + t + '<td><b>' + v + '</b></table>'; }
function addHtmlValue2(t, v) { return '<div><div style=display:inline-block;float:right>' + v + '</div><div style=display:inline-block>' + t + '</div></div>'; }
function addHtmlValue4(t, v) { return '<table style=width:100%><td style=width:120px>' + t + '<td style=text-align:right><b>' + v + '</b></table>'; }
function addLink(x, f) { return '<a style=cursor:pointer;text-decoration:none onclick=\'' + f + '\'>&diams; ' + x + '</a>'; }
function addLinkConditional(x, f, c) { if (c) return addLink(x, f); return x; }
function passwordcheck(p) { var re = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()]).{8,}/; return re.test(p); }
function getFileSizeStr(size) { if (typeof size != 'number') { size = 0; } if (size == 1) return "1 byte"; return format('{0} bytes', size); }
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('/'); }
function focusTextBox(x) { setTimeout(function () { Q(x).selectionStart = Q(x).selectionEnd = 65535; Q(x).focus(); }, 0); }
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] != '.'); } })();
function printDate(d) { return d.toLocaleDateString(args.locale); }
function printTime(d) { return d.toLocaleTimeString(args.locale); }
function printDateTime(d) { return d.toLocaleString(args.locale); }
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; }); };
function nobreak(x) { return x.split(' ').join('&nbsp;'); }
function getUserName(userid) { if (users && users[userid] != null) return users[userid].name; return userid.split('/')[2]; }
function addDetailItem(title, value, state) { return '<table style=width:100%><td>' + nobreak(title) + '<td style=text-align:right>' + value + '</table>'; }
function isPrivateIP(a) { return (a.startsWith('10.') || a.startsWith('172.16.') || a.startsWith('192.168.')); }
function encodeURIComponentEx(txt) { return encodeURIComponent(txt).replace(/'/g, '%27'); };
function safeNewWindow(url, target) { var newWindow = window.open(url, target, 'noopener,noreferrer'); if (newWindow) { newWindow.opener = null; } }
</script>
</body>
</html>