Files

291 lines
21 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NetDesk Console</title>
<link rel="stylesheet" href="/app.css?v=26">
<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">
<meta name="robots" content="noindex, nofollow">
</head>
<body class="portal console">
<style>
/* ---- Enterprise login (scoped to #viewLogin / .nd-*) ---------------------- */
#viewLogin[hidden]{display:none!important}
.nd-login{position:fixed;inset:0;z-index:60;display:flex;overflow:auto;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
background:#0b1020;color:#0f172a;-webkit-font-smoothing:antialiased}
.nd-login *{box-sizing:border-box}
/* hero / brand panel */
.nd-hero{flex:1 1 48%;min-height:100%;position:relative;display:flex;flex-direction:column;
justify-content:space-between;padding:44px 52px;color:#e8efff;overflow:hidden;
background:
radial-gradient(900px 520px at 12% -8%,rgba(80,140,255,.45),transparent 60%),
radial-gradient(760px 520px at 110% 115%,rgba(99,102,241,.40),transparent 55%),
linear-gradient(135deg,#0a1836 0%,#10245c 46%,#1b3a86 100%)}
.nd-hero::after{content:"";position:absolute;inset:0;opacity:.5;pointer-events:none;
background-image:radial-gradient(rgba(255,255,255,.08) 1px,transparent 1.4px);
background-size:26px 26px;mask-image:linear-gradient(180deg,#000,transparent 85%)}
.nd-hero-brand{display:flex;align-items:center;gap:12px;font-weight:700;font-size:21px;letter-spacing:.2px;position:relative}
.nd-hero-brand .nd-badge{display:inline-flex;align-items:center;gap:7px;margin-left:auto;font-size:11.5px;
font-weight:600;color:#bcd2ff;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.14);
padding:5px 11px;border-radius:999px;letter-spacing:.3px}
.nd-hero-mid{position:relative;max-width:460px}
.nd-hero-mid h2{font-size:38px;line-height:1.1;margin:0 0 16px;font-weight:760;letter-spacing:-.5px}
.nd-hero-mid p{font-size:15.5px;line-height:1.6;color:#b9c9ee;margin:0 0 30px}
.nd-points{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:15px}
.nd-points li{display:flex;align-items:center;gap:13px;font-size:14.5px;color:#dbe6ff}
.nd-points svg{flex:none;width:20px;height:20px;color:#7fb0ff}
.nd-hero-foot{position:relative;display:flex;align-items:center;gap:8px;font-size:12.5px;color:#8ba4d6}
/* sign-in panel */
.nd-panel{flex:1 1 52%;min-height:100%;display:flex;flex-direction:column;align-items:center;
justify-content:center;gap:16px;padding:40px 28px;background:#f4f6fb}
.nd-card{width:100%;max-width:410px;background:#fff;border:1px solid #e6eaf2;border-radius:18px;
padding:40px 40px 32px;box-shadow:0 24px 60px -24px rgba(16,32,80,.35)}
.nd-card-logo{display:flex;align-items:center;gap:10px;font-weight:700;color:#1b2742;font-size:15px;margin-bottom:26px}
.nd-card h1{font-size:26px;font-weight:720;letter-spacing:-.4px;color:#0f172a;margin:0 0 6px}
.nd-card-sub{font-size:14px;color:#64748b;margin:0 0 26px}
.nd-field{display:block;margin:0 0 6px}
.nd-field>span{display:block;font-size:12.5px;font-weight:600;color:#475569;margin-bottom:8px;letter-spacing:.2px}
.nd-inwrap{position:relative;display:flex;align-items:center}
.nd-inwrap>.nd-lock{position:absolute;left:14px;width:18px;height:18px;color:#94a3b8;pointer-events:none}
.nd-inwrap input{width:100%;height:50px;padding:0 46px 0 42px;font-size:15px;color:#0f172a;
background:#f8fafc;border:1.5px solid #dde3ee;border-radius:11px;outline:none;transition:border-color .15s,box-shadow .15s,background .15s}
.nd-inwrap input::placeholder{color:#b4bdcd;letter-spacing:2px}
.nd-inwrap input:focus{border-color:#3d7eff;background:#fff;box-shadow:0 0 0 4px rgba(61,126,255,.14)}
.nd-eye{position:absolute;right:8px;display:inline-flex;align-items:center;justify-content:center;
width:34px;height:34px;border:0;background:transparent;color:#94a3b8;cursor:pointer;border-radius:8px}
.nd-eye:hover{color:#475569;background:#eef2f9}
.nd-eye .off{display:none}.nd-eye.on .on{display:none}.nd-eye.on .off{display:block}
.nd-login-err{display:flex;align-items:center;gap:8px;margin:14px 0 0;padding:10px 13px;font-size:13px;
color:#b42318;background:#fef3f2;border:1px solid #fda29b;border-radius:10px;animation:nd-shake .3s}
@keyframes nd-shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-5px)}75%{transform:translateX(5px)}}
.nd-btn{width:100%;height:50px;margin-top:22px;display:inline-flex;align-items:center;justify-content:center;gap:9px;
font-size:15px;font-weight:650;color:#fff;border:0;border-radius:11px;cursor:pointer;letter-spacing:.2px;
background:linear-gradient(135deg,#4d8bff,#6aa1ff 44%,#34d399);background-size:160% 160%;position:relative;overflow:hidden;z-index:0;
box-shadow:0 10px 24px -10px rgba(45,95,224,.7),inset 0 1px 0 rgba(255,255,255,.22);transition:transform .12s,box-shadow .15s,filter .15s}
.nd-btn::before{content:"";position:absolute;inset:0;z-index:-1;border-radius:inherit;background:linear-gradient(100deg,transparent 22%,rgba(255,255,255,.4) 50%,transparent 78%);transform:translateX(-120%);transition:transform .6s ease}
.nd-btn:hover::before{transform:translateX(120%)}
.nd-btn:hover{filter:brightness(1.05);box-shadow:0 14px 28px -10px rgba(45,95,224,.8)}
.nd-btn:active{transform:translateY(1px)}
.nd-btn .nd-spin{display:none;width:17px;height:17px;border:2.4px solid rgba(255,255,255,.45);border-top-color:#fff;border-radius:50%;animation:nd-rot .7s linear infinite}
.nd-btn.nd-loading .nd-spin{display:inline-block}.nd-btn.nd-loading .nd-btn-lbl{opacity:.75}
@keyframes nd-rot{to{transform:rotate(360deg)}}
.nd-secure{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:22px;
font-size:12px;color:#64748b}
.nd-secure svg{width:14px;height:14px;color:#16a34a}
.nd-legal{max-width:410px;text-align:center;font-size:11.5px;line-height:1.5;color:#94a3b8}
/* dark mode (OS or portal) */
@media (prefers-color-scheme:dark){
.nd-panel{background:#0c1222}
.nd-card{background:#111a30;border-color:#243352;box-shadow:0 24px 60px -24px rgba(0,0,0,.6)}
.nd-card-logo{color:#e6edf8}.nd-card h1{color:#f1f5fb}.nd-card-sub{color:#93a4c4}
.nd-field>span{color:#a7b6d4}
.nd-inwrap input{background:#0d1526;border-color:#2b3b5c;color:#eef2fb}
.nd-inwrap input:focus{background:#0d1526;border-color:#4f8bff}
.nd-eye:hover{background:#1b2742}
.nd-login-err{color:#ffb4ab;background:#2a1414;border-color:#5b2420}
.nd-secure{color:#8aa0c6}.nd-legal{color:#6b7da0}
}
@media (max-width:860px){
.nd-login{flex-direction:column}
.nd-hero{flex:0 0 auto;padding:26px 24px;min-height:auto}
.nd-hero-mid,.nd-hero-foot{display:none}
.nd-panel{flex:1 1 auto;padding:30px 20px}
.nd-card{padding:30px 24px 26px}
}
/* ---- audit-log console ---- */
.au-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;margin-bottom:14px}
.au-ctrls{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.au-in{background:var(--bg2,#10141e);border:1px solid var(--border,#212a3a);border-radius:8px;color:var(--fg,#eaeff8);padding:7px 10px;font-size:13px;font-family:inherit;outline:none}
.au-in:focus{border-color:var(--accent,#3d7eff);box-shadow:0 0 0 3px rgba(61,126,255,.14)}
.au-q{min-width:190px}
.au-btn{background:var(--bg3,#161c28);border:1px solid var(--border,#212a3a);border-radius:8px;color:var(--fg,#eaeff8);padding:7px 13px;font-size:13px;cursor:pointer;font-weight:600;text-decoration:none;display:inline-flex;align-items:center;gap:6px;transition:.15s}
.au-btn:hover{border-color:var(--accent,#3d7eff);transform:translateY(-1px)}
.au-btn-cta{background:linear-gradient(135deg,#4d8bff,#34d399);border-color:transparent;color:#fff;box-shadow:0 6px 16px -6px rgba(61,126,255,.5)}
.audit-feed{max-height:460px;overflow:auto}
.audit-feed .feed-row{display:flex;gap:12px;align-items:baseline;padding:7px 2px;border-bottom:1px solid var(--border-soft,#1a2130);font-size:13.5px}
.audit-feed .feed-row .stats{color:var(--faint,#6b7688);font-size:12px;min-width:150px;font-variant-numeric:tabular-nums}
.audit-feed .kbadge{display:inline-block;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 7px;border-radius:999px;background:rgba(77,139,255,.14);color:#9cc0ff;margin-right:7px;vertical-align:1px}
.audit-feed .kind-deny .kbadge,.audit-feed .kind-authfail .kbadge{background:rgba(239,68,68,.16);color:#fca5a5}
.audit-feed .kind-connect .kbadge,.audit-feed .kind-session .kbadge{background:rgba(52,211,153,.16);color:#6ee7b7}
.audit-feed .act{color:var(--accent2,#6aa1ff);font-size:12px}
</style>
<div id="viewLogin" class="screen nd-login" hidden>
<!-- brand / trust panel -->
<div class="nd-hero">
<div class="nd-hero-brand">
<svg width="30" height="30" viewBox="0 0 32 32"><rect width="32" height="32" rx="8" fill="#3d7eff"/><path d="M8 20l8-12 8 12-8-4z" fill="white"/><rect x="12" y="18" width="8" height="7" rx="2" fill="white"/></svg>
NetDesk
<span class="nd-badge"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg> Secure Console</span>
</div>
<div class="nd-hero-mid">
<h2>Admin&nbsp;Console</h2>
<p>Manage your fleet of unattended machines — hosts, live sessions, users and the full access audit trail — from one private, self-hosted console.</p>
<ul class="nd-points">
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg> End-to-end encrypted remote sessions</li>
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> Self-hosted on your own server</li>
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg> Complete session &amp; access audit trail</li>
</ul>
</div>
<div class="nd-hero-foot">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
NetDesk by Akronlogic · netdesk.space
</div>
</div>
<!-- sign-in -->
<div class="nd-panel">
<div class="nd-card">
<div class="nd-card-logo">
<svg width="24" height="24" viewBox="0 0 32 32"><rect width="32" height="32" rx="7" fill="#3d7eff"/><path d="M8 20l8-12 8 12-8-4z" fill="white"/><rect x="12" y="18" width="8" height="7" rx="2" fill="white"/></svg>
NetDesk Console
</div>
<h1>Sign in</h1>
<p class="nd-card-sub">Enter your administrator password to continue.</p>
<label class="nd-field" for="pwPortal"><span>Console password</span></label>
<div class="nd-inwrap">
<svg class="nd-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<input id="pwPortal" type="password" autocomplete="current-password" placeholder="••••••••••••" aria-label="Console password">
<button type="button" class="nd-eye" aria-label="Show password"
onclick="(function(b){var i=document.getElementById('pwPortal');var s=i.type==='password';i.type=s?'text':'password';b.classList.toggle('on',s);b.setAttribute('aria-label',s?'Hide password':'Show password');i.focus();})(this)">
<svg class="on" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z"/><circle cx="12" cy="12" r="3"/></svg>
<svg class="off" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><path d="M1 1l22 22"/></svg>
</button>
</div>
<div id="loginErr" class="nd-login-err" hidden></div>
<button id="btnLogin" class="nd-btn" type="button"
onclick="var b=this;b.classList.add('nd-loading');setTimeout(function(){b.classList.remove('nd-loading')},2500)">
<span class="nd-spin"></span><span class="nd-btn-lbl">Sign in securely</span>
</button>
<div class="nd-secure">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
Encrypted connection · Authorized personnel only
</div>
</div>
<div class="nd-legal">This is a private administrative system. Unauthorized access is prohibited and all activity may be monitored and logged.</div>
</div>
</div>
<div id="viewDash" hidden>
<aside class="side">
<div class="side-brand">
<svg width="30" height="30" viewBox="0 0 32 32"><rect width="32" height="32" rx="8" fill="#3d7eff"/><path d="M8 20l8-12 8 12-8-4z" fill="white"/><rect x="12" y="18" width="8" height="7" rx="2" fill="white"/></svg>
<div><b>NetDesk</b><div class="side-sub" id="metaServer">console</div></div>
</div>
<nav class="side-nav">
<a href="#sec-hosts" class="nav-it"><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="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg> Computers</a>
<a href="#sec-visitors" class="nav-it"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="10" r="3"/><path d="M12 2a8 8 0 0 0-8 8c0 5 8 12 8 12s8-7 8-12a8 8 0 0 0-8-8z"/></svg> Visitors</a>
<a href="#sec-downloads" class="nav-it"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg> Downloads</a>
<a href="#sec-activity" class="nav-it"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg> Activity</a>
<a href="#sec-setup" class="nav-it"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg> Setup keys</a>
</nav>
<div class="side-foot">
<a href="/" class="nav-it"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><path d="M15 3h6v6M10 14L21 3"/></svg> Open viewer</a>
<a href="/portal/logout" class="nav-it"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/></svg> Sign out</a>
<button id="btnThemeP" class="theme-toggle" style="width:100%;justify-content:center;margin-top:8px">Theme</button>
</div>
</aside>
<main class="mainc">
<section class="stat-row">
<div class="stat"><div class="stat-n" id="stOnline">-</div><div class="stat-l">online</div></div>
<div class="stat"><div class="stat-n" id="stTotal">-</div><div class="stat-l">computers</div></div>
<div class="stat"><div class="stat-n" id="stSessions">-</div><div class="stat-l">sessions</div></div>
<div class="stat"><div class="stat-n" id="stTraffic">-</div><div class="stat-l">relayed</div></div>
<div class="stat"><div class="stat-n" id="stUptime">-</div><div class="stat-l">uptime</div></div>
</section>
<section class="p-card" id="sec-users" hidden>
<h2>Users</h2>
<table class="tbl" id="tblUsers">
<thead><tr><th>Email</th><th>Verified</th><th>Joined</th><th></th></tr></thead>
<tbody></tbody>
</table>
</section>
<section class="p-card" id="sec-hosts">
<h2>Computers</h2>
<table class="tbl" id="tblHosts">
<thead><tr><th>Status</th><th>Name</th><th>ID</th><th>Version</th><th>Last seen</th><th>Live · CPU / RAM / SSD</th><th></th></tr></thead>
<tbody></tbody>
</table>
<div id="noHosts" class="muted" hidden>No computers yet - run NetDeskHost on a PC and point it at this server.</div>
</section>
<style>
.pill{display:inline-block;padding:1px 8px;border-radius:999px;font-size:11px;font-weight:600;background:rgba(61,126,255,.14);color:#3d7eff;vertical-align:middle}
.pill-hidden{display:inline-block;padding:1px 7px;border-radius:999px;font-size:10px;font-weight:700;letter-spacing:.4px;background:rgba(148,163,184,.22);color:#6b7a90;vertical-align:middle}
.sm,.muted.sm{font-size:11px;opacity:.72}
#sec-visitors h2 .sub{font-size:12px;font-weight:400;color:#8a97ab;margin-left:8px}
.metrics-cell{min-width:190px}
.mbar{display:flex;align-items:center;gap:6px;font-size:10.5px;margin:2px 0}
.mlab{width:26px;color:#8a97ab;font-weight:700;letter-spacing:.3px}
.mtrack{flex:1;height:7px;background:rgba(148,163,184,.22);border-radius:4px;overflow:hidden;min-width:70px}
.mfill{display:block;height:100%;border-radius:4px;transition:width .5s ease}
.mpct{width:32px;text-align:right;color:#8a97ab;font-variant-numeric:tabular-nums}
.bat{font-size:11px;margin-top:3px;color:#8a97ab}
</style>
<section class="p-card" id="sec-visitors">
<h2>Live visitors<span class="sub">IP intelligence &amp; location</span></h2>
<table class="tbl" id="tblVisitors">
<thead><tr><th>Status</th><th>IP address</th><th>Location / ISP</th><th>Host</th><th>Who</th><th>Since</th></tr></thead>
<tbody></tbody>
</table>
<div id="noVis" class="muted" hidden>No one is connected right now.</div>
<h3 style="margin:18px 0 8px;font-size:14px">Recent connections</h3>
<div id="visRecent" class="feed"></div>
</section>
<div class="p-cols">
<section class="p-card" id="sec-downloads">
<h2>Downloads</h2>
<div id="dlList" class="muted">Put agent/server binaries in the server files folder to offer them here.</div>
</section>
<section class="p-card" id="sec-activity">
<div class="au-head">
<h2 style="margin:0">Audit log</h2>
<div class="au-ctrls">
<select id="auKind" class="au-in" title="Event type"><option value="">All events</option></select>
<input id="auQ" class="au-in au-q" type="text" placeholder="Search message or actor…" autocomplete="off">
<select id="auLimit" class="au-in" title="Rows">
<option value="200">200</option><option value="500" selected>500</option><option value="2000">2000</option>
</select>
<button id="auRefresh" class="au-btn" type="button">Refresh</button>
<a id="auCsv" class="au-btn au-btn-cta" href="/portal/api/audit.csv">↓ Export CSV</a>
</div>
</div>
<div id="feed" class="feed audit-feed"></div>
</section>
</div>
<section class="p-card" id="sec-setup">
<h2>Setup keys</h2>
<div class="keyrow">
<div>
<div class="key-l">Registration key - hosts present this to join your server</div>
<div class="key-v mono" id="kvReg">-</div>
</div>
<button id="btnCopyReg" class="tb">Copy</button>
</div>
<div class="keyrow">
<div>
<div class="key-l">Server address for the NetDesk app (wss + your domain)</div>
<div class="key-v mono" id="kvServer">-</div>
</div>
<button id="btnCopySrv" class="tb">Copy</button>
</div>
</section>
</main>
</div>
<script src="/portal.js?v=27"></script>
</body>
</html>