stage-27 polish: admin presence announces itself in the topbar
Once you're signed in, the topbar gains: - An "Admin" nav link (cyan-accented, with a small shield-lock SVG) appearing only when an admin session exists. - A glowing chip on the right showing "● ADMIN · <who>" with a separated lock button (⏻) for sign-out. Pops in with a scale-bounce entrance, then a slow box-shadow pulse so it stays noticeable without being annoying. Respects prefers-reduced-motion. - Both rendered via request.session in base.html, so every page gets them automatically with no per-route plumbing. Removed the now-redundant in-panel "lock ⏻" from admin.html — the topbar owns logout, the admin page header shows enrolled count instead. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -471,3 +471,61 @@ tr.sev-low .sev-badge { color: var(--muted); }
|
||||
.enroll-card .gate-qr { width: 130px; height: 130px; }
|
||||
.enroll-body h3 { margin: 0 0 6px; font-size: 15px; }
|
||||
.enroll-body p { margin: 0; font-size: 13px; color: var(--muted); }
|
||||
|
||||
/* ── admin in the topbar (announced, not hidden) ────────────── */
|
||||
.nav-admin {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
margin-left: 18px; padding: 4px 10px;
|
||||
color: var(--accent) !important;
|
||||
background: rgba(30, 200, 255, 0.08);
|
||||
border: 1px solid color-mix(in oklab, var(--accent) 35%, var(--border));
|
||||
border-radius: 6px;
|
||||
text-shadow: 0 0 8px var(--accent-glow);
|
||||
}
|
||||
.nav-admin:hover { background: rgba(30, 200, 255, 0.16); border-color: var(--accent); text-decoration: none; }
|
||||
|
||||
.admin-chip {
|
||||
display: inline-flex; align-items: stretch; margin-left: 14px;
|
||||
font-family: var(--font-display); font-size: 11px; letter-spacing: 0.12em;
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 6px; overflow: hidden;
|
||||
background: linear-gradient(180deg, rgba(30,200,255,0.12), rgba(30,200,255,0.02));
|
||||
box-shadow: 0 0 0 0 var(--accent-glow);
|
||||
animation: admin-chip-in 0.55s cubic-bezier(.2,1.4,.4,1), admin-chip-glow 3.2s ease-in-out 0.6s infinite;
|
||||
}
|
||||
.admin-chip-body, .admin-chip-lock {
|
||||
display: inline-flex; align-items: center; gap: 7px;
|
||||
padding: 4px 10px; color: var(--accent) !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
.admin-chip-body { padding-right: 8px; }
|
||||
.admin-chip-body:hover { background: rgba(30,200,255,0.15); text-decoration: none; }
|
||||
.admin-chip-dot {
|
||||
width: 8px; height: 8px; border-radius: 50%;
|
||||
background: var(--green); box-shadow: 0 0 10px var(--green);
|
||||
animation: admin-dot-blink 1.6s ease-in-out infinite;
|
||||
}
|
||||
.admin-chip-label { text-transform: uppercase; font-weight: 600; }
|
||||
.admin-chip-lock {
|
||||
border-left: 1px solid color-mix(in oklab, var(--accent) 35%, var(--border));
|
||||
font-size: 13px; padding: 4px 10px;
|
||||
background: rgba(0,0,0,0.18);
|
||||
}
|
||||
.admin-chip-lock:hover { background: rgba(248,113,113,0.18); color: var(--red) !important; }
|
||||
|
||||
@keyframes admin-chip-in {
|
||||
0% { opacity: 0; transform: translateY(-8px) scale(0.92); box-shadow: 0 0 0 0 var(--accent); }
|
||||
60% { opacity: 1; transform: translateY(0) scale(1.05); box-shadow: 0 0 24px var(--accent); }
|
||||
100% { opacity: 1; transform: scale(1); box-shadow: 0 0 12px var(--accent-glow); }
|
||||
}
|
||||
@keyframes admin-chip-glow {
|
||||
0%,100% { box-shadow: 0 0 8px rgba(30,200,255,0.25); }
|
||||
50% { box-shadow: 0 0 18px rgba(30,200,255,0.55); }
|
||||
}
|
||||
@keyframes admin-dot-blink {
|
||||
0%,80%,100% { opacity: 1; }
|
||||
90% { opacity: 0.35; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.admin-chip, .admin-chip-dot { animation: none; }
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
<h1>Admin Control Center</h1>
|
||||
<span class="count">{% if who %}signed in as <strong>{{ who }}</strong> · {% endif %}<a href="/admin/logout" class="lg-sub">lock ⏻</a></span>
|
||||
<span class="count">{{ members|length }} member{{ '' if members|length == 1 else 's' }} enrolled</span>
|
||||
</div>
|
||||
<p class="page-intro">The secured zone — TOTP-gated, hidden from the nav. Manage who can get in here, and (next) watch the live infrastructure.</p>
|
||||
<div class="verdict verdict-clean">✓ Admin session active — expires after 60 min idle.</div>
|
||||
|
||||
@@ -23,7 +23,27 @@
|
||||
<a href="/queue">Queue</a>
|
||||
<a href="/ledger">Ledger</a>
|
||||
<a href="/train">Trainline</a>
|
||||
{% if request.session.get('admin_who') %}
|
||||
<a href="/admin" class="nav-admin" title="Admin control center">
|
||||
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" style="vertical-align:-2px;">
|
||||
<path d="M12 2 L20 5 V12 C20 17 16.5 20.5 12 22 C7.5 20.5 4 17 4 12 V5 Z"
|
||||
fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
|
||||
<circle cx="12" cy="11" r="2.4" fill="none" stroke="currentColor" stroke-width="1.8"/>
|
||||
<rect x="11" y="12.5" width="2" height="4" rx="1" fill="currentColor"/>
|
||||
</svg>
|
||||
Admin
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% if request.session.get('admin_who') %}
|
||||
<span class="admin-chip" title="Admin session · click to manage">
|
||||
<a href="/admin" class="admin-chip-body">
|
||||
<span class="admin-chip-dot"></span>
|
||||
<span class="admin-chip-label">ADMIN · {{ request.session.get('admin_who') }}</span>
|
||||
</a>
|
||||
<a href="/admin/logout" class="admin-chip-lock" title="Lock the admin zone (sign out)" aria-label="lock">⏻</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="model-status" id="model-status" data-state="checking" title="checking…">
|
||||
<span class="model-status-dot"></span><span class="model-status-text">model</span>
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user