Files
netdesk/server/web/app.html
T

352 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NetDesk — The Fast Self-Hosted Remote Desktop</title>
<link rel="stylesheet" href="app.css?v=53">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%233d7eff'/%3E%3Cpath d='M8 20l8-12 8 12-8-4z' fill='white'/%3E%3Crect x='12' y='18' width='8' height='7' rx='2' fill='white'/%3E%3C/svg%3E">
<link rel="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#3d7eff">
<meta name="robots" content="noindex, nofollow">
</head>
<body>
<div id="screenConnect">
<div class="topnote">ⓘ <b>netdesk.space</b> is the only official domain for this NetDesk server. No third-party cloud ever sees your screen.</div>
<header class="nav">
<div class="nav-brand">
<svg width="30" height="30" viewBox="0 0 32 32"><defs><linearGradient id="ndg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#25d386"/><stop offset="1" stop-color="#16a765"/></linearGradient></defs><rect width="32" height="32" rx="8" fill="url(#ndg)"/><path d="M16 6.5L6.5 18.2C6.1 18.7 6.4 19.4 7 19.4H25C25.6 19.4 25.9 18.7 25.5 18.2L16 6.5Z" fill="#fff"/><path d="M11.8 21H20.2L21.5 26.4C21.6 26.8 21.3 27.2 20.9 27.2H11.1C10.7 27.2 10.4 26.8 10.5 26.4L11.8 21Z" fill="#fff"/></svg>
<b>NetDesk</b>
</div>
<nav class="nav-links">
<a href="/">Home</a>
<a href="/features">Features</a>
<a href="/download">Download</a>
<button id="btnTheme" class="theme-toggle" title="Switch theme">☀ Light</button>
<span id="navAuth"></span>
</nav>
</header>
<section class="hero">
<div class="hero-copy">
<h1>The Fast Self-Hosted<br>Remote Desktop</h1>
<p class="hero-sub">Your own remote access and support software — unattended, silent in the
background, end-to-end encrypted, with your own server. The alternative to TeamViewer,
AnyDesk and RustDesk that you fully control.</p>
<div class="hero-cta">
<a class="btn-big btn-blue" href="#download">Download <span class="dl-ic">⬇</span></a>
<a class="btn-big btn-ghost" href="/portal">Self-Hosting</a>
</div>
</div>
<div class="hero-card">
<div class="card" id="connect">
<h2>Connect to a computer</h2>
<p class="sub">Enter the remote ID shown by the NetDesk agent.</p>
<label>Remote ID</label>
<input id="inId" type="text" inputmode="numeric" pattern="[0-9]*" placeholder="123 456 789" spellcheck="false" autocomplete="off">
<label>Password</label>
<div class="pw-wrap">
<input id="inPw" type="password" placeholder="permanent password" autocomplete="off">
<button id="btnPwEye" class="ghost" type="button" title="Show password"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>
</div>
<label id="lblTotp" hidden>2FA code</label>
<input id="inTotp" type="text" inputmode="numeric" maxlength="6" placeholder="6-digit authenticator code" autocomplete="off" hidden>
<details style="margin-top:14px">
<summary class="foot" style="cursor:pointer;user-select:none">Advanced — direct connection</summary>
<label>Server</label>
<input id="inServer" type="text" placeholder="blank = this server · or ws://PC-IP:8901 for direct LAN" spellcheck="false">
</details>
<label class="chk"><input id="inRemember" type="checkbox"> Remember password on this device</label>
<label class="chk"><input id="inViewOnly" type="checkbox"> View only (no input)</label>
<button id="btnConnect" class="primary">Connect</button>
<div id="connectErr" class="error" hidden></div>
<style>
#hostShare{margin-top:22px;padding-top:20px;border-top:1px solid rgba(148,163,184,.25);text-align:center}
#hostShare .host-or{font-size:12px;color:#8a97ab;letter-spacing:1px;margin-bottom:12px}
#hostShare h3{margin:0 0 6px;font-size:16px}
#hostShare .host-sub{margin:0 0 14px;font-size:12.5px;color:#8a97ab;line-height:1.5}
#hostShare .host-cred{font-size:13px;margin-bottom:14px;color:#cdd6e4}
#hostShare .host-cred b{font-size:15px;letter-spacing:.5px;color:#3d7eff;font-variant-numeric:tabular-nums}
#hostShare #btnHostRegen{padding:2px 8px;font-size:13px;vertical-align:middle;margin-left:6px}
#hostShare .host-status{margin-top:12px;font-size:12.5px;color:#8a97ab;min-height:16px}
</style>
<div id="hostShare">
<div class="host-or">— OR —</div>
<h3>Share THIS screen</h3>
<p class="host-sub">Let someone view this computer right from their browser — no app to install.<br>(View-only: they can see &amp; hear it, not control it.)</p>
<div class="host-cred">ID <b id="hostIdOut">—</b> &nbsp;·&nbsp; Password <b id="hostPwOut">—</b><button id="btnHostRegen" class="tb" title="Generate a new ID + password">↻</button></div>
<button id="btnHostStart" class="primary">Share my screen</button>
<button id="btnHostStop" class="tb" hidden>Stop sharing</button>
<div id="hostStatus" class="host-status"></div>
</div>
<div id="myDevsWrap" hidden>
<div class="ab-head"><span>My computers</span><span class="foot">click to connect</span></div>
<div id="myDevs"></div>
</div>
<div id="abookWrap">
<div class="ab-head"><span>Address book</span><button id="btnAbAdd" class="tb">+ Save current</button></div>
<div id="abook" hidden></div>
</div>
<div class="hint">
Direct connection (no server): put <code>ws://IP:8901</code> in the Server
field when the host runs with <code>--listen</code> on your LAN or via port-forward.
</div>
<div class="form-foot">
<span class="foot">NetDesk viewer</span>
<span style="display:flex;gap:8px;align-items:center">
<span id="authChip" class="foot" hidden></span>
</span>
</div>
</div>
</div>
</section>
</div><!-- /screenConnect -->
<div id="authModal" class="modal-wrap" hidden>
<div class="modal-card auth-card">
<button id="btnAuthClose" class="modal-x" title="Close">✕</button>
<div class="auth-head">
<span class="auth-logo"><svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M12 3.2L3.2 12.6C2.8 13 3.1 13.7 3.7 13.7H20.3C20.9 13.7 21.2 13 20.8 12.6L12 3.2Z" fill="#fff"/><path d="M8.7 15.1H15.3L16.4 20.3C16.5 20.7 16.2 21 15.8 21H8.2C7.8 21 7.5 20.7 7.6 20.3L8.7 15.1Z" fill="#fff"/></svg></span>
<span class="auth-brand">NetDesk <i>Account</i></span>
</div>
<div class="auth-tabs">
<button class="atab on" data-t="in">Sign in</button>
<button class="atab" data-t="up">Create account</button>
<button class="atab" data-t="fp">Forgot</button>
<button class="atab" data-t="2fa" id="atab2fa" hidden>Two-factor</button>
</div>
<div class="apane" id="pane-in">
<h3 class="auth-title">Welcome back</h3>
<p class="auth-sub">Sign in to see your computers and connect in one click.</p>
<label class="auth-lbl" for="auEmail">Email address</label>
<input id="auEmail" type="email" placeholder="you@company.com" autocomplete="email" spellcheck="false">
<label class="auth-lbl" for="auPass">Password</label>
<input id="auPass" type="password" placeholder="••••••••••" autocomplete="current-password">
<label class="auth-lbl" id="auTotpLbl" for="auTotp" hidden>Two-factor code</label>
<input id="auTotp" type="text" inputmode="numeric" maxlength="6" placeholder="6-digit authenticator code" autocomplete="one-time-code" hidden>
<button id="auGo" class="primary auth-cta">Sign in&nbsp;&nbsp;→</button>
<div id="ssoWrap" hidden style="margin-top:12px">
<div style="display:flex;align-items:center;gap:10px;color:var(--faint);font-size:12px;margin:4px 0 10px"><span style="flex:1;height:1px;background:var(--border)"></span>OR<span style="flex:1;height:1px;background:var(--border)"></span></div>
<a id="ssoBtn" class="btn-big btn-ghost" href="/saml/login?RelayState=/app" style="width:100%;justify-content:center;box-sizing:border-box">Sign in with SSO</a>
</div>
</div>
<div class="apane" id="pane-up" hidden>
<h3 class="auth-title">Create your account</h3>
<p class="auth-sub">Free for individuals — your fleet syncs between the desktop app and this site.</p>
<label class="auth-lbl" for="auEmail2">Email address</label>
<input id="auEmail2" type="email" placeholder="you@company.com" autocomplete="email" spellcheck="false">
<label class="auth-lbl" for="auPass2">Password</label>
<input id="auPass2" type="password" placeholder="8+ characters" autocomplete="new-password">
<button id="auGo2" class="primary auth-cta">Create account&nbsp;&nbsp;→</button>
<div class="foot">Works right away — sign in with the same account in the NetDesk desktop app and your computers appear here automatically.</div>
</div>
<div class="apane" id="pane-fp" hidden>
<h3 class="auth-title">Reset your password</h3>
<p class="auth-sub">We'll email you a secure link to choose a new one.</p>
<label class="auth-lbl" for="auEmail3">Account email</label>
<input id="auEmail3" type="email" placeholder="you@company.com" autocomplete="email" spellcheck="false">
<button id="auGo3" class="primary auth-cta">Send reset link</button>
</div>
<div class="apane" id="pane-rs" hidden>
<h3 class="auth-title">Choose a new password</h3>
<label class="auth-lbl" for="auPass4">New password</label>
<input id="auPass4" type="password" placeholder="8+ characters" autocomplete="new-password">
<button id="auGo4" class="primary auth-cta">Set new password</button>
</div>
<div class="apane" id="pane-2fa" hidden>
<h3 class="auth-title">Two-factor authentication</h3>
<p class="auth-sub">Add a 6-digit authenticator code on top of your password at sign-in.</p>
<div id="tf2Status" class="foot">Checking…</div>
<button id="tf2Enable" class="primary auth-cta" hidden>Enable 2FA&nbsp;&nbsp;→</button>
<div id="tf2Enroll" hidden>
<p class="auth-sub">Add this secret to your authenticator app (Google Authenticator, Authy, 1Password…), then enter a code to confirm.</p>
<label class="auth-lbl">Secret</label>
<input id="tf2Secret" type="text" readonly spellcheck="false">
<div id="tf2Uri" class="foot" style="word-break:break-all;margin:4px 0 8px"></div>
<label class="auth-lbl" for="tf2Code">Confirmation code</label>
<input id="tf2Code" type="text" inputmode="numeric" maxlength="6" placeholder="6-digit code" autocomplete="one-time-code">
<button id="tf2Confirm" class="primary auth-cta">Confirm &amp; enable&nbsp;&nbsp;→</button>
</div>
<div id="tf2DisableWrap" hidden>
<label class="auth-lbl" for="tf2DisCode">Enter a current code to turn 2FA off</label>
<input id="tf2DisCode" type="text" inputmode="numeric" maxlength="6" placeholder="6-digit code" autocomplete="one-time-code">
<button id="tf2Disable" class="auth-cta">Disable 2FA</button>
</div>
</div>
<div id="auMsg" class="error" hidden></div>
<div id="auOk" class="auok" hidden></div>
<div class="auth-secure">🔒 TLS 1.3 in transit · passwords stored as PBKDF2-SHA256 · 200,000 rounds</div>
</div>
</div>
<div id="screenSession" class="screen" hidden>
<div id="toolbar">
<div class="tb-group">
<span id="tbTitle" class="tb-title">NetDesk</span>
<span id="tbState" class="state">connecting…</span>
</div>
<div class="sep"></div>
<div class="tb-group" id="menubar">
<button class="tb menu-btn" data-menu="mActions">Actions ▾</button>
<button class="tb menu-btn" data-menu="mView">View ▾</button>
<button class="tb menu-btn" data-menu="mComm">Communicate ▾</button>
<button class="tb menu-btn" data-menu="mExtras">Extras ▾</button>
</div>
<div class="tmenu" id="mComm" hidden>
<button class="mi" id="miMsg">Send message… (chat)</button>
<button class="mi mtoggle" id="miSound">Play remote computer sound<span class="mtick">✓</span></button>
<div class="msep"></div>
<button class="mi mtoggle" id="miVoice" title="Talk to the person at the host PC using your microphone">Start voice call (your mic)<span class="mtick">✓</span></button>
</div>
<div class="tmenu" id="mActions" hidden>
<button class="mi" data-sys="lock">Lock remote computer</button>
<button class="mi" data-sys="logoff" data-confirm="Sign out the remote user?">Sign out on remote computer</button>
<button class="mi" data-sys="reboot" data-confirm="Reboot the remote computer? The session will end.">Remote reboot</button>
<button class="mi" data-sys="cad">Send Ctrl + Alt + Del</button>
<div class="msep"></div>
<button class="mi" id="miWol">Wake a PC on this network (Wake-on-LAN)…</button>
<div class="msep"></div>
<button class="mi mtoggle" id="miBlockInput">Block remote user's input<span class="mtick">✓</span></button>
<button class="mi mtoggle" id="miLockEnd">Lock on session end<span class="mtick">✓</span></button>
<div class="msep"></div>
<button class="mi" id="miInfo">Session information…</button>
</div>
<div class="tmenu" id="mView" hidden>
<div class="mhead">Monitor</div>
<div id="miMons"></div>
<div class="mhead">Quality</div>
<div id="miQual"></div>
<div class="mhead">Scaling</div>
<button class="mi mradio" data-scale="fit">Best fit<span class="mtick">✓</span></button>
<button class="mi mradio" data-scale="orig">Original (1:1, scroll)<span class="mtick">✓</span></button>
<div class="mhead">Screen resolution</div>
<div id="miModes"><button class="mi" id="miModesLoad">Load resolutions…</button></div>
<div class="msep"></div>
<button class="mi mtoggle" id="miWallpaper">Hide wallpaper<span class="mtick">✓</span></button>
<button class="mi" id="miRefresh">Refresh screen</button>
</div>
<div class="tmenu" id="mExtras" hidden>
<button class="mi" id="miFiles">File transfer</button>
<button class="mi" id="miChat">Chat</button>
<button class="mi" id="miShot">Save screenshot</button>
<button class="mi" id="miRec">Record session (WebM)</button>
</div>
<div class="tb-group">
<select id="selMonitor" title="Monitor"></select>
<select id="selQuality" title="Quality">
<option value="auto" selected>Auto (adaptive)</option>
<option value="low">Low · 15fps · 2 Mbps</option>
<option value="balanced">Balanced · 30fps · 6 Mbps</option>
<option value="high">High · 30fps · 12 Mbps</option>
<option value="extreme">Extreme · 60fps · 20 Mbps</option>
</select>
</div>
<div class="sep"></div>
<div class="tb-group">
<button id="btnViewOnly" class="tb" title="Toggle remote input"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3l18 18"/><path d="M10.6 5.1A9.8 9.8 0 0 1 12 5c7 0 11 8 11 8a13.2 13.2 0 0 1-1.7 2.4"/><path d="M6.6 6.6C4 8.3 2 12 2 12s4 8 12 8a9.7 9.7 0 0 0 4.3-1"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2"/></svg><span>Input</span></button>
<button id="btnOsk" class="tb touch-only" title="Open the on-screen keyboard">⌨<span>Type</span></button>
<button id="btnKbdLock" class="tb" title="Capture system keys while fullscreen"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 10h.01M10 10h.01M14 10h.01M18 10h.01M7 14h10"/></svg><span>Keys</span></button>
<button id="btnMonCycle" class="tb" title="Switch monitor" hidden><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="13" height="9" rx="1.5"/><rect x="9" y="11" width="13" height="9" rx="1.5"/></svg><span id="monCycleLbl">1/2</span></button>
<button id="btnFullscreen" class="tb" title="Fullscreen"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3"/></svg></button>
<button id="btnClipboard" class="tb" title="Clipboard sync"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="8" y="2" width="8" height="4" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg></button>
<button id="btnFiles" class="tb" title="File manager"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></button>
<button id="btnChat" class="tb" title="Chat"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></button>
<button id="btnAudio" class="tb" title="Host audio: off → system → microphone"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 5 6 9H2v6h4l5 4V5z"/><path d="M15.5 8.5a5 5 0 0 1 0 7M19 5a8 8 0 0 1 0 14"/></svg><span id="audioLbl">Audio</span></button>
<button id="btnSys" class="tb" title="Power / processes"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18.4 6.6a9 9 0 1 1-12.8 0"/><path d="M12 2v10"/></svg></button>
<button id="btnPrivacy" class="tb" title="Privacy mode"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.9 17.9A9.8 9.8 0 0 1 12 20c-8 0-11-8-11-8a13.4 13.4 0 0 1 4.1-4.4M9.9 4.2A9.6 9.6 0 0 1 12 4c8 0 11 8 11 8a13.5 13.5 0 0 1-2.2 3.1"/><path d="M3 3l18 18"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2"/></svg></button>
<button id="btnRec" class="tb" title="Record session"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="4" fill="currentColor" stroke="none"/></svg></button>
</div>
<div class="sep"></div>
<div class="tb-group">
<span id="tbStats" class="stats"></span>
<button id="btnDisconnect" class="tb danger" title="Disconnect"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18M6 6l12 12"/></svg><span>Disconnect</span></button>
</div>
<button id="tbTab" title="Hide the toolbar">▲</button>
</div>
<button id="tbShow" title="Show the toolbar" hidden>▼ NetDesk</button>
<input id="oskInput" type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" aria-hidden="true">
<div id="stage">
<canvas id="canvas" tabindex="0"></canvas>
<img id="remoteCursor" alt="" hidden>
<div id="dropHint" hidden>Drop files to upload to the remote desktop</div>
<div id="banner" class="banner" hidden></div>
<div id="localCursor" hidden></div>
<div id="filePanel" class="panel" data-title="Files" hidden>
<div class="panel-head" data-panel="filePanel"><span>Remote files</span><button class="tb" data-min="filePanel" title="Minimize to edge">»</button><button class="tb" id="btnFilesClose" title="Close">✕</button></div>
<div class="file-path">
<input id="filePath" spellcheck="false">
<button id="btnFileGo" class="tb">Go</button>
<button id="btnFileUp" class="tb" title="Up">⬆</button>
<button id="btnFileHome" class="tb" title="Drives">💾</button>
</div>
<div id="fileList" class="file-list"></div>
<div class="panel-foot">
<button id="btnFileMkdir" class="tb">New folder</button>
<button id="btnFileUpload" class="tb">Upload…</button>
<input id="fileInput" type="file" multiple hidden>
<button id="btnDirUpload" class="tb" title="Upload a folder">Folder…</button>
<input id="dirInput" type="file" webkitdirectory multiple hidden>
<span id="fileStatus" class="stats"></span>
</div>
</div>
<div id="chatPanel" class="panel" data-title="Chat" hidden>
<div class="panel-head" data-panel="chatPanel"><span>Chat</span><button class="tb" data-min="chatPanel" title="Minimize to edge">»</button><button class="tb" id="btnChatClose" title="Close">✕</button></div>
<div id="chatLog" class="chat-log"></div>
<div class="chat-input">
<input id="chatText" placeholder="Type a message…">
<button id="chatSend" class="tb">Send</button>
</div>
</div>
<div id="sysPanel" class="panel" data-title="Remote power" hidden>
<div class="panel-head" data-panel="sysPanel"><span>Remote power</span><button class="tb" data-min="sysPanel" title="Minimize to edge">»</button><button class="tb" id="btnSysClose" title="Close">✕</button></div>
<details class="sec" open>
<summary>Power actions<span class="chev">▾</span></summary>
<div class="sys-grid">
<button class="tb" data-cmd="lock">Lock workstation</button>
<button class="tb" data-cmd="logoff">Sign out user</button>
<button class="tb" data-cmd="reboot">Reboot</button>
<button class="tb danger" data-cmd="shutdown">Shut down</button>
</div>
<div class="panel-foot">
<input id="sysCmd" placeholder="run a command on host (cmd /c …)">
<button id="btnSysRun" class="tb">Run</button>
</div>
</details>
<details class="sec" open>
<summary>Options<span class="chev">▾</span></summary>
<div class="panel-foot">
<label class="chk" style="margin:0"><input id="chkLockOnEnd" type="checkbox"> Lock PC when I disconnect</label>
</div>
</details>
<details class="sec">
<summary>Processes<span class="chev">▾</span><button id="btnProcRefresh" class="tb" style="margin-left:auto">↻ Refresh</button></summary>
<div id="procList" class="file-list" style="max-height:200px"></div>
</details>
<details class="sec">
<summary>TCP tunnel <span class="muted" style="text-transform:none">(portal sign-in)</span><span class="chev">▾</span></summary>
<div class="panel-foot">
<input id="tnlPort" placeholder="port on the host" style="width:120px">
<button id="btnTnlOpen" class="tb">Connect</button>
<span id="tnlStatus" class="stats"></span>
</div>
</details>
</div>
</div>
</div>
<script src="app.js?v=55"></script>
<script src="host.js?v=5"></script>
</body>
</html>