mirror of
https://github.com/sol1/rustguac.git
synced 2026-10-25 21:19:45 +00:00
32581f30ae
The '/' search box leaves the connections folder header for a bigger,
centred box in the brand row, inline with the logo. Results open in a dim,
blurred overlay over an untouched folder view: a centred panel with the
summary, the Type and Folder facets and the results table (fixed columns,
one line per cell, folder shown by display name over its key, Connect, new
window and open-folder gathered into one actions cell). Focusing the box or
pressing / opens it and lists every connection the user can see; typing
narrows it; Escape from anywhere, a mousedown outside the box and panel, or
opening a folder closes it (an open facet menu takes the first Escape).
static/search.js is the single implementation, used on every page
including connections, which dropped ~16k of duplicated markup, CSS and JS;
the styles live once in rustguac.css so the results look identical
everywhere. The index is cached in sessionStorage and only re-fetched when
the address book's version changes, so moving between pages is instant; the
connections page keeps the folder-specific hooks it needs (rgSelectFolder
selects a folder in place, rgSearchIndex feeds the credentials banner) and
opening a folder from another page navigates to connections with
class, and the module stands down cleanly on a page with no brand row.
Connections also gets a more generous content gutter, the folder action
buttons move down to the filter row beside Type, the folder title is inset
to align with the table, and the Connect button keeps its size and spins
in place instead of growing to 'Connecting...'.
Part of #254
tests: the global search overlay on every page
search: keep page modals above the brand-row search box
The global search lifts the brand row to z-index 300 so the box is not
dimmed by its own results overlay (200), but the page modals sat at 100, so
the search input showed through the ad-hoc session dialog and could still
be focused. Modal overlays now sit at 1000, above the search chrome and
below the themed dialogs (4000) and the embedded session (5000). A spec
pins it on the ad-hoc and folder modals.
search: never on the login page, nor for anyone the server does not recognise
The global search was included on index.html, so the login page carried
the search box, and because the index was cached in sessionStorage it
kept answering with the whole address book after logout, or to the next
login in the same tab.
- index.html no longer loads search.js at all.
- search.js only renders for an operator or above: the role the server
bakes on <html data-role-level> decides on first paint, and an API-key
user (served the anonymous variant) is confirmed through /api/me first.
Anonymous or viewer: nothing injected, cache swept.
- The cached index is keyed to the identity /api/me returns; a cache left
by a different login is dropped rather than shown.
- A 401/403 from the index or version endpoints removes the box, the
overlay and the cache; the logout link clears the cache before the
login page shows.
e2e: login page has no box and never loads search.js; anonymous and
viewer callers get nothing and a stale cache is wiped; a different
login's cache is dropped; the same login keeps its cache across pages;
logout and expiry forget it.
(cherry picked from commit b8c5c0f9ee)
231 lines
12 KiB
HTML
231 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>rustguac - Docs</title>
|
|
<link rel="stylesheet" href="/rustguac.css">
|
|
<style>
|
|
.docs-layout {
|
|
display: flex;
|
|
min-height: calc(100vh - 180px);
|
|
border-top: 1px solid var(--border);
|
|
margin-top: var(--s-3);
|
|
}
|
|
.sidebar {
|
|
width: 240px; min-width: 240px;
|
|
border-right: 1px solid var(--border);
|
|
padding: var(--s-3) 0;
|
|
overflow-y: auto;
|
|
}
|
|
.sidebar a {
|
|
display: block;
|
|
padding: var(--s-2) var(--s-4);
|
|
color: var(--text-muted);
|
|
text-decoration: none;
|
|
font-size: var(--fz-sm);
|
|
border-left: 3px solid transparent;
|
|
}
|
|
.sidebar a:hover { color: var(--text); background: var(--surface); }
|
|
.sidebar a.active {
|
|
color: var(--primary);
|
|
border-left-color: var(--primary);
|
|
background: var(--surface);
|
|
font-weight: bold;
|
|
}
|
|
.content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: var(--s-4) var(--s-5);
|
|
max-width: 960px;
|
|
}
|
|
.content h1 { color: var(--primary); font-size: var(--fz-xl); margin-bottom: var(--s-3); }
|
|
.content h2 {
|
|
color: var(--primary); font-size: var(--fz-lg);
|
|
margin-top: var(--s-5); margin-bottom: var(--s-2);
|
|
border-bottom: 1px solid var(--border); padding-bottom: var(--s-2);
|
|
}
|
|
.content h3 { color: var(--accent); font-size: var(--fz-md); margin-top: var(--s-4); margin-bottom: var(--s-1); }
|
|
.content h4 { color: var(--accent); font-size: var(--fz-sm); margin-top: var(--s-3); margin-bottom: var(--s-1); }
|
|
.content p { margin: var(--s-2) 0; line-height: 1.65; }
|
|
.content ul, .content ol { margin: var(--s-2) 0 var(--s-2) var(--s-4); line-height: 1.65; }
|
|
.content li { margin: var(--s-1) 0; }
|
|
.content a { color: var(--accent); }
|
|
.content a:hover { text-decoration: underline; }
|
|
.content code {
|
|
background: var(--surface);
|
|
padding: 2px var(--s-1);
|
|
border-radius: var(--radius);
|
|
font-size: 0.9em;
|
|
}
|
|
.content pre {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
padding: var(--s-3);
|
|
border-radius: var(--radius);
|
|
overflow-x: auto;
|
|
line-height: 1.5;
|
|
margin: var(--s-2) 0;
|
|
}
|
|
.content pre code { background: none; padding: 0; border-radius: 0; font-size: var(--fz-sm); }
|
|
.content table { border-collapse: collapse; width: 100%; margin: var(--s-2) 0; }
|
|
.content th, .content td {
|
|
text-align: left; padding: var(--s-2) var(--s-3);
|
|
border-bottom: 1px solid var(--border); font-size: var(--fz-sm);
|
|
}
|
|
.content th { color: var(--text-muted); text-transform: none; letter-spacing: 0; background: transparent; }
|
|
.content blockquote {
|
|
border-left: 3px solid var(--accent);
|
|
margin: var(--s-2) 0;
|
|
padding: var(--s-1) var(--s-3);
|
|
color: var(--text-muted);
|
|
}
|
|
.content hr { border: none; border-top: 1px solid var(--border); margin: var(--s-5) 0; }
|
|
.loading { padding: var(--s-4); color: var(--text-muted); }
|
|
@media (max-width: 700px) {
|
|
.sidebar { width: 180px; min-width: 180px; }
|
|
.content { padding: var(--s-3); }
|
|
}
|
|
</style>
|
|
<script>(function(){var c=localStorage.getItem('rustguac_theme_colors');if(c){try{var o=JSON.parse(c),r=document.documentElement.style;for(var k in o)r.setProperty('--'+k.replace(/_/g,'-'),o[k]);if(o.bg_pattern&&o.bg_pattern!=='none'){var s=document.createElement('style');s.id='bg-pattern-style';s.textContent='body{background-image:'+o.bg_pattern+';background-attachment:fixed}';document.head.appendChild(s)}}catch(e){}}})();</script>
|
|
</head>
|
|
<body>
|
|
<div class="brand-row">
|
|
<img id="site-logo" src="/logo.svg" alt="">
|
|
<h1>rustguac</h1>
|
|
</div>
|
|
<nav>
|
|
<a href="/connections.html">Connections</a>
|
|
<a href="/sessions.html" id="sessions-link" data-min-role="poweruser">Sessions</a>
|
|
<a href="/recordings.html">Recordings</a>
|
|
<a href="/reports.html" id="reports-link" data-min-role="poweruser">Reports</a>
|
|
<a href="/docs.html" class="active">Docs</a>
|
|
<a href="/tokens.html" id="tokens-link" data-min-role="operator">Tokens</a>
|
|
<a href="/admin.html" id="admin-link" data-min-role="admin">Admin</a>
|
|
<a href="/connections.html#my-credentials" id="my-creds-nav" title="Manage your saved credential variables"><span class="nav-key">🔑</span> Credentials</a>
|
|
<span id="user-menu-wrapper"><a href="#" id="logout-item">Logout</a><span id="user-menu-btn" title="Preferences">⚙ Settings</span><div id="user-menu"><div class="um-section-label">Theme</div><div id="um-theme-list"></div></div></span>
|
|
</nav>
|
|
|
|
<div class="docs-layout">
|
|
<div class="sidebar" id="sidebar"></div>
|
|
<div class="content" id="content">
|
|
<div class="loading">Loading documentation...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
var docs = [];
|
|
var sidebar = document.getElementById('sidebar');
|
|
var content = document.getElementById('content');
|
|
|
|
function showSection(slug) {
|
|
var doc = docs.find(function(d) { return d.slug === slug; });
|
|
if (!doc && docs.length) doc = docs[0];
|
|
if (!doc) return;
|
|
|
|
content.innerHTML = doc.html;
|
|
|
|
// Update sidebar active state
|
|
var links = sidebar.querySelectorAll('a');
|
|
for (var i = 0; i < links.length; i++) {
|
|
links[i].classList.toggle('active', links[i].dataset.slug === doc.slug);
|
|
}
|
|
|
|
// Update hash without triggering hashchange
|
|
if (location.hash !== '#' + doc.slug) {
|
|
history.replaceState(null, '', '#' + doc.slug);
|
|
}
|
|
|
|
content.scrollTop = 0;
|
|
}
|
|
|
|
function getSlugFromHash() {
|
|
return location.hash ? location.hash.substring(1) : '';
|
|
}
|
|
|
|
// Load docs from API
|
|
fetch('/api/docs')
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
docs = data;
|
|
|
|
// Build sidebar
|
|
var html = '';
|
|
for (var i = 0; i < docs.length; i++) {
|
|
html += '<a href="#' + docs[i].slug + '" data-slug="' + docs[i].slug + '">' + docs[i].title + '</a>';
|
|
}
|
|
sidebar.innerHTML = html;
|
|
|
|
// Click handler for sidebar links
|
|
sidebar.addEventListener('click', function(e) {
|
|
var a = e.target.closest('a');
|
|
if (!a) return;
|
|
e.preventDefault();
|
|
var slug = a.dataset.slug;
|
|
history.pushState(null, '', '#' + slug);
|
|
showSection(slug);
|
|
});
|
|
|
|
// Show initial section from hash or first
|
|
showSection(getSlugFromHash() || docs[0].slug);
|
|
})
|
|
.catch(function() {
|
|
content.innerHTML = '<div class="loading">Failed to load documentation.</div>';
|
|
});
|
|
|
|
// Handle browser back/forward
|
|
window.addEventListener('popstate', function() {
|
|
showSection(getSlugFromHash());
|
|
});
|
|
|
|
// Theme and auth
|
|
function applyThemeColors(colors){var r=document.documentElement.style;for(var k in colors)r.setProperty('--'+k.replace(/_/g,'-'),colors[k]);var s=document.getElementById('bg-pattern-style');if(!s){s=document.createElement('style');s.id='bg-pattern-style';document.head.appendChild(s)}s.textContent=colors.bg_pattern&&colors.bg_pattern!=='none'?'body{background-image:'+colors.bg_pattern+';background-attachment:fixed}':'';localStorage.setItem('rustguac_theme_colors',JSON.stringify(colors))}
|
|
var _themePresets={},_adminPreset='aurora';
|
|
var _themeDescriptions={dark:'Navy & cyan \u2014 the default',light:'Clean white & blue','high-contrast':'Maximum readability',terminal:'Retro green-on-black',nord:'Arctic, muted blues',corporate:'Slate & steel blue',aurora:'Midnight blue with ambient glow',jaguar:'Racing green & gold'};
|
|
function initTheme(t){if(!t)return;_themePresets=t.presets||{};_adminPreset=t.admin_preset||'aurora';var u=localStorage.getItem('rustguac_theme'),active=u&&_themePresets[u]?u:_adminPreset,colors=(active===_adminPreset)?t.admin_colors:_themePresets[active];if(colors)applyThemeColors(colors);if(t.logo_url){var l=document.getElementById('site-logo');if(l){if(l.src!==t.logo_url&&!l.src.endsWith(t.logo_url))l.src=t.logo_url;l.style.display=''}}var menu=document.getElementById('um-theme-list');if(menu){menu.innerHTML='';Object.keys(_themePresets).forEach(function(name){var item=document.createElement('div');item.className='um-item'+(name===active?' active':'');var sw=document.createElement('span');sw.className='um-swatch';var p=_themePresets[name];sw.style.background='linear-gradient(135deg,'+p.primary+' 50%,'+p.accent+' 50%)';item.appendChild(sw);var info=document.createElement('div');info.className='um-theme-info';var nm=document.createElement('span');nm.className='um-theme-name';nm.textContent=name;info.appendChild(nm);var desc=document.createElement('span');desc.className='um-theme-desc';desc.textContent=_themeDescriptions[name]||'';info.appendChild(desc);item.appendChild(info);item.addEventListener('click',function(){localStorage.setItem('rustguac_theme',name);applyThemeColors(_themePresets[name]);menu.querySelectorAll('.um-item').forEach(function(el){el.classList.remove('active')});item.classList.add('active');document.getElementById('user-menu').style.display='none'});menu.appendChild(item)})}}
|
|
var _ub=document.getElementById('user-menu-btn');if(_ub)_ub.addEventListener('click',function(e){e.stopPropagation();var m=document.getElementById('user-menu');m.style.display=m.style.display==='block'?'none':'block'});document.addEventListener('click',function(){var m=document.getElementById('user-menu');if(m)m.style.display='none'});
|
|
fetch('/api/auth/status').then(function(r){return r.json()}).then(function(d){
|
|
if(d.site_title){document.title=d.site_title+' - Docs';document.querySelector('h1').textContent=d.site_title;}
|
|
initTheme(d.theme);
|
|
});
|
|
var apiKey = sessionStorage.getItem('rustguac_api_key');
|
|
var roleLevel = { admin: 4, poweruser: 3, operator: 2, viewer: 1 };
|
|
if (apiKey) {
|
|
// A key is never on the page GET, so the server served the anonymous variant.
|
|
document.documentElement.setAttribute('data-role-level', '4');
|
|
document.getElementById('admin-link').style.display = '';
|
|
document.getElementById('tokens-link').style.display = '';
|
|
document.getElementById('reports-link').style.display = '';
|
|
} else {
|
|
fetch('/api/me', { credentials: 'same-origin' })
|
|
.then(function(res) {
|
|
if (res.status === 401 || res.status === 403) { window.location.href = '/'; return; }
|
|
if (!res.ok) return;
|
|
return res.json();
|
|
})
|
|
.then(function(data) {
|
|
if (!data) return;
|
|
if (data.role === 'admin') document.getElementById('admin-link').style.display = '';
|
|
var level = roleLevel[data.role] || 0;
|
|
// Reconcile the server-written nav gate (see rustguac.css): the page
|
|
// GET carried no API key, so key users arrive on the anonymous variant.
|
|
document.documentElement.setAttribute('data-role-level', String((typeof apiKey !== 'undefined' && apiKey) ? 4 : level));
|
|
if (level >= 2) document.getElementById('tokens-link').style.display = '';
|
|
if (level >= 3) {
|
|
document.getElementById('reports-link').style.display = '';
|
|
}
|
|
if (level < 3) {
|
|
document.getElementById('sessions-link').style.display = 'none';
|
|
}
|
|
})
|
|
.catch(function() {});
|
|
}
|
|
document.getElementById('logout-item').addEventListener('click', function() {
|
|
sessionStorage.removeItem('rustguac_api_key');
|
|
fetch('/auth/logout', { credentials: 'same-origin' })
|
|
.finally(function() { window.location.href = '/'; });
|
|
});
|
|
</script>
|
|
<script src="/search.js"></script>
|
|
</body>
|
|
</html>
|