<!DOCTYPE html><html lang="de" 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">
    <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">
    <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/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>
        a {
            color: #036;
            text-decoration: underline;
        }

        #footer a {
            color: #fff;
            text-decoration: underline;
        }

            #footer a:hover {
                color: #fff;
                text-decoration: none;
            }

        .i1 {
            background: url(../images/icons50.png) 0px 0px;
            height: 50px;
            width: 50px;
            border: none;
        }

        .i2 {
            background: url(../images/icons50.png) -50px 0px;
            height: 50px;
            width: 50px;
            border: none;
        }

        .i3 {
            background: url(../images/icons50.png) -100px 0px;
            height: 50px;
            width: 50px;
            border: none;
        }

        .i4 {
            background: url(../images/icons50.png) -150px 0px;
            height: 50px;
            width: 50px;
            border: none;
        }

        .i5 {
            background: url(../images/icons50.png) -200px 0px;
            height: 50px;
            width: 50px;
            border: none;
        }

        .i6 {
            background: url(../images/icons50.png) -250px 0px;
            height: 50px;
            width: 50px;
            border: none;
        }

        .i7 {
            background: url(../images/icons50.png) -300px 0px;
            height: 50px;
            width: 50px;
            border: none;
        }

        .i8 {
            background: url(../images/icons50.png) -350px 0px;
            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; }
        .NotifyIconSmall2 { width:24px; height:24px; background: url(../images/notify24.png) -24px 0px; }
        .NotifyIconSmall3 { width:24px; height:24px; background: url(../images/notify24.png) -48px 0px; }
        .NotifyIconSmall4 { width:24px; height:24px; background: url(../images/notify24.png) -72px 0px; }
        .NotifyIconSmall5 { width:24px; height:24px; background: url(../images/notify24.png) -96px 0px; }
        .NotifyIconSmall6 { width:24px; height:24px; background: url(../images/notify24.png) -120px 0px; }
        .NotifyIconSmall7 { width:24px; height:24px; background: url(../images/notify24.png) -144px 0px; }
        .NotifyIconSmall8 { width:24px; height:24px; background: url(../images/notify24.png) -168px 0px; }
        .NotifyIconSmall9 { width:24px; height:24px; background: url(../images/notify24.png) -192px 0px; }

        .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;
            background-color: white;
            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;
        }

        .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; }

        .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;
        }

        .devList4 {
            padding-left: 12px;
            padding-top: 2px;
        }

        .devList5 {
            padding-left: 12px;
            padding-top: 3px;
            color: gray
        }
    </style>
</head>
<body onload="if (typeof(startup) !== 'undefined') startup();" style="overflow-y:hidden;margin:0;padding:0;border:0;color:black;font-size:13px;font-family:\'Trebuchet MS\', Arial, Helvetica, sans-serif">
    <div id="container">
        <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">{{{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">{{{title2}}}</font></strong>
                </div>
            </div>
            <img id="topMenuIcon" class="noselect" style="position:absolute;right:0;top:10px;bottom:50px;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 getrennt</span>, <href onclick="reload()" style="cursor:pointer"><u>zum Wiederverbinden klicken</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:30px" onscroll="onDevicesScroll()" ontouchstart="onDeviceTouch(true)" ontouchend="onDeviceTouch(false)"></div>
                    <div id="xdevicesBar" style="position:absolute;overflow-y:auto;height:30px;left:0;right:0;bottom:0px;background-color:#aaa">
                        <div style="margin:4px">
                            <span style="width:20px;display:inline-block;text-align:center;cursor:pointer" onclick="clearSearchInput()"><b>X</b></span>
                            <input id="SearchInput" type="text" placeholder="Filter" onchange="onDeviceSearchChanged(event)" onkeyup="onDeviceSearchChanged(event)" autocomplete="off">&nbsp;
                            <label class="noselect"><input type="checkbox" id="RealNameCheckBox" onclick="onRealNameCheckBox()">Name im Betriebssystem</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;">
                        <tbody><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 style="background-color:white;width:10px;height:10px;border-radius:10px 0 0 0;border-right:1px solid white;border-bottom:1px solid white"></div>
                                </div>
                                <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">◀</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"><span id="p3userName"></span></strong><br>
                                </div>
                            </td>
                        </tr>
                    </tbody></table>
                    <div id="p3info" style="overflow-y:auto;position:absolute;top:55px;bottom:0px;width:100%">
                        <div style="margin-left:8px">
                            <div id="p3AccountActions">
                                <div id="p2AccountSecurity" style="display:none">
                                    <p><strong>Konto-Sicherheit</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>✓</strong></span></div>
                                        <div id="manageEmail2FA" style="margin-top:5px;display:none"><a onclick="account_manageAuthEmail()" style="cursor:pointer">E-Mail-Authentifizierung verwalten</a> <span id="authEmailSetupCheck"><strong>✓</strong></span></div>
                                        <div id="manageAuthApp" style="margin-top:5px;display:none"><a onclick="account_manageAuthApp()" style="cursor:pointer">Authentifikator-App verwalten</a> <span id="authAppSetupCheck"><strong>✓</strong></span></div>
                                        <div id="manageOtp" style="margin-top:5px;display:none"><a onclick="account_manageOtp(0)" style="cursor:pointer">Sicherungscodes verwalten</a> <span id="authCodesSetupCheck"><strong>✓</strong></span></div>
                                    </div>
                                </div>
                                <div id="p2AccountActions" style="display:none">
                                    <p><strong>Konto-Aktionen</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">E-Mail verifizieren</a></span></div>
                                        <div style="margin-top:5px"><span id="changeEmailId" style="display:none"><a onclick="account_showChangeEmail()" style="cursor:pointer">E-Mail-Adresse ändern</a></span></div>
                                        <div style="margin-top:5px"><a onclick="account_showChangePassword()" style="cursor:pointer">Passwort ändern</a><span id="p2nextPasswordUpdateTime"></span></div>
                                        <div style="margin-top:5px"><a onclick="account_showDeleteAccount()" style="cursor:pointer">Konto löschen</a></div>
                                    </div>
                                <br style="clear:both">
                                </div>
                            </div>
                            <strong>Gerätegruppen</strong>
                            <span id="p3createMeshLink1">( <a onclick="account_createMesh()" style="cursor:pointer"><img src="images/icon-addnew.png" width="12" height="12" border="0"> Neu</a> )</span>
                            <br><br>
                            <div id="p3meshes"></div>
                            <div id="p3noMeshFound" style="margin-left:9px;display:none">Keine Gerätegruppen.<span id="p3createMeshLink2"> <a onclick="account_createMesh()" style="cursor:pointer"><strong>Fang hier an!</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;">
                        <tbody><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 style="background-color:white;width:10px;height:10px;border-radius:10px 0 0 0;border-right:1px solid white;border-bottom:1px solid white"></div>
                                </div>
                                <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">◀</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">Meine Dateien</strong><br>
                                </div>
                            </td>
                        </tr>
                    </tbody></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">
                            <tbody><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="Hoch">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p5SelectAllButton" disabled="disabled" onclick="p5selectallfile()" value="Alle auswählen" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p5RenameFileButton" disabled="disabled" value="Umbenennen" onclick="p5renamefile()" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p5DeleteFileButton" disabled="disabled" value="Löschen" onclick="p5deletefile()" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p5NewFolderButton" disabled="disabled" value="Ordner" 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="Hochladen" onclick="p5uploadFile()" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p5CutButton" disabled="disabled" value="Ausschneiden" onclick="p5copyFile(1)" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p5CopyButton" disabled="disabled" value="Kopie" onclick="p5copyFile(0)" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p5PasteButton" disabled="disabled" value="Einfügen" onclick="p5pasteFile()" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p5RefreshButton" value="Aktualisieren" onclick="p5refreshFiles()" onkeypress="return false" onkeydown="return false">
                                    </div>
                                </td>
                            </tr>
                            <tr>
                                <td style="background-color:#E4E9E7;height:28px">
                                    <table style="width:100%">
                                        <tbody><tr>
                                            <td id="p5currentpath" style="overflow:hidden;padding-left:4px;padding-top:2px"></td>
                                            <td style="text-align:right;padding-right:4px">
                                                <select id="p5sortdropdown" onchange="updateFiles()">
                                                    <option value="1" selected="selected">Nach Name sortieren</option>
                                                    <option value="2">Nach Größe sortieren</option>
                                                    <option value="3">Nach Datum sortieren</option>
                                                    <option value="4">Nach Name absteigend</option>
                                                    <option value="5">Nach Größe absteigend</option>
                                                    <option value="6">Nach Datum absteigend</option>
                                                </select>
                                            </td>
                                        </tr>
                                    </tbody></table>
                                </td>
                            </tr>
                        </tbody></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">
                            <tbody><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>
                        </tbody></table>
                    </div>
                </div>
                <div id="p10" style="display:none;position:absolute;bottom:0;top:0;width:100%;overflow:hidden">
                    <table cellspacing="0" style="margin:0;padding:0;border-spacing:0;border:0;position:absolute;top:0">
                        <tbody><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 style="background-color:white;width:10px;height:10px;border-radius:10px 0 0 0;border-right:1px solid white;border-bottom:1px solid white"></div>
                                </div>
                                <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">◀</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>
                    </tbody></table>
                    <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">
                        </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"></div>
                    </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:25px">
                            <div style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
                                <div style="float:right;text-align:right">
                                    <span id="p14power"></span>&nbsp;
                                    <input id="DeskSoftInput" type="text" style="width:25px;display:none;opacity:.2" onblur="toggleSoftKeys(0)" onkeypress="return ondeskkeypress(event)" onkeydown="return ondeskkeydown(event)" onkeyup="return ondeskkeyup(event)">
                                </div>
                                <div style="margin-left:3px">
                                    <input type="button" id="connectbutton1" value="Verbinden" onclick="connectDesktop(event,1)" onkeypress="return false" onkeydown="return false" disabled="disabled">
                                    <input type="button" id="connectbutton1h" value="HW-Verbindung" onclick="connectDesktop(event,2)" onkeypress="return false" onkeydown="return false" disabled="disabled">
                                    <input type="button" id="disconnectbutton1" value="Trennen" onclick="connectDesktop(event,0)" onkeypress="return false" onkeydown="return false">
                                    <span id="deskstatus">Getrennt</span>
                                </div>
                            </div>
                        </div>
                        <div id="deskarea3" style="position:absolute;top:25px;width:100%;height:calc(100% - 50px)">
                            <div id="deskarea3x" style="background:black;text-align:center;height:100%;position:relative">
                                <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="DeskTools" style="position:absolute;width:400px;height:100%;background-color:gray;top:0;right:0;border-left:2px solid lightgray;display:none">
                                    <a id="DeskToolsRefreshButton" style="float:right;padding:3px;cursor:pointer" onclick="refreshDeskTools()">Aktualisieren</a>
                                    <div id="DeskToolsBar" style="position:absolute;padding:3px;border-radius: 3px 3px 0px 0px;top:5px;left:4px;bottom:26px;background-color:lightgray;cursor:pointer">Prozesse</div>
                                    <div style="position:absolute;top:26px;left:4px;right:4px;bottom:4px;background-color:lightgray;text-align:left">
                                        <div style="border-bottom:1px solid darkgray;padding:3px"><a style="width:50px;padding-right:5px;float:left;cursor:pointer" onclick="sortProcess(0)">PID</a><a style="cursor:pointer" onclick="sortProcess(1)">Name</a></div>
                                        <div id="DeskToolsProcesses" style="overflow-y:scroll;position:absolute;top:24px;bottom:0px;width:100%"></div>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <div id="deskarea4" style="position:absolute;bottom:0px;width:100%;height:25px">
                            <div style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
                                <div style="float:right;text-align:right">
                                    <span id="DeskToastButton"><img src="images/icon-notify.png" onclick="deviceToastFunction()" height="16" width="16" style="padding-top:2px"></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" onkeypress="return false" onkeydown="return false" value="Aktionen" onclick="deviceActionFunction()">
                                    <input type="button" value="Einstellungen" onkeypress="return false" onkeydown="return false" onclick="showDesktopSettings()">
                                    <input type="button" onkeypress="return false" onkeydown="return false" value="Energiezustandsaktionen" onclick="showPowerActionDlg()" style="display:none">
                                    <input type="button" id="DeskSpecialKeys" value="Keys" onkeypress="return false" onkeydown="return false" onclick="sendSpecialKeys()">
                                    <input type="button" id="DeskSoftKeys" value="Tastatur" onkeypress="return false" onkeydown="return false" onclick="toggleSoftKeys(1)">
                                    <input type="button" id="DeskScreens" value="Screens" onkeypress="return false" onkeydown="return false" onclick="deskSelectScreens()" style="display:none">
                                    <label><span id="DeskControlSpan" style="display:none"><input id="DeskControl" type="checkbox" onkeypress="return false" onkeydown="return false">Eingabe</span></label>
                                </div>
                            </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">
                            <tbody><tr>
                                <td style="background-color:#C0C0C0;border-bottom:2px solid black;padding:2px">
                                    <div style="float:right;text-align:right">
                                        <input id="filesActionsBtn" type="button" onkeypress="return false" onkeydown="return false" value="Aktionen" onclick="deviceActionFunction()" style="margin-right:2px">
                                    </div>
                                    <div style="margin-left:2px">
                                        <input id="p13AutoConnect" value="Auto-Verbindung" onclick="autoConnectFiles(event)" onkeypress="return false" onkeydown="return false" type="button" style="display:none">
                                        <input id="p13Connect" value="Verbinden" onclick="connectFiles(event)" onkeypress="return false" onkeydown="return false" type="button">
                                        <span id="p13Status">Getrennt</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="Hoch">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p13SelectAllButton" disabled="disabled" onclick="p13selectallfile()" value="Alle auswählen" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p13RenameFileButton" disabled="disabled" value="Umbenennen" onclick="p13renamefile()" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p13DeleteFileButton" disabled="disabled" value="Löschen" onclick="p13deletefile()" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p13NewFolderButton" disabled="disabled" value="Ordner" 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="Hochladen" onclick="p13uploadFile()" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p13CutButton" disabled="disabled" value="Ausschneiden" onclick="p13copyFile(1)" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p13CopyButton" disabled="disabled" value="Kopie" onclick="p13copyFile(0)" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p13PasteButton" disabled="disabled" value="Einfügen" onclick="p13pasteFile()" onkeypress="return false" onkeydown="return false">
                                        <input type="button" style="width:calc(100%/5 - 5px)" id="p13RefreshButton" disabled="disabled" value="Aktualisieren" onclick="p13folderup(9999)" onkeypress="return false" onkeydown="return false">
                                    </div>
                                </td>
                            </tr>
                            <tr>
                                <td style="background-color:#E4E9E7;height:28px">
                                    <table style="width:100%">
                                        <tbody><tr>
                                            <td id="p13currentpath" style="overflow:hidden;padding-left:4px;padding-top:2px"></td>
                                            <td style="text-align:right;padding-right:4px">
                                                <select id="p13sortdropdown" onchange="p13updateFiles()">
                                                    <option value="1" selected="selected">Nach Name sortieren</option>
                                                    <option value="2">Nach Größe sortieren</option>
                                                    <option value="3">Nach Datum sortieren</option>
                                                    <option value="4">Nach Name absteigend</option>
                                                    <option value="5">Nach Größe absteigend</option>
                                                    <option value="6">Nach Datum absteigend</option>
                                                </select>
                                            </td>
                                        </tr>
                                    </tbody></table>
                                </td>
                            </tr>
                        </tbody></table>
                        <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">
                            <tbody><tr><td style="text-align:left;padding:3px;text-align:center;background-color:#D3D9D6">&nbsp;<span id="p13bottomstatus"></span></td></tr>
                        </tbody></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>
                <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">
                        <tbody><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 style="background-color:white;width:10px;height:10px;border-radius:10px 0 0 0;border-right:1px solid white;border-bottom:1px solid white"></div>
                                </div>
                                <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">◀</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>
                    </tbody></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="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;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">Strg+Alt+Entf</option>
                    <option value="11">Tab</option>
                    <option value="5">Win</option>
                    <option value="0">Win+Runter</option>
                    <option value="1">Win+Hoch</option>
                    <option value="2">Win+L</option>
                    <option value="3">Win+M</option>
                    <option value="4">Umschalten+Win+M</option>
                    <option value="6">Win+R</option>
                    <option value="7">Alt+F4</option>
                    <option value="8">Strg+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">Entfernter Desktop via Agent</h4>
                    <div style="margin:3px 0 3px 0">
                        <select id="d7bitmapquality" style="float:right;width:200px;height:20px" dir="rtl"></select>
                        <div style="height:20px">Qualität</div>
                    </div>
                    <div style="margin:3px 0 3px 0">
                        <select id="d7bitmapscaling" style="float:right;width:200px;height:20px" 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>
                        <div style="height:20px">Skalierung</div>
                    </div>
                    <div style="margin:3px 0 3px 0">
                        <select id="d7framelimiter" style="float:right;width:200px;height:20px" dir="rtl">
                            <option selected="selected" value="50">Schnell</option>
                            <option value="100">Mittel</option>
                            <option value="400">Langsam</option>
                            <option value="1000">Sehr langsam</option>
                        </select>
                        <div style="height:20px">Rate</div>
                    </div>
                </div>
                <div id="d7amtkvm">
                    <h4 style="width:100%;border-bottom:1px solid gray">Intel® AMT Hardware-KVM</h4>
                    <div style="height:26px">
                        <select id="d7desktopmode" style="float:right;width:200px">
                            <option value="1">RLE8, am schnellsten</option>
                            <option value="2">RLE16, empfohlen</option>
                            <option value="3">RAW8, langsam</option>
                            <option value="4">RAW16, sehr langsam</option>
                        </select>
                        <div>Kodierung</div>
                    </div>
                    <div style="height:60px">
                        <div style="float:right;border:1px solid #666;width:200px;height:60px;overflow-y:scroll;background-color:white">
                            <label><input type="checkbox" id="d7showfocus">Fokus-Werkzeug anzeigen</label><br>
                            <label><input type="checkbox" id="d7showcursor">Lokalen Mauszeiger anzeigen</label><br>
                        </div>
                        <div>Andere</div>
                    </div>
                </div>
            </div>
        </div>
        <div id="idx_dlgButtonBar" style="padding:10px;margin-bottom:20px">
            <input id="idx_dlgCancelButton" type="button" value="Abbrechen" 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>
    </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)">Meine Dateien</div>
        <div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer" onclick="topMenu(1)">Mein Konto</div>
        <div id="logoutMenuOption"><a href="/logout"><div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer">Abmelden</div></a></div>
    </div>
    <iframe name="fileUploadFrame" style="display:none"></iframe>
    <script>
        'use strict';

        // 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'); }

        var urlargs = parseUriArgs();
        if (urlargs.key && (isAlphaNumeric(urlargs.key) == false)) { delete urlargs.key; }
        if (urlargs.locale && (isAlphaNumeric(urlargs.locale) == false)) { delete urlargs.locale; }
        var args = urlargs;
        var debugLevel = parseInt('{{{debuglevel}}}');
        var features = parseInt('{{{features}}}');
        var sessionTime = parseInt('{{{sessiontime}}}');
        var sessionRefreshTimer = null;
        var domain = '{{{domain}}}';
        var domainUrl = '{{{domainurl}}}';
        var authCookie = '{{{authCookie}}}';
        var authRelayCookie = '{{{authRelayCookie}}}';
        var authCookieRenewTimer = null;
        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 = ["Getrennt", "Verbinden...", "Aufbau...", "Verbunden", "Intel&reg; AMT verbunden"];
        var agentsStr = ["Unbekannt", "Windows 32bit Konsole", "Windows 64bit Konsole", "Windows 32bit Dienst", "Windows 64bit Dienst", "Linux 32bit", "Linux 64bit", "MIPS", "XENx86", "Android ARM", "Linux ARM", "MacOS 32bit", "Android x86", "PogoPlug ARM", "Android-APK", "Linux Poky x86-32bit", "MacOS 64bit", "ChromeOS", "Linux Poky x86-64bit", "Linux NoKVM x86-32bit", "Linux NoKVM x86-64bit", "Windows MinCore Konsole", "Windows MinCore Dienst", "NodeJS", "ARM-Linaro", "ARMv6l / ARMv7l", "ARMv8 64bit", "ARMv6l / ARMv7l / NoKVM", "MIPS24KC (OpenWRT)", "Unbekannt", "FreeBSD x86-64"];
        var files;
        var passRequirements = '{{{passRequirements}}}';
        if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); }
        var sessionActivity = Date.now();

        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', "Verbinden...");
            go(1);

            // Connect to the mesh server
            meshserver = MeshServerCreateControl(domainUrl, authCookie);
            meshserver.onStateChanged = onStateChanged;
            meshserver.onMessage = onMessage;
            meshserver.Start();

            // Setup stared devices
            try { stars = JSON.parse(getstore('stars', '{}')); } catch (ex) { }

            // Load desktop settings
            var t = localStorage.getItem('desktopsettings');
            if (t != null) { desktopsettings = JSON.parse(t); }
            applyDesktopSettings();

            // Arrange the user interface
            QV('manageEmail2FA', features & 0x00800000);
            QV('managePhoneNumber1', (features & 0x02000000) && (features & 0x04000000));
            QV('managePhoneNumber2', (features & 0x02000000) && !(features & 0x04000000));

            attemptWebRTC = 0; // 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)); }
        }

        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
                if (errorCode == 'noauth') { QH('p0span', "Authentifizierung kann nicht durchgeführt werden"); return; }
                if (prevState == 2) { setTimeout(serverPoll, 5000); } else { QH('p0span', "Web-Socket kann nicht verbunden werden"); }
                // Clean up here
                if (authCookieRenewTimer != null) { clearInterval(authCookieRenewTimer); authCookieRenewTimer = null; }
            } else if (state == 2) {
                // Fetch list of meshes, nodes, files
                meshserver.send({ action: 'usergroups' });
                meshserver.send({ action: 'meshes' });
                meshserver.send({ action: 'nodes' });
                meshserver.send({ action: 'files' });
                if (xxcurrentView < 2) { go(2); }
                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;
            xdr.onload = function () { reload(); };
            xdr.onerror = xdr.ontimeout = function () { setTimeout(serverPoll, 10000); };
            xdr.send();
        }

        function updateSelf() {
            var accountSettingsLocked = false;
            if (userinfo) { accountSettingsLocked = ((userinfo.siteadmin != 0xFFFFFFFF) && ((userinfo.siteadmin & 1024) != 0)); }
            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('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
            QV('manageAuthApp', features & 4096);
            QV('manageOtp', ((features & 4096) != 0) && ((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);

            if (typeof userinfo.passchange == 'number') {
                if (userinfo.passchange == -1) { QH('p2nextPasswordUpdateTime', " - Beim nächsten Anmelden zurücksetzen."); }
                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', " - Beim nächsten Anmelden zurücksetzen."); }
                    else if (seconds < 3600) { var secs = Math.floor(seconds / 60); QH('p2nextPasswordUpdateTime',format((secs == 1)?" - Zurücksetzen in 1 Minute.":" - Zurücksetzen in {0} Minuten.", secs)); }
                    else if (seconds < 86400) { var hours = Math.floor(seconds / 3600); QH('p2nextPasswordUpdateTime', format((hours == 1) ? " - Zurücksetzen in 1 Stunde." : " - Zurücksetzen in {0} Stunden.", hours)); }
                    else { var days = Math.floor(seconds / 86400); QH('p2nextPasswordUpdateTime', format((hours == 1) ? " - Zurücksetzen in 1 Tag." : " - Zurücksetzen in {0} Tagen.", 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();
                    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();
                    updateDevices();
                    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); } }

                    //onSortSelectChange();
                    //onSearchInputChanged();
                    updateDevices();
                    //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
                    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 'getnetworkinfo': {
                    if (currentNode._id != message.nodeid) return;
                    updateDeviceDetails(getNodeFromId(message.nodeid), null, message);
                    break;
                }
                case 'otpauth-request': {
                    if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-request')) {
                        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("Installiere <a href=\"https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2\" rel=\"noreferrer noopener\" target=_blank>Google Authenticator</a> oder eine kompatible Applikation und verwende <a href=\"{0}\" rel=\"noreferrer noopener\" target=_blank>diesen Link</a> oder gebe das Geheimnis manuell unten ein. Anschließend gebe den aktuellen 6-stelligen Token nachfolgend ein, um die Zweifaktor-Anmeldung zu aktivieren.", 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, "Authentifikator-Anwendung", 1, null, message.success ? "<b style=color:green>Aktivierung der Zweifaktor-Anmeldung erfolgreich</b>. Sie werden nun ein gültiges Token benötigen, um sich erneut einzuloggen." : "<b style=color:red>Aktivierung der Zweifaktor-Anmeldung fehlgeschlagen</b>. Entfernen Sie das Geheimnis aus der Applikation und versuchen Sie es erneut. Sie haben nur wenige Minuten Zeit, um einen gültigen Code einzugeben.");
                    break;
                }
                case 'otpauth-clear': {
                    if (xxdialogMode) return;
                    setDialogMode(2, "Authentifikator-Anwendung", 1, null, message.success ? "<b style=color:green>Aktivierung der Zweifaktor-Anmeldung entfernt</b>. Sie können dieses Merkmal jederzeit reaktivieren." : "<b style=color:red>Entfernen der Aktivierung der Zweifaktor-Anmeldung fehlgeschlagen</b>. Versuchen Sie es erneut.");
                    break;
                }
                case 'otpauth-getpasswords': {
                    if (xxdialogMode) return;
                    var x = "Einmal-Token können als sekundäre Authentifizierung verwendet werden. Erstellen Sie ein Set, drucken Sie es aus und bewahren Sie es an einem sicheren Ort auf.";
                    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>' + "Keine aktiven Token";
                    }
                    x += '</table></div></div><br />';
                    x += '<div><input type=button value=\'' + "Schließen" + '\' onclick=setDialogMode(0) style=float:right></input>';
                    x += '<input type=button value=\'' + "Neue Token" + '\' onclick=\'account_manageOtp(1);\'></input>';
                    if (message.passwords != null) { x += '<input type=button value=\'' + "Leeren" + '\' onclick=\'account_manageOtp(2);\'></input>'; }
                    x += '</div><br />';
                    setDialogMode(2, "Sicherungscodes verwalten", 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 '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 '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; }
                                    updateDevices();
                                    updateMeshes();
                                } else if (webstate.stars != null) {
                                    updateDevices();
                                    if (Q('SearchInput').value == '*') { onSearchInputChanged(); }
                                }
                                if (currentNode) { refreshDevice(currentNode._id); }
                            }
                            break;
                        }
                        case 'accountchange': {
                            // An account was created or changed
                            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' }); }
                            }
                            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;
                            }
                            //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' });
                            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.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] = { _id: message.event.meshid, name: message.event.name, mtype: message.event.mtype, desc: message.event.desc, links: message.event.links };
                                updateMeshes();
                                updateDevices();
                                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 };
                                    meshserver.send({ action: 'nodes' }); // 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;

                                // 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; }
                                }
                            }
                            updateMeshes();
                            updateDevices();
                            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;
                            updateDevices();

                            // 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();
                            updateDevices();
                            //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);
                                updateDevices();
                                //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.userloc = message.event.node.userloc;
                                node.rdpport = message.event.node.rdpport;
                                node.consent = message.event.node.consent;
                                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.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; }
                                }
                                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);
                                //updateMapMarkers();
                                updateDevices();

                                //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():'*';
                                }
                                updateDevices();
                                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);
                                updateDevices();
                            }
                            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; }

                                updateDevices();
                                refreshDevice(node._id);
                                //updateMapMarkers();
                            }
                            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 };
                            //if (message.event.tag != null) { n.tag = message.event.tag; }
                            //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 '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; }
                            }

                            updateDevices();
                            refreshDevice(message.event.nodeid);

                            //mainUpdate(4);
                            //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); }

                            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;
            }
        }

        //
        // 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_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, "E-Mail-Authentifikation", 1, function () {
                if (emailU2Fenabled != Q('email2facheck').checked) { meshserver.send({ action: 'otpemail', enabled: Q('email2facheck').checked }); }
            }, "Wenn diese Option aktiviert ist, erhalten Sie bei jeder Anmeldung die Option, ein Anmeldetoken für zusätzliche Sicherheit an Ihr E-Mail-Konto zu senden." + '<br /><br /><label><input id=email2facheck type=checkbox ' + (emailU2Fenabled ? 'checked' : '') + '/>' + "Aktivieren Sie die E-Mail-Zwei-Faktor-Authentifizierung." + '</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, "Authentifikator-Anwendung", 2, function () { meshserver.send({ action: 'otpauth-setup', secret: Q('d2optsecret').attributes.secret.value, token: Q('d2otpauthinput').value }); }, '<div id=d2optinfo>' + "Laden..." + '</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, "Authentifikator-Anwendung", 3, function () { meshserver.send({ action: 'otpauth-clear' }); }, "Bestätigen Sie das Entfernen der Zweifaktor-Anmeldung mittels Authentifikator-Applikation?");
        }

        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 = "Auf OK klicken, um eine Bestätigungsmail an folgende Adresse zu senden:" + '<br /><div style=padding:8px><b>' + EscapeHtml(userinfo.email) + '</b></div>' + "Bitte warten Sie einige Minuten, um die Bestätigung zu erhalten.";
            setDialogMode(2, "E-Mail-Verifizierung", 3, account_showVerifyEmailEx, x);
        }

        function account_showVerifyEmailEx() {
            meshserver.send({ action: 'verifyemail', email: userinfo.email });
        }

        function account_showChangeEmail() {
            if (xxdialogMode) return;
            var x = addHtmlValue("E-Mail", '<input id=dp3email style=width:170px maxlength=256 onchange=account_validateEmail() onkeyup=account_validateEmail(event) />');
            setDialogMode(2, "E-Mail-Adresse ändern", 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>' + "Passwort:" + '</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateDeleteAccount() onkeyup=account_validateDeleteAccount() /></td>';
            x += '</tr><tr><td align=right>' + "Passwort:" + '</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="' + "Abbrechen" + '" 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, "Konto löschen", 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("Altes Passwort:") + '</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("Neues Passwort:") + '</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("Neues Passwort:") + '</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>' + "Passwort-Tipp:" + '</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("Voraussetzungen: {0}.", r.join(', ')) + '</span>'; }
            }
            x += '<br />';
            setDialogMode(2, "Passwort ändern", 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, "Neue Gerätegruppe", 1, null, "Dieses Konto besitzt keine Rechte um eine neue Gerätegruppe zu erzeugen."); return; }

            // Remind the user to verify the email address
            if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Konto-Sicherheit", 1, null, "Auf Geräte kann nicht zugegriffen werden, bis die E-Mail-Adresse verifiziert wurde. Dies wird für eine Passwort-Wiederherstellung benötigt. Gehen Sie auf \"Mein Konto\", um Ihre E-Mail-Adresse zu ändern und zu verifizieren."); 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, "Konto-Sicherheit", 1, null, "Auf Geräte kann nicht zugegriffen werden, bis Zweifaktor-Authentifizierung eingeschaltet wurde. Dies wird als Sicherheitsmaßnahme vorausgesetzt. Gehen Sie auf \"Mein Konto\" und sehen Sie sich die \"Kontosicherheit\"-Sektion an."); 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("Tippen", '<div style=width:170px;margin:0;padding:0><select id=dp3meshtype style=width:100% onchange=account_validateMeshCreate() ><option value=2>' + "Software-Agenten-Gruppe" + '</option><option value=1>' + "Nur Intel&reg; AMT" + '</option></select></div>');
            x += addHtmlValue("Beschreibung", '<div style=width:170px;margin:0;padding:0><textarea id=dp3meshdesc maxlength=1024 style=width:100%;resize:none></textarea></div>');
            setDialogMode(2, "Gerätegruppe erstellen", 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: 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>' + "Richtlinie" + '<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 = "Partielle Rechte";
                if (meshrights == 0xFFFFFFFF) rights = "Gesamtadministrator"; else if (meshrights == 0) rights = "Keine Berechtigungen";

                // 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 style="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">';
                r += '<div><div style=padding-left:12px;padding-top:2px><b>' + EscapeHtml(meshes[i].name) + '</b></div><div style=padding-left:12px;padding-top:3px;color:gray>' + 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 onclick=p5folderup(0)>' + "Wurzel" + '</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 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;
                shortname = name;
                if (name.length > 40) { shortname = EscapeHtml(name.substring(0, 40)) + "..."; } else { shortname = EscapeHtml(name); }
                name = 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=\'' + 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 "Speicher überschritten";
            if (bytes < 2048) return format("{0}B verbleibend", bytes);
            if (bytes < 2097152) return format("{0}k verbleibend", Math.round(bytes / 1024));
            if (bytes < 2147483648) return format("{0}M verbleibend", Math.round(bytes / 1024 / 1024));
            return format("{0}G verbleibend", 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, "Öffentlicher 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 ? "Kein(e)" : "Alle");
            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, "Neuer Ordner", 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>' + "Rekursiv Löschen" + '</label><br>' : '<input type=checkbox id=p5recdeleteinput style=\'display:none\'>'; setDialogMode(2, "Löschen", 3, p5deletefileEx, (cc > 1) ? (format("{0} ausgewählte Elemente löschen?", cc) + rec) : ("Ausgewähltes Element löschen?" + 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, "Umbenennen", 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, "Datei hochladen", 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("Bestätigen Sie das {0} von {1} Einträgen{2} an diesen Ort?", (p5clipboardCut == 0 ? 'copy' : 'move'), p5clipboard.length, ((p5clipboard.length > 1) ? 's' : '')) } setDialogMode(2, "Einfügen", 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("Halte {0} Einträge{1} zum {2}", p5clipboard.length, ((p5clipboard.length > 1) ? 's' : ''), (p5clipboardCut == 0 ? "kopieren" : "verschieben")) + ', <a href=# onclick="return p5clearClip()" style=cursor:pointer>' + "Leeren" + '</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);
            updateDevices();
        }

        function onOnlineCheckBox(e) {
            putstore('onlineOnly', Q('OnlineCheckBox').checked ? 1 : 0);
            onSearchInputChanged();
        }

        function onDeviceSearchChanged(e) {
            onSearchInputChanged();
        }

        function clearSearchInput() {
            Q('SearchInput').value = '';
            onSearchInputChanged();
        }

        function onSearchInputChanged() {
            var x = Q('SearchInput').value.toLowerCase().trim(); putstore('_search', Q('SearchInput').value);
            var userSearch = null, ipSearch = null, groupSearch = null, tagSearch = null;
            if (x.startsWith("Benutzer:".toLowerCase())) { userSearch = x.substring("Benutzer:".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("gruppe:".toLowerCase())) { groupSearch = x.substring("gruppe:".length); }
            else if (x.startsWith("g:".toLowerCase())) { groupSearch = x.substring("g:".length); }
            else if (x.startsWith("Etikett:".toLowerCase())) { tagSearch = Q('SearchInput').value.trim().substring("Etikett:".length); }
            else if (x.startsWith("t:".toLowerCase())) { tagSearch = Q('SearchInput').value.trim().substring("t:".length); }

            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 (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 (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) {
                        nodes[d].v = (rx.test(nodes[d].name.toLowerCase())) || (nodes[d].rnamel != null && rx.test(nodes[d].rnamel.toLowerCase()));
                        if ((nodes[d].v == false) && nodes[d].tags) {
                            for (var s in nodes[d].tags) {
                                if (rx.test(nodes[d].tags[s].toLowerCase())) {
                                    nodes[d].v = true;
                                    break;
                                } else {
                                    nodes[d].v = false;
                                }
                            }
                        }
                    }
                } 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; } } }
            updateDevices();
        }

        function ondeskkeypress(e) {
            toggleSoftKeys(0);
            Q('DeskSoftInput').value = '';
            setSessionActivity();
            if (desktop && !xxdialogMode && xxcurrentView == 10) {
                // Check what keys we are allows to send
                if (currentNode != null) {
                    var meshrights = GetMeshRights(currentNode.meshid);
                    var inputAllowed = ((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);
            }
        }

        function ondeskkeydown(e) {
            toggleSoftKeys(0);
            Q('DeskSoftInput').value = '';
            setSessionActivity();
            if (desktop && !xxdialogMode && xxcurrentView == 10) {
                // Check what keys we are allows to send
                if (currentNode != null) {
                    var meshrights = GetMeshRights(currentNode.meshid);
                    var inputAllowed = ((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);
            }
        }

        function ondeskkeyup(e) {
            toggleSoftKeys(0);
            Q('DeskSoftInput').value = '';
            setSessionActivity();
            if (desktop && !xxdialogMode && xxcurrentView == 10) {
                // Check what keys we are allows to send
                if (currentNode != null) {
                    var meshrights = GetMeshRights(currentNode.meshid);
                    var inputAllowed = ((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.handleKeyUp(e);
            }
        }

        // Since the update device call can be quite frequent, we can moderate it and only call it at most 5 times a second.
        var updateDevicesTimer = null;
        function updateDevices() { if (updateDevicesTimer != null) return; updateDevicesTimer = setTimeout(updateDevicesEx, 200); }

        var sort = 0;
        var deviceHeaderId = 0;
        var deviceHeaderCount;
        var deviceHeaders = {};
        var showRealNames = false;
        var deviceHeaderTotal = 0;
        var deviceHeaders = {};
        var deviceHeadersTitles = {};
        function updateDevicesEx() {
            if (updateDevicesTimer != null) { clearTimeout(updateDevicesTimer); updateDevicesTimer = null; }
            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>' + ", nur Intel&reg; AMT" + '</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>' + "Einzelne Geräte" + '</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 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">' + "Keine Geräte, die auf diese Suche passen." + '</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>' + "Keine Intel&reg;-AMT-Geräte in dieser Gruppe"; }
                        if (mesh.mtype == 2) { r += '<div style=padding:10px><i>' + "Keine Geräte in dieser Gruppe"; }
                        r += '.</i></div></div>';
                        current = mesh._id;
                        count++;
                    }
                }
            }

            if (count == 0) {
                QH('xdevices', '<div style="margin-top:50px;text-align:center"><span style="font-size:30px">' + "Keine Geräte" + '</span><br /><br />' + "Verwenden Sie die Desktop-Version dieser Website, um Geräte hinzuzufügen." + '</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) { clearTimeout(onDevicesScrollnagleTimer); onDevicesScrollnagleTimer = null; }
            if (onDevicesTouchActive) return;
            onDevicesScrollnagleTimer = setTimeout(onDevicesScrollEx, 50);
        }

        function onDeviceTouch(x) {
            if (onDevicesTouchActive == x) return;
            onDevicesTouchActive = x;
            if (x == false) onDevicesScrollEx();
        }

        function onDevicesScrollEx() {
            onDevicesScrollnagleTimer = null;
            var visibleTop = Q('xdevices').scrollTop - 250, visibleBottom = Q('xdevices').scrollTop + Q('xdevices').clientHeight + 250, devdivs = document.getElementsByName('xxdevice');
            for (var i = 0; i < devdivs.length; i++) {
                if ((devdivs[i].offsetTop >= visibleTop) && (devdivs[i].offsetTop < visibleBottom)) {
                    // Show
                    var node = getNodeFromId(devdivs[i].id)
                    if (node == null) break;

                    var title = EscapeHtml(node.name);
                    if (title.length == 0) { title = '<i>' + "Kein(e)" + '</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>' + "Kein(e)" + '</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>';
                        }

                        // 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)) { icon += ' gray'; }
                    devdivs[i].innerHTML = '<div onclick=goForward(\'' + node._id + '\')>' + 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 {
                    devdivs[i].innerHTML = ''; // Hide
                }
            }
        }

        // Show currently active sessions on this device
        function showDeviceSessions() {
            if (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) { x += addHtmlValue4(getUserName(j), (node.sessions.kvm[j] == 1) ? "1 Sitzung" : nobreak(format("{0} Sitzungen", node.sessions.kvm[j]))); }
                } else if (i == 'multidesk') {
                    x += '<u>' + "Remote Desktop" + '</u>';
                    for (var j in node.sessions.multidesk) { x += addHtmlValue4(getUserName(j), ((node.sessions.multidesk[j] == 1) ? "1 Sitzung" : nobreak(format("{0} Sitzungen", node.sessions.multidesk[j])))); }
                } else if (i == 'terminal') {
                    x += '<u>' + "Terminal" + '</u>';
                    for (var j in node.sessions.terminal) { x += addHtmlValue4(getUserName(j), ((node.sessions.terminal[j] == 1) ? "1 Sitzung" : nobreak(format("{0} Sitzungen", node.sessions.terminal[j])))); }
                } else if (i == 'files') {
                    x += '<u>' + "Dateien" + '</u>';
                    for (var j in node.sessions.files) { x += addHtmlValue4(getUserName(j), ((node.sessions.files[j] == 1) ? "1 Sitzung" : nobreak(format("{0} Sitzungen", node.sessions.files[j])))); }
                } else if (i == 'tcp') {
                    x += '<u>' + "TCP Routing" + '</u>';
                    for (var j in node.sessions.tcp) { x += addHtmlValue4(getUserName(j), ((node.sessions.tcp[j] == 1) ? "1 Sitzung" : nobreak(format("{0} Sitzungen", node.sessions.tcp[j])))); }
                } else if (i == 'udp') {
                    x += '<u>' + "UDP Routing" + '</u>';
                    for (var j in node.sessions.udp) { x += addHtmlValue4(getUserName(j), ((node.sessions.udp[j] == 1) ? "1 Sitzung" : nobreak(format("{0} Sitzungen", node.sessions.udp[j])))); }
                }
            }
            if (x != '') setDialogMode(2, "Sessions" + ' - ' + EscapeHtml(node.name), 1, null, x, 'SESSIONS-' + node._id);
        }

        // 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 = ['', "eingeschaltet", "Schlafen", "Schlafen", "Schlafen", "Ruhezustand", "Ausschalten", "präsent"];
        var powerStateStrings = ['', "eingeschaltet", "Schlafend", "Schlafend", "Tiefschlaf", "Ruhezustand", "Soft-Off", "präsent"];
        var powerStateStrings2 = ['', "Gerät ist eingeschaltet", "Gerät befindet sich im Schlafzustand (S1)", "Gerät befindet sich im Schlafzustand (S2)", "Gerät befindet sich im Tiefschlafzustand (S3)", "Gerät ist im Ruhezustand (S4)", "Gerät ist im Soft-Off-Zustand (S5)", "Gerät ist präsent, aber der Energiezustand kann nicht ermittelt werden"];
        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>' + "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>' + "Weiterleitung" + '</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 "Unbekannt";
        }

        function onSortSelectChange(skipsave) {
            sort = document.getElementById('sortselect').selectedIndex;
            if (!skipsave) { putstore('sort', sort); }
            updateDevicesEx();
        }

        function deviceHeaderSet() {
            if (deviceHeaderId == 0) { deviceHeaderId = 1; return; }
            deviceHeaders['DevxHeader' + deviceHeaderId] = ', ' + deviceHeaderTotal + ((deviceHeaderTotal == 1) ? " Knoten" : " Knoten");
            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; }

        //
        // 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, "Konto-Sicherheit", 1, null, "Auf Geräte kann nicht zugegriffen werden, bis die E-Mail-Adresse verifiziert wurde. Dies wird für eine Passwort-Wiederherstellung benötigt. Gehen Sie auf \"Mein Konto\", um Ihre E-Mail-Adresse zu ändern und zu verifizieren."); 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, "Konto-Sicherheit", 1, null, "Auf Geräte kann nicht zugegriffen werden, bis Zweifaktor-Authentifizierung eingeschaltet wurde. Dies wird als Sicherheitsmaßnahme vorausgesetzt. Gehen Sie auf \"Mein Konto\" und sehen Sie sich die \"Kontosicherheit\"-Sektion an."); return; }

            var node = getNodeFromId(nodeid);
            if (node == null) { goBack(); return; }
            var mesh = meshes[node.meshid];
            var meshrights = GetNodeRights(node);
            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);
                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>' + "Kein(e)" + '</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>' + "Gruppe" + '</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) { // 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>' + "Kein(e)" + '</i></span>');
                        }
                    } else {
                        x += addDeviceAttribute("Hostname", EscapeHtml(node.host));
                    }
                }

                // Attribute: Description
                var description = node.desc ? EscapeHtml(node.desc) : '<i>' + "Kein(e)" + '</i>';
                if ((meshrights & 4) != 0) {
                    x += addDeviceAttribute("Beschreibung", '<span onclick=showEditNodeValueDialog(2) style=cursor:pointer>' + description + '</span>');
                } else {
                    x += addDeviceAttribute("Beschreibung", description);
                }

                // Attribute: Mesh Agent
                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; }
                    x += addDeviceAttribute("Agent", str);
                }

                // Attribute: Intel AMT
                if (node.intelamt != null) {
                    var str = '';
                    var provisioningStates = { 0: nobreak("Nicht aktiviert (Pre)"), 1: nobreak("Nicht aktiviert (In)"), 2: nobreak("Aktiviert") };
                    if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>' + nobreak("Unbekannter Zustand") + '</i>, v' + EscapeHtml(node.intelamt.ver); } else

                        if ((node.intelamt.ver == null) && (node.intelamt.state == 2)) { str += '<i>' + "Aktiviert" + '</i>'; }
                        else if ((node.intelamt.ver == null) || (node.intelamt.state == null)) { str += '<i>' + "Unbekannte Version & Zustand" + '</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 (node.intelamt.tls == 1) { str += ', <span>' + "TLS" + '</span>'; }
                    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("Keine Zugangsdaten") + '</i>';
                            } else {
                                str += ', <i style=color:#FF0000>' + "Keine Zugangsdaten" + '</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-Markierung", 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>' + "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-Weiterleitung" + '</span>');
                    if ((node.conn & 16) != 0) cstate.push('<span>' + "MQTT" + '</span>');
                    x += addDeviceAttribute("Konnektivität", cstate.join(', '));
                }

                // Node tags
                var groupingTags = '<i>' + "Kein(e)" + '</i>';
                if (node.tags != null) { groupingTags = ''; for (var i in node.tags) { groupingTags += '<span style="background-color:lightgray;padding:3px;margin-right:4px;border-radius:5px">' + EscapeHtml(node.tags[i]) + '</span>'; } }
                if ((meshrights & 4) != 0) {
                    x += addDeviceAttribute("Tags", '<span onclick=showEditNodeValueDialog(3) style=cursor:pointer>' + groupingTags + '</span>');
                } else {
                    x += addDeviceAttribute("Tags", groupingTags);
                }

                x += '</table><br />';
                // Show action button, only show if we have permissions 4, 8, 64
                if ((meshrights & 76) != 0) { x += '<input type=button value=\"' + "Aktionen" + '\" 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() />';  }
                QH('p10html', x);

                // Show node last 7 days timeline
                //drawDeviceTimeline();
                setupFiles();

                // 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 + '")>' + "Gerät löschen" + '</a>'; }
                x += '</div><div style=font-size:x-small>';
                //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 += '<span style=font-size:10px>' + "Mesh-Agent" + '</span>'; }
                if ((connectivity & 2) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += '<span style=font-size:10px>' + "Intel&reg; AMT verbunden" + '</span>'; }
                else if ((connectivity & 4) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += '<span style=font-size:10px>' + "Intel&reg; AMT erkannt" + '</span>'; }
                if ((connectivity & 16) != 0) { if (powerstate.length > 0) { powerstate += '<br/>'; } powerstate += '<span style=font-size:12px>' + "MQTT-Kanal verbunden" + '</span>'; }
                QH('MainComputerState', powerstate);

                // 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: 'getsysinfo', nodeid: currentNode._id });
                    meshserver.send({ action: 'getnetworkinfo', nodeid: currentNode._id });
                    QH('p10detailshtml', '');
                }
            }
            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 deviceToastFunction() {
            if (xxdialogMode) return;
            setDialogMode(2, "Geräte-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);
            var menus = [];
            if (currentDevicePanel != 0) { menus.push({ n: "Allgemein", f: 'setupDeviceMenu(0)' }); }

            if ((currentDevicePanel != 1) &&
                (currentNode != null) &&
                ((meshrights & 8) || (meshrights & 256)) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 65536) == 0)) &&
                (((currentNode.agent == null) && ((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 != 2) && (currentNode != null) && (meshrights & 8) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 1024) == 0)) && ((currentNode.mtype == 2) && (currentNode.agent.caps & 4))) { menus.push({ n: "Dateien", f: 'setupDeviceMenu(2)' }); }
            if ((currentDevicePanel != 3) && (currentNode != null)) { menus.push({ n: "Details", f: 'setupDeviceMenu(3)' }); }
            updateFooterMenu(menus);
        }

        function deviceActionFunction() {
            if (xxdialogMode) return;
            var meshrights = GetNodeRights(currentNode);
            var x = "Wählen Sie eine Operation für die Durchführung auf diesem Gerät." + '<br /><br />';
            var y = '<select id=d2deviceop style=float:right;width:170px>';
            if ((meshrights & 64) != 0) { y += '<option value=100>' + "Aufwecken" + '</option>'; } // Wake-up permission
            if ((meshrights & 8) != 0) { y += '<option value=4>' + "Schlafen" + '</option><option value=3>' + "Zurücksetzen" + '</option><option value=2>' + "Ausschalten" + '</option>'; } // Remote control permission
            y += '</select>';
            x += addHtmlValue("Vorgang", y);
            setDialogMode(2, "Geräteaktion", 3, deviceActionFunctionEx, x);
        }

        function deviceActionFunctionEx() {
            var op = Q('d2deviceop').value;
            if (op == 100) {
                // Device wake
                meshserver.send({ action: 'wakedevices', nodeids: [currentNode._id] });
            } else {
                // Power operation
                meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: op });
            }
        }

        // Look to see if we need to update the device timeline
        function updateDeviceTimeline() {
            if ((meshserver.State != 2) || (powerTimelineNode == null) || (powerTimelineUpdate == null) || (currentNode == null)) 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() {
            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>' + "Tag" + '</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("Benutzername", '<input id=dp10username style=width:170px maxlength=32 autocomplete=nope placeholder="admin" onchange=validateDeviceAmtSettings() onkeyup=validateDeviceAmtSettings() />');
            x += addHtmlValue("Passwort", '<input id=dp10password type=password style=width:170px autocomplete=nope maxlength=32 onchange=validateDeviceAmtSettings() onkeyup=validateDeviceAmtSettings() />');
            x += addHtmlValue("Sicherheit", '<select id=dp10tls style=width:176px><option value=0>' + "Keine TLS-Sicherheit" + '</option><option value=1>' + "TLS-Sicherheit vorausgesetzt" + '</option></select>');
            if ((node.intelamt.user != null) && (node.intelamt.user != '')) { buttons = 7; }
            setDialogMode(2, "Zugangsdaten für Intel&reg; AMT bearbeiten", 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'; }
            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 = '';
                meshserver.send({ action: 'changedevice', nodeid: tag.node._id, intelamt: { user: amtuser, pass: amtpass, tls: parseInt(Q('dp10tls').value) } });
                tag.node.intelamt.user = amtuser;
                tag.node.intelamt.tls = parseInt(Q('dp10tls').value);
                if (tag.func) { setTimeout(tag.func, 300); }
            }
        }

        function p10showDeleteNodeDialog(nodeid) {
            if (xxdialogMode) return;
            setDialogMode(2, "Knoten löschen", 3, p10showDeleteNodeDialogEx, format("{0} löschen?", EscapeHtml(currentNode.name)) + '<br /><br /><label><input id=p10check type=checkbox onchange=p10validateDeleteNodeDialog() />' + "Bestätigen" + '</label>', nodeid);
            p10validateDeleteNodeDialog();
        }

        function p10validateDeleteNodeDialog() {
            QE('idx_dlgOkButton', Q('p10check').checked);
        }

        function p10showDeleteNodeDialogEx(buttons, nodeid) {
            meshserver.send({ action: 'removedevices', nodeids: [nodeid] });
        }

        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-Auswahl", 0, null, x);
            QV('id_dialogclose', true);
        }

        function p10setIcon(icon) {
            setDialogMode(0);
            meshserver.send({ action: 'changedevice', nodeid: currentNode._id, icon: icon });
        }

        var showEditNodeValueDialog_modes = ["Gerätename", "Hostname", "Beschreibung", "Tags"];
        var showEditNodeValueDialog_modes2 = ['name', 'host', 'desc', 'tags'];
        var showEditNodeValueDialog_modes3 = ['', '', '', "Gruppe1, Gruppe2, Gruppe3"];
        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) />');
            setDialogMode(2, "Gerät bearbeiten", 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 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 };
        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));
            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 && ((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);
        }

        function connectDesktop(e, contype) {
            setSessionActivity();
            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 = desktopsettings.showmouse;
                    desktop.m.onScreenSizeChange = deskAdjust;
                    desktop.Start(desktopNode._id, 16994, '*', '*', 0);
                    desktop.contype = 2;
                } else {
                    // 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.onStateChanged = onDesktopStateChange;
                    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 = deskAdjust;
                    desktop.Start(desktopNode._id);
                    desktop.contype = 1;
                }
            } else {
                // Disconnect and clean up the remote desktop
                desktop.Stop();
                desktopNode = desktop = null;
            }
        }

        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, "Einstellungen des entfernten Desktops", 3, showDesktopSettingsChanged);
        }

        function showDesktopSettingsChanged() {
            desktopsettings.encoding = d7desktopmode.value;
            desktopsettings.showfocus = d7showfocus.checked;
            desktopsettings.showmouse = d7showcursor.checked;
            desktopsettings.quality = d7bitmapquality.value;
            desktopsettings.scaling = d7bitmapscaling.value;
            desktopsettings.framerate = d7framelimiter.value;
            localStorage.setItem('desktopsettings', JSON.stringify(desktopsettings));
            applyDesktopSettings();
            if (desktop) {
                if (desktop.contype == 1) {
                    if (desktop.State != 0) { desktop.m.SendCompressionLevel(1, desktopsettings.quality, desktopsettings.scaling, desktopsettings.framerate); }
                }
                if (desktop.contype == 2) {
                    if (desktop.State != 0) { desktop.Stop(); setTimeout(function () { connectDesktop(null, 2); }, 50); }
                }
            }
        }

        function applyDesktopSettings() {
            var r = '', ops = (features & 512) ? [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;
            d7showfocus.checked = desktopsettings.showfocus;
            d7showcursor.checked = desktopsettings.showmouse;
            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; }
        }

        var fullscreen = false;
        /*
        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);
            if (fullscreen) {
                QS('container').width = '100%';
                QS('container')['border-right'] = '0';
                QS('container')['border-left'] = '0';
                QS('column_l').padding = '0';
                QS('column_l').width = '100%';
            } else {
                QS('container').width = '960px';
                QS('container')['border-right'] = '1px solid #b7b7b7';
                QS('container')['border-left'] = '1px solid #b7b7b7';
                QS('column_l').padding = '0 15px';
                QS('column_l').width = '930px';
                toggleFullScreen();
            }
            deskAdjust();
        }
        */

        function deskAdjust() {
            var x = (Q('DeskParent').clientHeight - Q('Desk').clientHeight) / 2;
            if (x < 0) {
                var mh = Q('DeskParent').clientHeight, mw = 9999;
                if (desktop) { mw = (desktop.m.width / desktop.m.height) * mh; }
                QS('Desk')['max-height'] = mh + 'px';
                QS('Desk')['max-width'] = mw + 'px';
                x = 0;
            } else {
                QS('Desk')['max-height'] = null;
                QS('Desk')['max-width'] = null;
            }
            QS('Desk')['margin-top'] = x + 'px';
            QS('Desk')['margin-bottom'] = x + 'px';
        }

        // Remote desktop special key combos for Windows
        function deskSendKeys() {
            if (xxdialogMode || desktop == null || desktop.State != 3) return;
            var ks = Q('deskkeys').value;
            if (ks == 0) { // WIN+Down arrow
                if (desktop.contype == 2) {
                    desktop.m.sendkey([[0xffe7, 1], [0xff54, 1], [0xff54, 0], [0xffe7, 0]]); // Intel AMT: Meta-left down, Down arrow press, Down arrow release, Meta-left release
                } else {
                    desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.DOWN, 40], [desktop.m.KeyAction.UP, 40], [desktop.m.KeyAction.EXUP, 0x5B]]); // Agent: L-Winkey press, Down arrow press, Down arrow release, L-Winkey release
                }
            } else if (ks == 1) { // WIN+Up arrow
                if (desktop.contype == 2) {
                    desktop.m.sendkey([[0xffe7, 1], [0xff52, 1], [0xff52, 0], [0xffe7, 0]]); // Intel AMT: Meta-left down, Up arrow press, Up arrow release, Meta-left release
                } else {
                    desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.DOWN, 38], [desktop.m.KeyAction.UP, 38], [desktop.m.KeyAction.EXUP, 0x5B]]); // MeshAgent: L-Winkey press, Up arrow press, Up arrow release, L-Winkey release
                }
            } else if (ks == 2) { // WIN+L arrow
                if (desktop.contype == 2) {
                    desktop.m.sendkey([[0xffe7, 1], [0x6c, 1], [0x6c, 0], [0xffe7, 0]]); // Intel AMT: Meta-left down, 'l' press, 'l' release, Meta-left release
                } else {
                    desktop.sendCtrlMsg('{"action":"lock"}');
                    //desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN,0x5B],[desktop.m.KeyAction.DOWN,76],[desktop.m.KeyAction.UP,76],[desktop.m.KeyAction.EXUP,0x5B]]); // MeshAgent: L-Winkey press, 'L' press, 'L' release, L-Winkey release
                    //desktop.m.SendKeyMsgKC(desktop.m.KeyAction.EXDOWN, 0x5B);
                    //desktop.m.SendKeyMsgKC(desktop.m.KeyAction.DOWN, 76);
                    //desktop.m.SendKeyMsgKC(desktop.m.KeyAction.UP, 76);
                    //desktop.m.SendKeyMsgKC(desktop.m.KeyAction.EXUP, 0x5B);
                }
            } else if (ks == 3) { // WIN+M arrow
                if (desktop.contype == 2) {
                    desktop.m.sendkey([[0xffe7, 1], [0x6d, 1], [0x6d, 0], [0xffe7, 0]]); // Intel AMT: Meta-left down, 'm' press, 'm' release, Meta-left release
                } else {
                    desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.DOWN, 77], [desktop.m.KeyAction.UP, 77], [desktop.m.KeyAction.EXUP, 0x5B]]); // MeshAgent: L-Winkey press, 'M' press, 'M' release, L-Winkey release
                }
            } else if (ks == 4) { // Shift+WIN+M arrow
                if (desktop.contype == 2) {
                    desktop.m.sendkey([[0xffe1, 1], [0xffe7, 1], [0x6d, 1], [0x6d, 0], [0xffe7, 0], [0xffe1, 0]]); // Intel AMT: Shift-left down, Meta-left down, 'm' press, 'm' release, Meta-left release, Shift-left release
                } else {
                    desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.DOWN, 16], [desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.DOWN, 77], [desktop.m.KeyAction.UP, 77], [desktop.m.KeyAction.EXUP, 0x5B], [desktop.m.KeyAction.UP, 16]]);     // MeshAgent: L-shift press, L-Winkey press, 'M' press, 'M' release, L-Winkey release, L-shift release
                }
            } else if (ks == 5) { // WIN
                if (desktop.contype == 2) {
                    desktop.m.sendkey([[0xffe7, 1], [0xffe7, 0]]); // Intel AMT: Meta-left down, Meta-left release
                } else {
                    desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.EXUP, 0x5B]]); // MeshAgent: L-Winkey press, L-Winkey release
                }
            } else if (ks == 6) { // WIN+R
                if (desktop.contype == 2) {
                    desktop.m.sendkey([[0xffe7, 1], [0x72, 1], [0x72, 0], [0xffe7, 0]]); // Intel AMT: Meta-left down, 'r' press, 'r' release, Meta-left release
                } else {
                    desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 0x5B], [desktop.m.KeyAction.DOWN, 82], [desktop.m.KeyAction.UP, 82], [desktop.m.KeyAction.EXUP, 0x5B]]); // MeshAgent: L-Winkey press, 'R' press, 'R' release, L-Winkey release
                }
            } else if (ks == 7) { // ALT-F4
                if (desktop.contype == 2) {
                    desktop.m.sendkey([[0xffe9, 1], [0xffc1, 1], [0xffc1, 0], [0xffe9, 0]]); // Intel AMT: Alt down, 'F4' press, 'F4' release, Alt release
                } else {
                    desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 18], [desktop.m.KeyAction.DOWN, 115], [desktop.m.KeyAction.UP, 115], [desktop.m.KeyAction.EXUP, 18]]); // MeshAgent: Alt press, 'F4' press, 'F4' release, Alt release
                }
            } else if (ks == 8) { // CTRL-W
                if (desktop.contype == 2) {
                    desktop.m.sendkey([[0xffe3, 1], [0x77, 1], [0x77, 0], [0xffe3, 0]]); // Intel AMT: Ctrl down, 'w' press, 'w' release, Ctrl release
                } else {
                    desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 17], [desktop.m.KeyAction.DOWN, 87], [desktop.m.KeyAction.UP, 87], [desktop.m.KeyAction.EXUP, 17]]); // MeshAgent: Ctrl press, 'W' press, 'W' release, Ctrl release
                }
            } else if (ks == 9) { // ALT-TAB
                if (desktop.contype == 2) {
                    desktop.m.sendkey([[0xffe9, 1], [0xff09, 1], [0xff09, 0], [0xffe9, 0]]); // Intel AMT: Alt down, 'TAB' press, 'TAB' release, Alt release
                } else {
                    desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN, 18], [desktop.m.KeyAction.DOWN, 9], [desktop.m.KeyAction.UP, 9], [desktop.m.KeyAction.EXUP, 18]]); // MeshAgent: Alt press, 'TAB' press, 'TAB' release, Alt release
                }
            } else if (ks == 10) { // CTRL-ALT-DEL
                desktop.m.sendcad();
            } else if (ks == 11) { // TAB
                if (desktop.contype == 2) {
                    desktop.m.sendkey([[0xff09, 1], [0xff09, 0]]); // Intel AMT: 'TAB' press, 'TAB' release
                } else {
                    desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.DOWN, 9], [desktop.m.KeyAction.UP, 9]]); // MeshAgent: 'TAB' press, 'TAB' release
                }
            } else if (ks == 12) { // Shift-F10
                if (desktop.contype == 2) {
                    desktop.m.sendkey([[0xffe1, 1], [0xffc7, 1], [0xffc7, 0], [0xffe1, 0]]); // Intel AMT: Left-Shift Down, F10 down, F10 up, Left-Shift Up
                } else {
                    desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.DOWN, 16], [desktop.m.KeyAction.DOWN, 121], [desktop.m.KeyAction.UP, 121], [desktop.m.KeyAction.UP, 16]]); // MeshAgent: Left-Shift Down, F10 down, F10 up, Left-Shift Up
                }
            }
        }

        function sendSpecialKeys() {
            if (xxdialogMode || desktop == null || desktop.State != 3) return;
            setDialogMode(3, "Spezielle Tasten", 3, deskSendKeys);
        }

        // Send CTRL-ALT-DEL
        /*
        function sendCAD() {
            if (xxdialogMode || desktop == null || desktop.State != 3) return;
            desktop.m.sendcad();
        }
        */

        // Toggle soft keyboard
        function toggleSoftKeys(x) {
            QV('DeskSoftInput', x == 1);
            if (x == 1) { Q('DeskSoftInput').focus(); }
        }

        // Show process dialogs
        function toggleDeskTools() {
            setSessionActivity();
            if (xxdialogMode) return;
            if (QS('DeskTools').display == 'none') {
                QV('DeskTools', true);
                Q('DeskTools').nodeid = currentNode._id;
                refreshDeskTools();
            } else {
                QV('DeskTools', false);
            }
        }

        // Refresh all of the desktop tool panels
        function refreshDeskTools() {
            setSessionActivity();
            QV('DeskToolsRefreshButton', false);
            setTimeout(refreshDeskToolsEx, 500);
            meshserver.send({ action: 'msg', type: 'ps', nodeid: currentNode._id });
        }
        function refreshDeskToolsEx() { QV('DeskToolsRefreshButton', true); }
        var deskTools = { sort: 1, msg: null };
        function sortProcess(sort) { deskTools.sort = sort; showDeskToolsProcesses(deskTools.msg); }
        function sortProcessPid(a, b) { if (a.p > b.p) return 1; if (a.p < b.p) return (-1); return 0; }
        function sortProcessName(a, b) { if (a.d > b.d) return 1; if (a.d < b.d) return (-1); return 0; }
        function showDeskToolsProcesses(message) {
            deskTools.msg = message;
            if (message == null) { QH('DeskToolsProcesses', ''); return; }
            if (Q('DeskTools').nodeid != message.nodeid) return;
            var p = [], processes = null;
            try { processes = JSON.parse(message.value); } catch (e) { }
            if (processes != null) {
                for (var pid in processes) { p.push({ p: parseInt(pid), c: processes[pid].cmd, d: processes[pid].cmd.toLowerCase(), u: processes[pid].user }); }
                if (deskTools.sort == 0) { p.sort(sortProcessPid); } else if (deskTools.sort == 1) { p.sort(sortProcessName); }
                var x = '';
                for (var i in p) { if (p[i].p != 0) { x += '<div class=deskToolsBar><div style=width:50px;float:left;text-align:right;padding-right:5px>' + p[i].p + '</div><a style=float:right;padding-right:5px;cursor:pointer onclick=stopProcess(' + p[i].p + ',"' + p[i].c + '")><img width=10 height=10 src="images/trash.png"></a><div style=float:right;padding-right:5px>' + (p[i].u ? p[i].u : '') + '</div><div>' + p[i].c + '</div></div>'; } }
                QH('DeskToolsProcesses', x);
            }
        }

        // 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)) desktop.m.mousedown(e) }
        function dmouseup(e) { setSessionActivity(); if ((!xxdialogMode && desktop != null)) desktop.m.mouseup(e) }
        function dmousemove(e) { setSessionActivity(); if ((!xxdialogMode && desktop != null)) desktop.m.mousemove(e) }
        function dmousewheel(e) { setSessionActivity(); if ((!xxdialogMode && desktop != null) && desktop.m.mousewheel) { 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(); deskAdjust(); } }
        function stopProcess(id, name) { setDialogMode(2, "Prozesskontrolle", 3, stopProcessEx, format("Prozess #{0} \"{1}\" beenden?", id, name), id); return false; }
        function stopProcessEx(buttons, tag) { meshserver.send({ action: 'msg', type: 'pskill', nodeid: currentNode._id, value: tag }); setTimeout(refreshDeskTools, 300); }

        //
        // FILES
        //

        var filesNode;
        function setupFiles() {
            // Setup the files tab
            var samenode = (filesNode == currentNode);
            filesNode = currentNode;
            var online = ((filesNode.conn & 1) != 0) ? true : false; // If Agent (1) connected, enable Terminal
            QE('p13Connect', online);
            if (((samenode == false) || (online == false)) && files) { files.Stop(); files = null; }
        }

        function onFilesStateChange(xfiles, state) {
            setSessionActivity();
            p13Connect.value = (state == 0) ? "Verbinden" : "Trennen";
            var str = StatusStrs[state];
            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; }
                    break;
                case 3:
                    p13targetpath = '';
                    files.sendText({ action: 'ls', reqid: 1, path: '' });
                    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) {
            if (!files) {
                // Setup a mesh agent files
                files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
                files.attemptWebRTC = attemptWebRTC;
                files.onStateChanged = onFilesStateChange;
                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);
            data = JSON.parse(decode_utf8(data));
            if (data.action == 'download') { p13gotDownloadCommand(data); return; }

            // Process file upload commands
            if ((data.action != null) && (data.action.startsWith('upload'))) { p13gotUploadData(data); return; }

            if (data.path != null) {
                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 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 onclick=p13folderup(0)>' + "Wurzel" + '</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 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;
                shortname = name;
                if (name.length > 70) { shortname = EscapeHtml(name.substring(0, 70)) + "..."; } else { shortname = EscapeHtml(name); }
                name = 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('/');
            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('/');
            files.sendText({ action: 'ls', reqid: 1, path: p13targetpath });
        }

        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() {
            if (p13filetree == null) {
                QE('p13DeleteFileButton', false);
                QE('p13NewFolderButton', false);
                QE('p13UploadButton', false);
                QE('p13RenameFileButton', false);
                QE('p13SelectAllButton', false);
                Q('p13SelectAllButton').value = "Alle";
                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));
                QE('p13DeleteFileButton', (cc > 0) && ((p13filetreelocation.length > 0) || (winAgent == false)));
                QE('p13NewFolderButton', ((p13filetreelocation.length > 0) || (winAgent == false)));
                QE('p13UploadButton', ((p13filetreelocation.length > 0) || (winAgent == false)));
                QE('p13RenameFileButton', (cc == 1) && ((p13filetreelocation.length > 0) || (winAgent == false)));
                QE('p13SelectAllButton', tc > 0);
                Q('p13SelectAllButton').value = (cc > 0 ? "Kein(e)" : "Alle");
                QE('p13RefreshButton', true);
                QE('p13CutButton', (cc > 0) && (cc == sfc) && ((p13filetreelocation.length > 0) || (winAgent == false)));
                QE('p13CopyButton', (cc > 0) && (cc == sfc) && ((p13filetreelocation.length > 0) || (winAgent == false)));
                QE('p13PasteButton', ((p13filetreelocation.length > 0) || (winAgent == false)) && ((p13clipboard != null) && (p13clipboard.length > 0)));
            }
        }

        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, "Neuer Ordner", 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>' + "Rekursiv Löschen" + '</label><br>' : '<input type=checkbox id=p13recdeleteinput style=\'display:none\'>'; setDialogMode(2, "Löschen", 3, p13deletefileEx, (cc > 1) ? (format("{0} ausgewählte Elemente löschen?", cc) + rec) : ("Ausgewähltes Element löschen?" + 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, "Umbenennen", 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, "Datei hochladen", 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("Datei-Editor", "Nur Dateien kleiner als 200 kB können bearbeitet werden."); }
                    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("Bestätigen Sie das Kopieren von {0} Einträgen an diesen Ort?", p13clipboard.length); } else { x = format("Bestätigen Sie das Kopieren von 1 Eintrag an diesen Ort?"); }
                } else {
                    if (p13clipboard.length > 1) { x = format("Bestätigen Sie das Verschieben von {0} Einträgen an diesen Ort?", p13clipboard.length); } else { x = format("Bestätigen Sie das Verschieben von 1 Eintrag an diesen Ort?"); }
                }
            }
            setDialogMode(2, "Einfügen", 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("Halte {0} Einträge zum Kopieren" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Leeren" + '</a>.', p13clipboard.length);
                    } else {
                        x = format("Halte 1 Eintrag zum Kopieren" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Leeren" + '</a>.');
                    }
                } else {
                    if (p13clipboard.length > 1) {
                        x = format("Halte {0} Einträge zum Verschieben" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Leeren" + '</a>.', p13clipboard.length);
                    } else {
                        x = format("Halte 1 Eintrag zum Verschieben" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Leeren" + '</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, "Datei herunterladen", 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));
            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, "Datei hochladen", 3, p13uploadFileContinue, format((overWriteCount == 1) ? "Das Hochladen wird 1 Datei überschreiben. Fortfahren?" : "Das Hochladen wird {0} Dateien überschreiben. Fortfahren?", overWriteCount), files);
            }
        }

        function p13uploadFileContinue(b, files) {
            uploadFile = {};
            uploadFile.xpath = p13filetreelocation.join('/');
            uploadFile.xfiles = files;
            uploadFile.xfilePtr = -1;
            setDialogMode(2, "Datei hochladen", 10, p13uploadFileCancel, '<div id=p13dfileName>' + "Verbinden..." + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=0 />');
            p13uploadNextFile();
        }

        // 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) {
                    // Load the data
                    uploadFile.xreader = new FileReader();
                    uploadFile.xreader.onload = function () {
                        uploadFile.xdata = uploadFile.xreader.result;
                        files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength }));
                    };
                    uploadFile.xreader.readAsArrayBuffer(file);
                } 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': { p13uploadNextPart(false); for (var i = 0; i < 8; i++) { p13uploadNextPart(true); } 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; }
            }
        }

        // 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) {
            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;
            }
        }

        //
        // 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) { x += addDetailItem("Architektur", EscapeHtml(m.OSArchitecture), s); }
                }
                if (x != '') { sections.push({ name: "Betriebssystem", html: x, img: 'software32.png' }); }
            }

            // 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; }
                    x += addDetailItem("Mesh-Agent", str);
                }
                if ((node.conn & 1) != 0) {
                    x += addDetailItem("Letzte Agenten-Verbindung", "Jetzt verbunden");
                } else {
                    if (node.lastconnect) { x += addDetailItem("Letzte Agenten-Verbindung", printDateTime(new Date(node.lastconnect))); }
                }
                if (node.lastaddr) {
                    var splitip = node.lastaddr.split(':');
                    if (splitip.length > 2) {
                        // IPv6
                        x += addDetailItem("Letzte Agenten-Adresse", node.lastaddr);
                    } else {
                        // IPv4
                        if (isPrivateIP(node.lastaddr)) {
                            x += addDetailItem("Letzte Agenten-Adresse", splitip[0]);
                        } else {
                            x += addDetailItem("Letzte Agenten-Adresse", '<a href="https://iplocation.com/?ip=' + splitip[0] + '" rel="noreferrer noopener" target="MeshIPLoopup">' + splitip[0] + '</a>');
                        }
                    }
                }
                if (x != '') { sections.push({ name: "Mesh-Agent", html: x, img: 'meshagent32.png' }); }
            }

            // 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("Beschreibung", 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-Schicht", format("MAC: {0}, Gateway: {1}", EscapeHtml(m[0].mac), EscapeHtml(m[0].gatewaymac)));
                        } else {
                            x += addDetailItem("MAC-Schicht", format("MAC: {0}", EscapeHtml(m[0].mac)));
                        }
                    }
                    for (var j = 0; j < m.length; j++) {
                        var iplayer = m[j];
                        if (iplayer.family == 'IPv4') {
                            if (iplayer.gateway && iplayer.netmask) {
                                x += addDetailItem("IPv4-Schicht", format("IP: {0}, Maske: {1}, Gateway: {2}", EscapeHtml(iplayer.address), EscapeHtml(iplayer.netmask), EscapeHtml(iplayer.gateway)));
                            } else {
                                x += addDetailItem("IPv4-Schicht", format("IP: {0}", EscapeHtml(iplayer.address)));
                            }
                        }
                        if (iplayer.family == 'IPv6') {
                            if (iplayer.gateway && iplayer.netmask) {
                                x += addDetailItem("IPv6 Layer", format("IP: {0}, Maske: {1}, Gateway: {2}", EscapeHtml(iplayer.address), EscapeHtml(iplayer.netmask), EscapeHtml(iplayer.gateway)));
                            } else {
                                x += addDetailItem("IPv6 Layer", format("IP: {0}", EscapeHtml(iplayer.address)));
                            }
                        }
                    }
                    x += '</div>';
                }
                x += '</table>';
                if (x != '') { sections.push({ name: "Vernetzung", html: x, img: 'networking64.png' }); }
            }

            // Attribute: Intel AMT
            if (node.intelamt != null) {
                var x = '';
                x += addDetailItem("Version", (node.intelamt.ver) ? ('v' + EscapeHtml(node.intelamt.ver)) : ('<i>' + "Unbekannt" + '</i>'), s);
                var provisioningStates = { 0: nobreak("Nicht aktiviert (Pre)"), 1: nobreak("Nicht aktiviert (In)"), 2: nobreak("Aktiviert") };
                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("Bereitstellungsstaat", ((node.intelamt.state) ? (provisioningStates[node.intelamt.state]) : ('<i>' + "Unbekannt" + '</i>')) + provisioningMode, s);
                x += addDetailItem("Sicherheit", (node.intelamt.tls == 1) ? "Mit TLS gesichert" : "TLS ist nicht eingerichtet", s);
                x += addDetailItem("Administrator-Anmeldeinformationen", (node.intelamt.user == null || node.intelamt.user == '') ? "Nicht bekannt" : "Bekannt", s);
                if (x != '') { sections.push({ name: "Intel&reg; Active Management Technology (Intel&reg; AMT)", html: x, img: 'amt32.png' }); }
            }

            if (hardware.identifiers) {
                var x = '', ident = hardware.identifiers;
                // BIOS
                if (ident.bios_vendor) { x += addDetailItem("Hersteller", 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: 'chip32.png' }); }

                // Motherboard
                x = '';
                if (ident.board_vendor) { x += addDetailItem("Hersteller", 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("Seriennummer", EscapeHtml(ident.board_serial), s); }
                if (ident.board_version) { x += addDetailItem("Version", EscapeHtml(ident.board_version), s); }
                if (ident.product_uuid) { x += addDetailItem("Kennung", 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: "Hauptplatine", html: x, img: 'motherboard32.png' }); }
            }

            if (hardware.windows) {
                if (hardware.windows.memory) {
                    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) { x += addDetailItem("Kapazität / Geschwindigkeit", format("{0} MB, {1} MHz", (m.Capacity / 1024 / 1024), m.Speed), s); }
                        if (m.PartNumber) { x += addDetailItem("Teilenummer", EscapeHtml((m.Manufacturer && m.Manufacturer != 'Undefined') ? (m.Manufacturer + ', ') : '') + EscapeHtml(m.PartNumber), s); }
                        x += '</div>';
                    }
                    x += '</table>';

                    if (x != '') { sections.push({ name: "Speicher", html: x, img: 'ram32.png' }); }
                }
            }

            // 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: 'storage32.png' }); }
            }

            // 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 + ' 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', "Keine Informationen zu diesem Gerät.");
            } else {
                QH('p10detailshtml', x);
            }
        }

        //
        // MY MESHS
        //

        var currentMesh;
        function p20updateMesh() {
            if (currentMesh == null) return;
            QH('p20meshName', EscapeHtml(currentMesh.name));
            var meshtype = format("Unbekannt #{0}", currentMesh.mtype);
            var meshrights = GetMeshRights(currentMesh);
            if (currentMesh.mtype == 1) meshtype = "Nur Intel&reg; AMT, kein Agent";
            if (currentMesh.mtype == 2) meshtype = "Verwaltet mittels Software-Agent";

            var x = '';
            x += addHtmlValue("Name", addLinkConditional(EscapeHtml(currentMesh.name), 'p20editmesh(1)', (meshrights & 1) != 0));
            x += addHtmlValue("Beschreibung", addLinkConditional(((currentMesh.desc && currentMesh.desc != '') ? EscapeHtml(currentMesh.desc) : ('<i>' + "Kein(e)" + '</i>')), 'p20editmesh(2)', (meshrights & 1) != 0));
            x += addHtmlValue("Tippen", meshtype);
            //x += addHtmlValue('Identifier', currentMesh._id.split('/')[2]);

            //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><a onclick=p20showAddMeshUserDialog() style=cursor:pointer><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Benutzer hinzufügen" + '</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>' + "Benutzerberechtigungen" + '</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 = "Partielle Rechte", r = sortedusers[i].rights, icon = 2;
                if (r == 0xFFFFFFFF) rights = "Gesamtadministrator"; else if (r == 0) rights = "Keine Berechtigungen";
                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>' + "Gruppe löschen" + '</a></span></div>'; }

            QH('p20info', x);
        }

        function p20showDeleteMeshDialog() {
            if (xxdialogMode) return false;
            var x = format("Möchten Sie die Gruppe {0} wirklich löschen? Das Löschen der Gerätegruppe wird auch alle Informationen über die Geräte innerhalb dieser Gruppe löschen.", EscapeHtml(currentMesh.name)) + '<br /><br />';
            x += '<label><input id=p20check type=checkbox onchange=p20validateDeleteMeshDialog() />' + "Bestätigen" + '</label>';
            setDialogMode(2, "Gruppe löschen", 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 p20editmesh(focus) {
            if (xxdialogMode) return;
            var x = addHtmlValue("Name", '<input id=dp20meshname style=width:170px maxlength=32 onchange=p20editmeshValidate() onkeyup=p20editmeshValidate() />');
            x += addHtmlValue("Beschreibung", '<input id=dp20meshdesc style=width:170px maxlength=1024 onkeyup=p20editmeshValidate() />');
            setDialogMode(2, "Gerätegruppe bearbeiten", 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 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>' + "Gesamtadministrator" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20editmesh>' + "Gerätegruppe bearbeiten" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20manageusers>' + "Gerätegruppen-Benutzer verwalten" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20managecomputers>' + "Gerätegruppen-Rechner verwalten" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20remotecontrol>' + "Fernsteuerung" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20remoteview style=margin-left:12px>' + "Nur entfernte Ansicht" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20remotelimitedinput style=margin-left:12px>' + "Nur eingeschränkte Eingabe" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20noterminal style=margin-left:12px>' + "Kein Terminalzugriff" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20nofiles style=margin-left:12px>' + "Kein Dateizugriff" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20noamt style=margin-left:12px>' + "Kein Intel&reg; AMT" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20meshagentconsole>' + "Mesh-Agent-Konsole" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20meshserverfiles>' + "Server-Dateien" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20wakedevices>' + "Geräte wecken" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20editnotes>' + "Geräte-Notizen bearbeiten" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20limitevents>' + "Nur eigene Ereignisse anzeigen" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20chatnotify>' + "Chat & Benachrichtigen" + '</label><br>';
            x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20uninstall>' + "Agenten deinstallieren" + '</label><br>';
            x += '</div>';
            setDialogMode(2, "Benutzer zu Gerätegruppe hinzufügen", 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);
        }

        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;
            }
            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("Gesamtadministrator"); else {
                if ((meshrights & 1) != 0) r.push("Gerätegruppe bearbeiten");
                if ((meshrights & 2) != 0) r.push("Gerätegruppen-Benutzer verwalten");
                if ((meshrights & 4) != 0) r.push("Gerätegruppen-Rechner verwalten");
                if ((meshrights & 8) != 0) r.push("Fernsteuerung");
                if ((meshrights & 16) != 0) r.push("Agent-Konsole");
                if ((meshrights & 32) != 0) r.push("Server-Dateien");
                if ((meshrights & 64) != 0) r.push("Geräte wecken");
                if ((meshrights & 128) != 0) r.push("Notizen bearbeiten");
                if ((meshrights & 256) != 0) r.push("Nur entfernte Ansicht");
                if ((meshrights & 512) != 0) r.push("Kein Terminal");
                if ((meshrights & 1024) != 0) r.push("Keine Dateien");
                if ((meshrights & 2048) != 0) r.push("Kein Intel&reg; AMT");
                if (((meshrights & 8) != 0) && ((meshrights & 4096) != 0) && ((meshrights & 256) == 0)) r.push("Eingeschränkte Eingabe");
                if ((meshrights & 8192) != 0) r.push("Nur eigene Ereignisse");
                if ((meshrights & 16384) != 0) r.push("Chat & Benachrichtigen");
                if ((meshrights & 32768) != 0) r.push("Deinstallation");
            }
            if (r.length == 0) { r.push("Keine Berechtigungen"); }
            var buttons = 1, uname = userid.split('/')[2];
            if (currentMesh.links[userid].name) { uname = currentMesh.links[userid].name; }
            var x = addHtmlValue("Benutzername", EscapeHtml(uname));
            if (uname != userid.split('/')[2]) { x += addHtmlValue("User ID", EscapeHtml(userid.split('/')[2])); }
            x += addHtmlValue("Berechtigungen", r.join(", "));
            if (((userinfo._id) != userid) && (cmeshrights == 0xFFFFFFFF || (((cmeshrights & 2) != 0) && (meshrights != 0xFFFFFFFF)))) buttons += 4;
            setDialogMode(2, "Gerätegruppenbenutzer", 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, "Entfernter Mesh-Benutzer", 3, p20viewuserEx2, format("Bestätigen Sie das Entfernen des Benutzers {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 }); }

        //
        // 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;
        }

        //
        // 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_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
        //

        // 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 (userinfo.manageAllDeviceGroups && (userid == userinfo._id)) return 0xFFFFFFFF;

            // Check device group link permission
            var rights = 0, r = mesh.links[userid];
            if (r != null) {
                if (r.rights == 0xFFFFFFFF) { return 0xFFFFFFFF; } // 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 0xFFFFFFFF; } // User has full rights thru a user group, stop here.
                            rights |= r.rights; // TODO: Deal with reverse permissions
                        }
                    }
                }
            }

            return rights;
        }

        // 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 (userinfo.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 r;
            var user = null;

            // 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
            /*
            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 r;
        }

        // 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 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() { onDevicesScroll(); QS('dialog').left = ((((getDocWidth() - 300) / 2)) + 'px'); deskAdjust(); deskAdjust(); /*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;color:darkblue;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>'; }

    </script>

</body></html>