1
0
mirror of git://f0xx.org/ac/ac-be-issues synced 2026-07-29 04:18:08 +03:00

feat(issues): hub nav shell on layout and education page

Signed-off-by: Anton Afanasyeu <a.afanasieff@gmail.com>
This commit is contained in:
Anton Afanasyeu
2026-07-12 13:53:36 +02:00
parent 6baaaaaba1
commit 4565765572
3 changed files with 50 additions and 217 deletions

View File

@@ -11,6 +11,12 @@
* Digest: SHA256 21e56cd12df6a94df41a1c0d67be530bd01454865ee1f032cef5f8d2e497509a * 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> <!DOCTYPE html>
<html lang="en" data-theme="dark"> <html lang="en" data-theme="dark">
<head> <head>
@@ -77,199 +83,12 @@
<?= (cfg('media_pipelines.enabled', false) && (Rbac::isGlobalAdmin() || Rbac::can('media_pipelines_operate'))) ? ' data-can-mp-operate="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> <header class="top-menu" hidden aria-hidden="true"></header>
<div class="shell<?= ($view ?? '') === 'graphs' ? ' shell--graphs-full' : '' ?>"> <div class="shell<?= ($view ?? '') === 'graphs' ? ' shell--graphs-full' : '' ?>">
<nav class="nav-pane" id="nav-pane" aria-label="Console navigation"> <?php platform_render_nav_shell([
<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"> 'project_base' => $__navProjectBase,
<span class="nav-icon nav-icon--menu" aria-hidden="true"></span> 'issues_base' => $__navIssuesBase,
</button> 'active' => $__navActive,
<ul class="nav-list"> 'username' => $__navUser,
<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>
<?php if (cfg('media_pipelines.enabled', false) && (Rbac::isGlobalAdmin() || Rbac::can('media_pipelines_view') || Rbac::can('media_pipelines_operate'))): ?>
<li>
<a href="<?= h(Auth::basePath()) ?>/?view=media_pipelines"
class="nav-link <?= ($view ?? '') === 'media_pipelines' ? 'active' : '' ?>"
aria-label="Media pipelines"
title="Media pipelines (experimental)">
<span class="nav-icon nav-icon--graphs" aria-hidden="true"></span>
<span class="nav-text">
<span class="nav-label">Media pipelines</span>
<span class="nav-desc">Experimental restream — encode once, HLS + RTMP fanout.</span>
</span>
</a>
</li>
<?php endif; ?>
<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>
<main class="main-pane"> <main class="main-pane">
<?php if (($view ?? 'home') === 'home'): ?> <?php if (($view ?? 'home') === 'home'): ?>
<?php require __DIR__ . '/partials/console_home_landing.php'; ?> <?php require __DIR__ . '/partials/console_home_landing.php'; ?>

View File

@@ -1,6 +1,9 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
$bp = Auth::basePath(); $bp = Auth::basePath();
$projectBase = Auth::projectBasePath();
$issuesBase = rtrim((string) cfg('links.crashes', $projectBase . '/issues'), '/');
$navUser = trim((string) (Auth::user()['username'] ?? ''));
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" data-theme="dark"> <html lang="en" data-theme="dark">
@@ -12,38 +15,49 @@ $bp = Auth::basePath();
(function () { (function () {
var t = localStorage.getItem('crash_console_theme'); var t = localStorage.getItem('crash_console_theme');
if (t === 'light' || t === 'dark') document.documentElement.setAttribute('data-theme', t); 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> </script>
<link rel="stylesheet" href="<?= h($bp) ?>/assets/css/app.css"> <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> <script src="<?= h($bp) ?>/assets/js/cookie_consent.js" defer></script>
<?php AnalyticsHead::render('live_education'); ?> <?php AnalyticsHead::render('live_education'); ?>
<script src="<?= h($bp) ?>/assets/js/live_cast_webrtc.js" defer></script> <script src="<?= h($bp) ?>/assets/js/live_cast_webrtc.js" defer></script>
<script src="<?= h($bp) ?>/assets/js/live_education.js" defer></script> <script src="<?= h($bp) ?>/assets/js/live_education.js" defer></script>
</head> </head>
<body class="live-page" data-base-path="<?= h($bp) ?>" data-view="live_education"> <body class="live-page" data-base-path="<?= h($bp) ?>" data-view="live_education">
<main id="live-education-app" class="live-shell"> <div class="shell">
<header class="live-header"> <?php platform_render_nav_shell([
<a href="<?= h(Auth::projectBasePath()) ?>/" class="live-brand">← Back to the console</a> 'project_base' => $projectBase,
<span class="tag-pill">Education demo</span> 'issues_base' => $issuesBase,
</header> 'active' => 'education',
<section class="card card--lift live-card"> 'username' => $navUser,
<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> <main class="main-pane" id="live-education-app">
<p id="edu-status" class="reports-status muted" aria-live="polite">Ready when you are.</p> <section class="card card--lift live-card">
<p class="live-timer-label">Time left: <strong id="edu-timer">5:00</strong></p> <header class="live-header">
<video id="edu-preview" class="live-preview" playsinline muted autoplay hidden></video> <span class="tag-pill">Education demo</span>
<div id="edu-share" class="live-share card card--lift" hidden></div> </header>
<pre id="edu-stats" class="live-stats-nerds" hidden aria-live="polite"></pre> <h1>Try screen sharing in your browser</h1>
<div class="live-actions"> <p class="muted">Free demo up to 5 minutes. No install required — great for first-time visitors.</p>
<button type="button" class="btn btn--primary" id="edu-start">Start screen share</button> <p id="edu-status" class="reports-status muted" aria-live="polite">Ready when you are.</p>
<button type="button" class="btn" id="edu-stop">Stop</button> <p class="live-timer-label">Time left: <strong id="edu-timer">5:00</strong></p>
<button type="button" class="btn btn--ghost" id="edu-notify">Enable notifications</button> <video id="edu-preview" class="live-preview" playsinline muted autoplay hidden></video>
</div> <div id="edu-share" class="live-share card card--lift" hidden></div>
<p class="muted graphs-footnote"> <pre id="edu-stats" class="live-stats-nerds" hidden aria-live="polite"></pre>
WebRTC P2P (no SFU): share the viewer link or QR. Same REST signaling API supports future mobile cast to browser on LAN. <div class="live-actions">
</p> <button type="button" class="btn btn--primary" id="edu-start">Start screen share</button>
</section> <button type="button" class="btn" id="edu-stop">Stop</button>
</main> <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 require __DIR__ . '/partials/cookie_consent.php'; ?>
<?php platform_render_footer(); ?> <?php platform_render_footer(); ?>
</body> </body>