mirror of
https://github.com/sol1/rustguac.git
synced 2026-09-10 09:35:45 +00:00
0d69e8fed4
Three pieces of v1.6.0 work that happened together and are easier to
review as one save point.
Rename: Address Book → Connections
- static/addressbook.html renamed to static/connections.html
- Nav links, page titles, empty states, onboarding, and prose updated
across all 8 static pages (connections, admin, docs, index,
recordings, reports, sessions, tokens).
- README, CLAUDE.md, and every file under docs/ updated.
- src/main.rs: connections.html added to the branded-page map and
route list; /addressbook.html returns a 308 permanent redirect so
existing bookmarks keep working.
- Backend API paths, Rust types, and Vault storage paths are
deliberately unchanged — internal only.
Folder allowed_groups picker
- New SQLite table `seen_groups` tracks OIDC groups observed in any
user login; OIDC callback upserts after extracting groups.
- `GET /api/auth/known-groups` (admin-only) returns the union of
group_role_mappings and seen_groups.
- `GET /api/addressbook/folders/{scope}/{folder}/config` adds the
missing endpoint the frontend was already calling — existing
allowed_groups now prefill the edit-folder modal.
- Folder modal swaps the free-text comma-separated input for a chip
picker with a themed combobox dropdown: autocomplete over known
groups, keyboard nav, "+ add custom" row for unlisted groups.
Active session visibility (GitHub #102)
- `GET /api/sessions` scopes to the caller's own sessions by default;
`?all=true` lets admins opt in (used by the Sessions page).
- `GET /api/sessions/{id}` and the thumbnail GET/PUT endpoints are
now owner-or-admin, returning 404 for other callers so session
existence isn't leaked.
- Connections' Active Sessions strip is now always owner-scoped —
admins still manage everyone via the Sessions page.
166 lines
6.9 KiB
HTML
166 lines
6.9 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>rustguac</title>
|
|
<link rel="stylesheet" href="/rustguac.css">
|
|
<style>
|
|
body { display: flex; flex-direction: column; align-items: center; }
|
|
.login-container { max-width: 380px; width: 100%; margin: var(--s-4) 0; }
|
|
#sso-section { display: none; margin-bottom: var(--s-4); }
|
|
#sso-btn {
|
|
width: 100%;
|
|
height: var(--ctl-lg);
|
|
background: var(--accent);
|
|
border-color: var(--accent);
|
|
color: var(--bg);
|
|
font-size: var(--fz-md);
|
|
font-weight: bold;
|
|
}
|
|
#sso-btn:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
|
.api-key-toggle {
|
|
color: var(--text-muted);
|
|
font-size: var(--fz-sm);
|
|
cursor: pointer;
|
|
user-select: none;
|
|
margin-top: var(--s-3);
|
|
display: none;
|
|
}
|
|
.api-key-toggle:hover { color: var(--text); }
|
|
.api-key-toggle .chevron {
|
|
display: inline-block;
|
|
transition: transform .2s;
|
|
margin-right: var(--s-1);
|
|
}
|
|
.api-key-toggle .chevron.open { transform: rotate(90deg); }
|
|
#login-form {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
padding: var(--s-4);
|
|
border-radius: var(--radius);
|
|
margin-top: var(--s-2);
|
|
}
|
|
#login-form input {
|
|
display: block;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
margin-top: var(--s-1);
|
|
}
|
|
#login-form button {
|
|
margin-top: var(--s-4);
|
|
width: 100%;
|
|
height: var(--ctl-lg);
|
|
font-size: var(--fz-md);
|
|
}
|
|
</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>
|
|
|
|
<div class="login-container">
|
|
<div id="sso-section">
|
|
<button id="sso-btn">Sign in with SSO</button>
|
|
</div>
|
|
|
|
<div class="api-key-toggle" id="api-key-toggle">
|
|
<span class="chevron" id="api-key-chevron">▶</span> Sign in with API key
|
|
</div>
|
|
|
|
<form id="login-form">
|
|
<strong>Login</strong>
|
|
<label>API Key
|
|
<input type="password" id="api-key" placeholder="Bearer API key" required>
|
|
</label>
|
|
<button type="submit" id="login-btn">Login</button>
|
|
<div id="error"></div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
// Show SSO error if redirected back from a failed callback
|
|
if (new URLSearchParams(window.location.search).get('sso_error')) {
|
|
document.getElementById('error').textContent = 'SSO login failed — please try again.';
|
|
history.replaceState(null, '', '/');
|
|
}
|
|
|
|
// If already logged in via API key, redirect
|
|
if (sessionStorage.getItem('rustguac_api_key')) {
|
|
window.location.href = '/connections.html';
|
|
}
|
|
|
|
// Check if already authenticated via cookie
|
|
fetch('/api/me', { credentials: 'same-origin' })
|
|
.then(function(res) {
|
|
if (res.ok) window.location.href = '/connections.html';
|
|
});
|
|
|
|
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';
|
|
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 loginForm = document.getElementById('login-form');
|
|
var apiKeyToggle = document.getElementById('api-key-toggle');
|
|
var chevron = document.getElementById('api-key-chevron');
|
|
|
|
// Check if OIDC is enabled
|
|
fetch('/api/auth/status')
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
if (data.oidc_enabled) {
|
|
document.getElementById('sso-section').style.display = 'block';
|
|
apiKeyToggle.style.display = 'block';
|
|
// Hide API key form by default when SSO is available
|
|
loginForm.style.display = 'none';
|
|
}
|
|
if (data.site_title) {
|
|
document.title = data.site_title;
|
|
document.querySelector('h1').textContent = data.site_title;
|
|
}
|
|
initTheme(data.theme);
|
|
});
|
|
|
|
document.getElementById('sso-btn').addEventListener('click', function() {
|
|
window.location.href = '/auth/login';
|
|
});
|
|
|
|
// Toggle API key form
|
|
apiKeyToggle.addEventListener('click', function() {
|
|
var hidden = loginForm.style.display === 'none';
|
|
loginForm.style.display = hidden ? '' : 'none';
|
|
chevron.className = hidden ? 'chevron open' : 'chevron';
|
|
});
|
|
|
|
var errorEl = document.getElementById('error');
|
|
var btn = document.getElementById('login-btn');
|
|
|
|
loginForm.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
errorEl.textContent = '';
|
|
btn.disabled = true;
|
|
btn.textContent = 'Checking...';
|
|
|
|
var key = document.getElementById('api-key').value;
|
|
fetch('/api/sessions', {
|
|
headers: { 'Authorization': 'Bearer ' + key }
|
|
})
|
|
.then(function(res) {
|
|
if (!res.ok) throw new Error('Invalid API key');
|
|
sessionStorage.setItem('rustguac_api_key', key);
|
|
window.location.href = '/connections.html';
|
|
})
|
|
.catch(function(err) {
|
|
errorEl.textContent = err.message;
|
|
})
|
|
.finally(function() {
|
|
btn.disabled = false;
|
|
btn.textContent = 'Login';
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|