187 lines
9.3 KiB
PHP
187 lines
9.3 KiB
PHP
<?php
|
|
if (!defined('RELAY_BOOTSTRAPPED')) { http_response_code(404); exit; }
|
|
?><!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="theme-color" content="#1e293b">
|
|
<title>hyprcrd relay</title>
|
|
<link rel="stylesheet" href="fonts/phosphor.css">
|
|
<link rel="stylesheet" href="ui/style.css">
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<span class="dot"></span>
|
|
<h1>hyprcrd relay</h1>
|
|
<span id="header-status" class="pixel" style="font-size:9px;color:var(--dim);"></span>
|
|
<span class="spacer"></span>
|
|
<div id="nav-buttons" class="hidden" style="display:flex;gap:6px;align-items:center;">
|
|
<span id="nav-user" class="user"></span>
|
|
<button id="nav-compose" class="nav-btn active" onclick="showView('compose')"><i class="ph ph-pencil-line"></i> compose</button>
|
|
<button id="nav-account" class="nav-btn" onclick="showView('account')"><i class="ph ph-user-circle"></i> account</button>
|
|
<button id="nav-admin" class="nav-btn hidden" onclick="showView('admin')"><i class="ph ph-users"></i> users</button>
|
|
<button class="nav-btn danger" onclick="doLogout()"><i class="ph ph-sign-out"></i> logout</button>
|
|
</div>
|
|
</header>
|
|
|
|
<main>
|
|
|
|
<!-- ── Setup view (first install) ── -->
|
|
<div id="view-setup" class="auth-form hidden">
|
|
<h2>welcome to relay</h2>
|
|
<p>This is the first time the relay is being set up. The user you create here will become the <strong>admin</strong>. You'll also set the <strong>super-shared secret</strong> — a relay-wide key used to route messages between tenants.</p>
|
|
<div class="field">
|
|
<label>admin username</label>
|
|
<input type="text" id="setup-username" placeholder="alice" autocomplete="username">
|
|
</div>
|
|
<div class="field">
|
|
<label>admin password</label>
|
|
<input type="password" id="setup-password" placeholder="min 4 chars" autocomplete="new-password">
|
|
</div>
|
|
<div class="field">
|
|
<label>super-shared secret</label>
|
|
<input type="password" id="setup-super-secret" placeholder="min 12 chars" autocomplete="new-password">
|
|
<p style="font-size:9px;color:var(--dim);margin-top:4px;line-height:1.4;">Used by the relay to encrypt messages at rest and route them between tenants. Set once, can't be changed without re-setup.</p>
|
|
</div>
|
|
<button class="btn btn-primary" onclick="doSetup()"><i class="ph ph-user-circle-plus"></i> create admin + activate relay</button>
|
|
<div id="setup-err" class="err"></div>
|
|
</div>
|
|
|
|
<!-- ── Login view ── -->
|
|
<div id="view-login" class="auth-form hidden">
|
|
<h2>login</h2>
|
|
<div class="field">
|
|
<label>username</label>
|
|
<input type="text" id="login-username" autocomplete="username">
|
|
</div>
|
|
<div class="field">
|
|
<label>password</label>
|
|
<input type="password" id="login-password" autocomplete="current-password">
|
|
</div>
|
|
<button class="btn btn-primary" onclick="doLogin()"><i class="ph ph-sign-in"></i> unlock</button>
|
|
<div id="login-err" class="err"></div>
|
|
</div>
|
|
|
|
<!-- ── Compose view ── -->
|
|
<div id="view-compose" class="hidden">
|
|
<div class="compose-card">
|
|
<h2><i class="ph ph-paper-plane-tilt"></i> compose</h2>
|
|
<div class="field" id="compose-recipient-field">
|
|
<label>recipient</label>
|
|
<select id="compose-recipient"></select>
|
|
</div>
|
|
<div class="field">
|
|
<label>title (optional)</label>
|
|
<input type="text" id="compose-title" autocomplete="off">
|
|
</div>
|
|
<div class="field">
|
|
<label>body</label>
|
|
<textarea id="compose-body" placeholder="write something..."></textarea>
|
|
</div>
|
|
<div class="actions">
|
|
<button class="btn btn-secondary" onclick="clearCompose()"><i class="ph ph-x"></i> clear</button>
|
|
<span class="spacer"></span>
|
|
<span id="compose-status" class="status"></span>
|
|
<button class="btn btn-primary" onclick="sendCompose()"><i class="ph ph-paper-plane-tilt"></i> send</button>
|
|
</div>
|
|
</div>
|
|
<div id="inbox-section" class="section">
|
|
<h2><i class="ph ph-tray"></i> your inbox <span class="count" id="inbox-count">0</span></h2>
|
|
<div id="inbox-list"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Forced password change (first login) ── -->
|
|
<div id="view-change-password" class="auth-form hidden">
|
|
<h2>change your password</h2>
|
|
<p>This is your first login. Please choose a new password before you can use the relay.</p>
|
|
<div class="field">
|
|
<label>current password</label>
|
|
<input type="password" id="cp-current" placeholder="the one the admin gave you" autocomplete="current-password">
|
|
</div>
|
|
<div class="field">
|
|
<label>new password</label>
|
|
<input type="password" id="cp-new" placeholder="min 4 chars" autocomplete="new-password">
|
|
</div>
|
|
<div class="field">
|
|
<label>confirm new password</label>
|
|
<input type="password" id="cp-new2" placeholder="repeat" autocomplete="new-password">
|
|
</div>
|
|
<button class="btn btn-primary" onclick="doChangePassword()"><i class="ph ph-key"></i> change password</button>
|
|
<div id="cp-err" class="err"></div>
|
|
</div>
|
|
|
|
<!-- ── Account view ── -->
|
|
<div id="view-account" class="hidden">
|
|
<div class="section">
|
|
<h2><i class="ph ph-user-circle"></i> your client secret</h2>
|
|
<p style="font-size:11px;color:var(--muted);line-height:1.5;margin-bottom:14px;">
|
|
Your hyprcrd clients use this secret to encrypt messages before sending them to the relay.
|
|
All your devices share the same secret. Other tenants never see it. The relay stores it
|
|
encrypted with the master key so it can route your messages.
|
|
</p>
|
|
<div id="account-no-secret" class="hidden notice warn" style="margin-bottom:14px;">
|
|
⚠ no secret set yet — your hyprcrd clients can't connect until you generate or set one below
|
|
</div>
|
|
<div id="account-current" class="hidden" style="margin-bottom:14px;">
|
|
<label class="field-label" style="display:block;margin-bottom:4px;">current secret</label>
|
|
<div style="display:flex;gap:8px;align-items:stretch;">
|
|
<input id="account-secret-display" type="password" readonly class="w-full" style="flex:1;background:var(--bg);border:1px solid var(--border);color:var(--bright);padding:9px 12px;font-size:13px;font-family:'JetBrains Mono',monospace;border-radius:3px;">
|
|
<button class="btn btn-secondary" onclick="toggleAccountSecret()" id="account-secret-toggle"><i class="ph ph-eye"></i> show</button>
|
|
<button class="btn btn-secondary" onclick="copyAccountSecret()"><i class="ph ph-copy"></i> copy</button>
|
|
</div>
|
|
</div>
|
|
<div style="display:grid;grid-template-columns:1fr;gap:14px;">
|
|
<div>
|
|
<label class="field-label">set your own secret</label>
|
|
<div style="display:flex;gap:8px;">
|
|
<input id="account-secret-input" type="text" placeholder="min 8 chars" style="flex:1;background:var(--bg);border:1px solid var(--border);color:var(--bright);padding:9px 12px;font-size:13px;font-family:'JetBrains Mono',monospace;border-radius:3px;">
|
|
<button class="btn btn-primary" onclick="saveAccountSecret()"><i class="ph ph-floppy-disk"></i> save</button>
|
|
</div>
|
|
</div>
|
|
<div style="text-align:center;color:var(--dim);font-family:var(--pixel);font-size:9px;">— or —</div>
|
|
<button class="btn btn-secondary" onclick="generateAccountSecret()"><i class="ph ph-dice-five"></i> generate random secret</button>
|
|
</div>
|
|
<div id="account-status" class="status" style="margin-top:12px;"></div>
|
|
<div style="margin-top:24px;padding-top:14px;border-top:1px solid var(--border);">
|
|
<button class="btn btn-danger" onclick="clearAccountSecret()"><i class="ph ph-x-circle"></i> clear secret (disconnect all clients)</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Admin view ── -->
|
|
<div id="view-admin" class="hidden">
|
|
<div class="section">
|
|
<h2><i class="ph ph-users-three"></i> users <span class="count" id="users-count">0</span></h2>
|
|
<div id="users-list"></div>
|
|
<div class="create-user-form">
|
|
<div>
|
|
<label for="new-username" class="field-label">username</label>
|
|
<input type="text" id="new-username" placeholder="alice" autocomplete="off">
|
|
</div>
|
|
<div>
|
|
<label for="new-password" class="field-label">password</label>
|
|
<input type="password" id="new-password" placeholder="min 4 chars" autocomplete="new-password">
|
|
</div>
|
|
<label class="checkbox">
|
|
<input type="checkbox" id="new-is-admin"> grant admin privileges
|
|
</label>
|
|
<p style="font-size:9px;color:var(--dim);line-height:1.4;margin-top:2px;">The new user sets their own client secret on first login.</p>
|
|
<div class="actions">
|
|
<button class="btn btn-primary" onclick="createUser()"><i class="ph ph-user-plus"></i> create user</button>
|
|
<span id="create-user-status" class="status"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</main>
|
|
|
|
<div class="toast" id="toast"></div>
|
|
|
|
<script src="ui/app.js"></script>
|
|
</body>
|
|
</html>
|