mirror of
git://f0xx.org/ac/ac-deploy
synced 2026-07-29 06:17:57 +03:00
feat(deploy): lab-seeds sync, msmtp nginx-readable secret, hub downloads
Signed-off-by: Anton Afanasyeu <a.afanasieff@gmail.com>
This commit is contained in:
@@ -11,6 +11,12 @@
|
||||
* Digest: SHA256 21e56cd12df6a94df41a1c0d67be530bd01454865ee1f032cef5f8d2e497509a
|
||||
*/
|
||||
?>
|
||||
<?php
|
||||
$__navProjectBase = Auth::projectBasePath();
|
||||
$__navIssuesBase = rtrim((string) cfg('links.crashes', $__navProjectBase . '/issues'), '/');
|
||||
$__navActive = platform_nav_active_from_view($view ?? null);
|
||||
$__navUser = trim((string) (Auth::user()['username'] ?? ''));
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="dark">
|
||||
<head>
|
||||
@@ -25,8 +31,6 @@
|
||||
if (l === 'en' || l === 'ru') document.documentElement.setAttribute('lang', l);
|
||||
})();
|
||||
</script>
|
||||
<link rel="icon" type="image/x-icon" href="<?= h(Auth::basePath()) ?>/assets/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="<?= h(Auth::basePath()) ?>/assets/apple-touch-icon.ico">
|
||||
<link rel="stylesheet" href="<?= h(Auth::basePath()) ?>/assets/css/app.css">
|
||||
<script src="<?= h(Auth::basePath()) ?>/assets/js/nav_shell.js" defer></script>
|
||||
<script src="<?= h(Auth::basePath()) ?>/assets/js/i18n.js" defer></script>
|
||||
@@ -51,6 +55,19 @@
|
||||
<?php if (($view ?? '') === 'rbac'): ?>
|
||||
<script src="<?= h(Auth::basePath()) ?>/assets/js/rbac_admin.js" defer></script>
|
||||
<?php endif; ?>
|
||||
<?php if (($view ?? '') === 'media_pipelines'): ?>
|
||||
<link rel="stylesheet" href="<?= h(Auth::basePath()) ?>/assets/vendor/nuevo-player/vdjs/v10.0.0/skins/treso/videojs.min.css">
|
||||
<style>
|
||||
.media-pipelines-player-wrap { max-width: 960px; margin: 0.5rem 0 1rem; background: var(--surface-2, #111); border-radius: 8px; overflow: hidden; }
|
||||
.media-pipelines-player-wrap .video-js { width: 100%; }
|
||||
.tag-field--wide { flex: 2 1 280px; min-width: 200px; }
|
||||
.btn--sm { font-size: 0.8rem; padding: 0.2rem 0.5rem; margin-right: 0.25rem; }
|
||||
</style>
|
||||
<script src="<?= h(Auth::basePath()) ?>/assets/vendor/nuevo-player/js/video.min.js" defer></script>
|
||||
<script src="<?= h(Auth::basePath()) ?>/assets/vendor/nuevo-player/js/videojs-contrib-hls.min.js" defer></script>
|
||||
<script src="<?= h(Auth::basePath()) ?>/assets/vendor/nuevo-player/js/nuevo.min.js" defer></script>
|
||||
<script src="<?= h(Auth::basePath()) ?>/assets/js/media_pipelines.js" defer></script>
|
||||
<?php endif; ?>
|
||||
<?php AnalyticsHead::render('crashes'); ?>
|
||||
</head>
|
||||
<body data-base-path="<?= h(console_base_path($view ?? null)) ?>"
|
||||
@@ -62,188 +79,16 @@
|
||||
<?= Rbac::can('remote_access_admin') ? ' data-can-ra-admin="1"' : '' ?>
|
||||
<?= Rbac::can('short_links_operate') ? ' data-can-sl-operate="1"' : '' ?>
|
||||
<?= Rbac::isGlobalAdmin() ? ' data-can-sl-admin="1"' : '' ?>
|
||||
<?= Rbac::isRoot() ? ' data-can-rbac-root="1"' : '' ?>>
|
||||
<?= Rbac::isRoot() ? ' data-can-rbac-root="1"' : '' ?>
|
||||
<?= (cfg('media_pipelines.enabled', false) && (Rbac::isGlobalAdmin() || Rbac::can('media_pipelines_operate'))) ? ' data-can-mp-operate="1"' : '' ?>>
|
||||
<header class="top-menu" hidden aria-hidden="true"></header>
|
||||
<div class="shell<?= ($view ?? '') === 'graphs' ? ' shell--graphs-full' : '' ?>">
|
||||
<nav class="nav-pane" id="nav-pane" aria-label="Console navigation">
|
||||
<button type="button" class="nav-handle" id="nav-handle" data-i18n-aria="nav.toggle" data-i18n-title="nav.toggle" aria-label="Toggle navigation" title="Navigation">
|
||||
<span class="nav-icon nav-icon--menu" aria-hidden="true"></span>
|
||||
</button>
|
||||
<ul class="nav-list">
|
||||
<li>
|
||||
<a href="<?= h(Auth::basePath()) ?>/?view=home"
|
||||
class="nav-link <?= ($view ?? '') === 'home' ? 'active' : '' ?>"
|
||||
data-i18n-aria="nav.home" data-i18n-title="nav.home"
|
||||
aria-label="Home"
|
||||
title="Home">
|
||||
<span class="nav-icon nav-icon--home" aria-hidden="true"></span>
|
||||
<span class="nav-text">
|
||||
<span class="nav-label" data-i18n="nav.home">Home</span>
|
||||
<span class="nav-desc" data-i18n="nav.home_desc">Console home and workspace cards.</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?= h(Auth::basePath()) ?>/?view=reports"
|
||||
class="nav-link <?= ($view ?? '') === 'reports' || ($view ?? '') === 'report' ? 'active' : '' ?>"
|
||||
data-i18n-aria="nav.reports" data-i18n-title="nav.reports"
|
||||
aria-label="Reports"
|
||||
title="Reports">
|
||||
<span class="nav-icon nav-icon--reports" aria-hidden="true"></span>
|
||||
<span class="nav-text">
|
||||
<span class="nav-label" data-i18n="nav.reports">Reports</span>
|
||||
<span class="nav-desc" data-i18n="nav.reports_desc">Browse and triage issues.</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?= h(Auth::basePath()) ?>/?view=tickets"
|
||||
class="nav-link <?= ($view ?? '') === 'tickets' || ($view ?? '') === 'ticket' ? 'active' : '' ?>"
|
||||
data-i18n-aria="nav.tickets" data-i18n-title="nav.tickets"
|
||||
aria-label="Tickets"
|
||||
title="Tickets">
|
||||
<span class="nav-icon nav-icon--tickets" aria-hidden="true"></span>
|
||||
<span class="nav-text">
|
||||
<span class="nav-label" data-i18n="nav.tickets">Tickets</span>
|
||||
<span class="nav-desc" data-i18n="nav.tickets_desc">Roadmap tasks, QA items, and tags.</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/app/androidcast_project/graphs/"
|
||||
class="nav-link <?= ($view ?? '') === 'graphs' ? 'active' : '' ?>"
|
||||
aria-label="Analytics"
|
||||
title="Analytics">
|
||||
<span class="nav-icon nav-icon--graphs" aria-hidden="true"></span>
|
||||
<span class="nav-text">
|
||||
<span class="nav-label" data-i18n="nav.graphs">Analytics</span>
|
||||
<span class="nav-desc" data-i18n="nav.graphs_desc">Sessions, issues, and device activity.</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/app/androidcast_project/monitor/"
|
||||
class="nav-link"
|
||||
aria-label="Monitoring"
|
||||
title="Monitoring">
|
||||
<span class="nav-icon nav-icon--monitor" aria-hidden="true"></span>
|
||||
<span class="nav-text">
|
||||
<span class="nav-label" data-i18n="nav.monitor">Monitoring</span>
|
||||
<span class="nav-desc" data-i18n="nav.monitor_desc">Grafana dashboards — cluster health, alerts, exporters.</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?= h(Auth::basePath()) ?>/?view=live_sessions"
|
||||
class="nav-link <?= ($view ?? '') === 'live_sessions' ? 'active' : '' ?>"
|
||||
aria-label="Live sessions"
|
||||
title="Live sessions">
|
||||
<span class="nav-icon nav-icon--live" aria-hidden="true"></span>
|
||||
<span class="nav-text">
|
||||
<span class="nav-label">Live sessions</span>
|
||||
<span class="nav-desc">Cast intents, join stats, and session history.</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?= h(Auth::basePath()) ?>/live/education"
|
||||
class="nav-link"
|
||||
aria-label="Education demo"
|
||||
title="Education demo">
|
||||
<span class="nav-icon nav-icon--education" aria-hidden="true"></span>
|
||||
<span class="nav-text">
|
||||
<span class="nav-label">Education demo</span>
|
||||
<span class="nav-desc">Browser screen-share trial (5 min).</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php if (Rbac::can('remote_access_view')): ?>
|
||||
<li>
|
||||
<a href="<?= h(Auth::basePath()) ?>/?view=remote_access"
|
||||
class="nav-link <?= ($view ?? '') === 'remote_access' ? 'active' : '' ?>"
|
||||
aria-label="Remote access"
|
||||
title="Remote access">
|
||||
<span class="nav-icon nav-icon--remote" aria-hidden="true"></span>
|
||||
<span class="nav-text">
|
||||
<span class="nav-label" data-i18n="nav.remote">Remote access</span>
|
||||
<span class="nav-desc" data-i18n="nav.remote_desc">WireGuard sessions and device reachability.</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (Rbac::can('short_links_view')): ?>
|
||||
<li>
|
||||
<a href="<?= h(Auth::basePath()) ?>/?view=short_links"
|
||||
class="nav-link <?= ($view ?? '') === 'short_links' ? 'active' : '' ?>"
|
||||
aria-label="Short links"
|
||||
title="Short links">
|
||||
<span class="nav-icon nav-icon--link" aria-hidden="true"></span>
|
||||
<span class="nav-text">
|
||||
<span class="nav-label" data-i18n="home.card_short_links">Short links</span>
|
||||
<span class="nav-desc" data-i18n="home.card_short_links_desc">Mint bearer tokens and shorten URLs for s.f0xx.org.</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (Rbac::canManageRbac()): ?>
|
||||
<li>
|
||||
<a href="<?= h(Auth::basePath()) ?>/?view=rbac"
|
||||
class="nav-link <?= ($view ?? '') === 'rbac' ? 'active' : '' ?>"
|
||||
aria-label="Access control"
|
||||
title="Access control">
|
||||
<span class="nav-icon nav-icon--tickets" aria-hidden="true"></span>
|
||||
<span class="nav-text">
|
||||
<span class="nav-label" data-i18n="nav.access">Access</span>
|
||||
<span class="nav-desc" data-i18n="nav.access_desc">Roles, privilege sets, and operators.</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<li>
|
||||
<a href="/app/androidcast_project/build/"
|
||||
class="nav-link"
|
||||
aria-label="Builder"
|
||||
title="Builder">
|
||||
<span class="nav-icon nav-icon--builder" aria-hidden="true"></span>
|
||||
<span class="nav-text">
|
||||
<span class="nav-label" data-i18n="nav.builder">Builder</span>
|
||||
<span class="nav-desc" data-i18n="nav.builder_desc">Docker CI for APK baking and OTA artifacts.</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="nav-locale">
|
||||
<label class="toolbar-select locale-toolbar-select" data-i18n-title="lang.label" title="Language">
|
||||
<span class="locale-flag" id="locale-flag" aria-hidden="true">🇬🇧</span>
|
||||
<select class="lang-select" id="lang-select" data-i18n-aria="lang.label" aria-label="Language">
|
||||
<option value="en">EN</option>
|
||||
<option value="ru">RU</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="nav-user">
|
||||
<span class="nav-user-name" title="<?= h(Auth::user()['username'] ?? '') ?>">
|
||||
<span class="nav-icon nav-icon--user" aria-hidden="true"></span>
|
||||
<span class="nav-label"><?= h(Auth::user()['username'] ?? '') ?></span>
|
||||
</span>
|
||||
<a href="<?= h(Auth::basePath()) ?>/account-security"
|
||||
class="nav-link"
|
||||
data-i18n-aria="nav.security" data-i18n-title="nav.security"
|
||||
aria-label="Security"
|
||||
title="Security">
|
||||
<span class="nav-icon nav-icon--user" aria-hidden="true"></span>
|
||||
<span class="nav-label" data-i18n="nav.security">Security</span>
|
||||
</a>
|
||||
<a href="<?= h(Auth::authUrl('/logout')) ?>"
|
||||
class="nav-link nav-link--logout"
|
||||
data-i18n-aria="nav.logout" data-i18n-title="nav.logout"
|
||||
aria-label="Logout"
|
||||
title="Logout">
|
||||
<span class="nav-icon nav-icon--logout" aria-hidden="true"></span>
|
||||
<span class="nav-label" data-i18n="nav.logout">Logout</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<?php platform_render_nav_shell([
|
||||
'project_base' => $__navProjectBase,
|
||||
'issues_base' => $__navIssuesBase,
|
||||
'active' => $__navActive,
|
||||
'username' => $__navUser,
|
||||
]); ?>
|
||||
<main class="main-pane">
|
||||
<?php if (($view ?? 'home') === 'home'): ?>
|
||||
<?php require __DIR__ . '/partials/console_home_landing.php'; ?>
|
||||
@@ -607,6 +452,91 @@
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?php elseif (($view ?? '') === 'media_pipelines'): ?>
|
||||
<div id="media-pipelines-app" class="reports-app">
|
||||
<div class="toolbar reports-toolbar">
|
||||
<h1>Media pipelines <span class="muted" style="font-size:0.65em;font-weight:normal">(experimental)</span></h1>
|
||||
<div class="toolbar-actions">
|
||||
<button type="button" class="btn" id="mp-refresh">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php require __DIR__ . '/partials/console_quick_links.php'; ?>
|
||||
<p id="mp-status" class="reports-status muted" aria-live="polite">Loading…</p>
|
||||
<p class="muted graphs-footnote">
|
||||
Lab-only restream control plane. Requires <code>streaming_v2</code> on BE, ffmpeg, yt-dlp.
|
||||
Visible when <code>media_pipelines.enabled</code> and global admin / privilege grant.
|
||||
</p>
|
||||
|
||||
<section class="graphs-scope" id="mp-create-section">
|
||||
<h2 class="graphs-scope-title">New pipeline</h2>
|
||||
<form id="mp-create-form" class="tag-editor-add">
|
||||
<label class="tag-field"><span>Name</span><input type="text" name="name" maxlength="128" placeholder="My restream"></label>
|
||||
<label class="tag-field tag-field--wide"><span>Source URL</span><input type="url" name="source_url" required placeholder="https://… or /path/file.mkv"></label>
|
||||
<label class="tag-field"><span>yt-dlp format</span><input type="text" name="source_fmt" placeholder="bv*+ba/b"></label>
|
||||
<label class="tag-field tag-field--wide"><span>Separate audio URL</span><input type="url" name="audio_source" placeholder="optional"></label>
|
||||
<label class="tag-field">
|
||||
<span>Filter</span>
|
||||
<select name="filter_profile">
|
||||
<option value="default">Logo overlay</option>
|
||||
<option value="blurred">Blurred + logo</option>
|
||||
<option value="nologo">No logo</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="tag-field">
|
||||
<span>Preset</span>
|
||||
<select name="output_preset">
|
||||
<option value="1080p">1080p</option>
|
||||
<option value="720p">720p</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="tag-field">
|
||||
<span>Mode</span>
|
||||
<select name="run_mode">
|
||||
<option value="full">Full (encode + RTMP fanout)</option>
|
||||
<option value="encode_only">Encode only (record + HLS)</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="submit" class="btn" id="mp-create-btn">Create pipeline</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="graphs-scope">
|
||||
<h2 class="graphs-scope-title">Pipelines</h2>
|
||||
<div class="reports-table-wrap">
|
||||
<table class="data-table reports-table--cols" id="mp-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Mode</th>
|
||||
<th>Source</th>
|
||||
<th>HLS</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="mp-tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="graphs-scope" id="mp-player-section" hidden>
|
||||
<h2 class="graphs-scope-title">Preview</h2>
|
||||
<div class="media-pipelines-player-wrap">
|
||||
<video id="mp-player" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto" width="960" height="540"></video>
|
||||
</div>
|
||||
<p class="muted graphs-footnote" id="mp-player-url"></p>
|
||||
</section>
|
||||
|
||||
<section class="graphs-scope" id="mp-probe-section" hidden>
|
||||
<h2 class="graphs-scope-title">Source profile (ffprobe)</h2>
|
||||
<pre id="mp-probe-json" class="graph-detail-body" style="max-height:240px;overflow:auto"></pre>
|
||||
</section>
|
||||
|
||||
<section class="graphs-scope" id="mp-logs-section" hidden>
|
||||
<h2 class="graphs-scope-title">Encode log (tail)</h2>
|
||||
<pre id="mp-logs" class="graph-detail-body" style="max-height:200px;overflow:auto;font-size:0.75rem"></pre>
|
||||
</section>
|
||||
</div>
|
||||
<?php elseif (($view ?? '') === 'short_links'): ?>
|
||||
<div id="short-links-app" class="reports-app">
|
||||
<div class="toolbar reports-toolbar">
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
$bp = Auth::basePath();
|
||||
$projectBase = Auth::projectBasePath();
|
||||
$issuesBase = rtrim((string) cfg('links.crashes', $projectBase . '/issues'), '/');
|
||||
$navUser = trim((string) (Auth::user()['username'] ?? ''));
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="dark">
|
||||
@@ -12,38 +15,49 @@ $bp = Auth::basePath();
|
||||
(function () {
|
||||
var t = localStorage.getItem('crash_console_theme');
|
||||
if (t === 'light' || t === 'dark') document.documentElement.setAttribute('data-theme', t);
|
||||
var l = localStorage.getItem('crash_console_lang');
|
||||
if (l === 'en' || l === 'ru') document.documentElement.setAttribute('lang', l);
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="<?= h($bp) ?>/assets/css/app.css">
|
||||
<script src="<?= h($bp) ?>/assets/js/nav_shell.js" defer></script>
|
||||
<script src="<?= h($bp) ?>/assets/js/i18n.js" defer></script>
|
||||
<script src="<?= h($bp) ?>/assets/js/cookie_consent.js" defer></script>
|
||||
<?php AnalyticsHead::render('live_education'); ?>
|
||||
<script src="<?= h($bp) ?>/assets/js/live_cast_webrtc.js" defer></script>
|
||||
<script src="<?= h($bp) ?>/assets/js/live_education.js" defer></script>
|
||||
</head>
|
||||
<body class="live-page" data-base-path="<?= h($bp) ?>" data-view="live_education">
|
||||
<main id="live-education-app" class="live-shell">
|
||||
<header class="live-header">
|
||||
<a href="<?= h(Auth::projectBasePath()) ?>/" class="live-brand">← Back to the console</a>
|
||||
<span class="tag-pill">Education demo</span>
|
||||
</header>
|
||||
<section class="card card--lift live-card">
|
||||
<h1>Try screen sharing in your browser</h1>
|
||||
<p class="muted">Free demo up to 5 minutes. No install required — great for first-time visitors.</p>
|
||||
<p id="edu-status" class="reports-status muted" aria-live="polite">Ready when you are.</p>
|
||||
<p class="live-timer-label">Time left: <strong id="edu-timer">5:00</strong></p>
|
||||
<video id="edu-preview" class="live-preview" playsinline muted autoplay hidden></video>
|
||||
<div id="edu-share" class="live-share card card--lift" hidden></div>
|
||||
<pre id="edu-stats" class="live-stats-nerds" hidden aria-live="polite"></pre>
|
||||
<div class="live-actions">
|
||||
<button type="button" class="btn btn--primary" id="edu-start">Start screen share</button>
|
||||
<button type="button" class="btn" id="edu-stop">Stop</button>
|
||||
<button type="button" class="btn btn--ghost" id="edu-notify">Enable notifications</button>
|
||||
</div>
|
||||
<p class="muted graphs-footnote">
|
||||
WebRTC P2P (no SFU): share the viewer link or QR. Same REST signaling API supports future mobile cast to browser on LAN.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
<div class="shell">
|
||||
<?php platform_render_nav_shell([
|
||||
'project_base' => $projectBase,
|
||||
'issues_base' => $issuesBase,
|
||||
'active' => 'education',
|
||||
'username' => $navUser,
|
||||
]); ?>
|
||||
<main class="main-pane" id="live-education-app">
|
||||
<section class="card card--lift live-card">
|
||||
<header class="live-header">
|
||||
<span class="tag-pill">Education demo</span>
|
||||
</header>
|
||||
<h1>Try screen sharing in your browser</h1>
|
||||
<p class="muted">Free demo up to 5 minutes. No install required — great for first-time visitors.</p>
|
||||
<p id="edu-status" class="reports-status muted" aria-live="polite">Ready when you are.</p>
|
||||
<p class="live-timer-label">Time left: <strong id="edu-timer">5:00</strong></p>
|
||||
<video id="edu-preview" class="live-preview" playsinline muted autoplay hidden></video>
|
||||
<div id="edu-share" class="live-share card card--lift" hidden></div>
|
||||
<pre id="edu-stats" class="live-stats-nerds" hidden aria-live="polite"></pre>
|
||||
<div class="live-actions">
|
||||
<button type="button" class="btn btn--primary" id="edu-start">Start screen share</button>
|
||||
<button type="button" class="btn" id="edu-stop">Stop</button>
|
||||
<button type="button" class="btn btn--ghost" id="edu-notify">Enable notifications</button>
|
||||
</div>
|
||||
<p class="muted graphs-footnote">
|
||||
WebRTC P2P (no SFU): share the viewer link or QR. Same REST signaling API supports future mobile cast to browser on LAN.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
<?php require __DIR__ . '/partials/cookie_consent.php'; ?>
|
||||
<?php platform_render_footer(); ?>
|
||||
</body>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
$bp = Auth::basePath();
|
||||
$auth = Auth::authUrl();
|
||||
$registerPending = !empty($registerPending);
|
||||
$registerExpiresAt = (string) ($registerExpiresAt ?? '');
|
||||
$registerEmail = (string) ($registerEmail ?? '');
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="dark">
|
||||
@@ -16,11 +19,18 @@ $auth = Auth::authUrl();
|
||||
if (l === 'en' || l === 'ru') document.documentElement.setAttribute('lang', l);
|
||||
})();
|
||||
</script>
|
||||
<link rel="icon" type="image/x-icon" href="<?= h($bp) ?>/assets/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="<?= h($bp) ?>/assets/apple-touch-icon.ico">
|
||||
<link rel="stylesheet" href="<?= h($bp) ?>/assets/css/app.css">
|
||||
<script src="<?= h($bp) ?>/assets/js/i18n.js" defer></script>
|
||||
<script src="<?= h($bp) ?>/assets/js/register.js" defer></script>
|
||||
<?php AnalyticsHead::render('crashes_register'); ?>
|
||||
</head>
|
||||
<body class="login-page" data-base-path="<?= h($bp) ?>">
|
||||
<body class="login-page" data-base-path="<?= h($bp) ?>"
|
||||
data-auth-base="<?= h($auth) ?>"
|
||||
<?= $registerPending ? ' data-register-pending="1"' : '' ?>
|
||||
<?= $registerExpiresAt !== '' ? ' data-register-expires-ts="' . (int) strtotime($registerExpiresAt) . '"' : '' ?>
|
||||
<?= $registerEmail !== '' ? ' data-register-email="' . h($registerEmail) . '"' : '' ?>>
|
||||
<div class="login-locale">
|
||||
<label class="toolbar-select locale-toolbar-select" data-i18n-title="lang.label" title="Language">
|
||||
<span class="locale-flag" aria-hidden="true">🇬🇧</span>
|
||||
@@ -30,17 +40,14 @@ $auth = Auth::authUrl();
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<form class="login-card" method="post" action="<?= h($auth) ?>/register">
|
||||
<form class="login-card" id="register-form" method="post" action="<?= h($auth) ?>/register"<?= $registerPending ? ' hidden' : '' ?>>
|
||||
<h1 data-i18n="register.title">Create account</h1>
|
||||
<p class="muted" data-i18n="register.hint">We will email a verification link before you can sign in.</p>
|
||||
<?php if (!empty($registerError)): ?>
|
||||
<div class="alert"><?= h($registerError) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($registerSuccess)): ?>
|
||||
<div class="alert alert--ok"><?= h($registerSuccess) ?></div>
|
||||
<?php endif; ?>
|
||||
<label><span data-i18n="register.email">Email</span>
|
||||
<input name="email" type="email" autocomplete="email" required value="<?= h($registerEmail ?? '') ?>"></label>
|
||||
<input name="email" type="email" autocomplete="email" required value="<?= h($registerEmail) ?>"></label>
|
||||
<label><span data-i18n="register.username">Username</span>
|
||||
<input name="username" autocomplete="username" value="<?= h($registerUsername ?? '') ?>"></label>
|
||||
<label><span data-i18n="register.password">Password</span>
|
||||
@@ -48,6 +55,22 @@ $auth = Auth::authUrl();
|
||||
<button type="submit" data-i18n="register.submit">Register</button>
|
||||
<p class="hint"><a href="<?= h($auth) ?>/login" data-i18n="register.back_login">Back to sign in</a></p>
|
||||
</form>
|
||||
|
||||
<section class="login-card" id="register-pending"<?= $registerPending ? '' : ' hidden' ?>>
|
||||
<h1 data-i18n="register.pending_title">Check your email</h1>
|
||||
<p class="muted" data-i18n="register.pending_hint">We sent a verification link. Confirm your email before signing in.</p>
|
||||
<?php if (!empty($registerSuccess)): ?>
|
||||
<div class="alert alert--ok"><?= h($registerSuccess) ?></div>
|
||||
<?php endif; ?>
|
||||
<p id="register-pending-email" class="muted"><?= $registerEmail !== '' ? h($registerEmail) : '' ?></p>
|
||||
<p class="register-countdown-wrap">
|
||||
<span data-i18n="register.countdown_label">Link expires in</span>
|
||||
<strong id="register-countdown" aria-live="polite">—</strong>
|
||||
</p>
|
||||
<p id="register-resend-status" class="muted" aria-live="polite"></p>
|
||||
<button type="button" class="btn" id="register-resend-btn" data-i18n="register.resend">Resend verification email</button>
|
||||
<p class="hint"><a href="<?= h($auth) ?>/login" data-i18n="register.back_login">Back to sign in</a></p>
|
||||
</section>
|
||||
<?php platform_render_footer(); ?>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user