Files
rustguac/static/index.html
T
Dave Kempe 2bf34440e6 v0.5.1: RDP resize fix, new themes, Docker config persistence
- Fix RDP display resize for FreeRDP 3.x (patch 004: config.h struct layout)
- Add aurora theme (midnight blue with ambient glow gradients)
- Add jaguar theme (racing green & gold with subtle gradients)
- Add bg_pattern support for CSS gradient backgrounds in themes
- Fix Docker config.toml persistence across rebuilds (#38)
- Add Docker Compose volume mount documentation
- Increase API rate limit to 5/s burst 30 (fix spurious 429s)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:08:14 +11:00

191 lines
8.4 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>rustguac</title>
<style>
:root { --primary: #e94560; --primary-hover: #c73652; --accent: #5bc0be; --accent-hover: #4aa3a1; --bg: #1a1a2e; --surface: #16213e; --input: #0f3460; --text: #e0e0e0; --text-muted: #aaa; --border: #333; --text-dim: #888; --text-on-primary: #fff; --btn-disabled: #555; --status-pending: #f0c040; --status-active: #5bc0be; --status-completed: #888; --status-error: #e94560; --status-expired: #666; --type-ssh-bg: #1a3a2a; --type-ssh-fg: #5bc0be; --type-rdp-bg: #2a1a3a; --type-rdp-fg: #a78bfa; --type-vnc-bg: #3a2a1a; --type-vnc-fg: #f0c040; --type-web-bg: #1a2a3a; --type-web-fg: #60a5fa; --hop-bg: #1e3a5f; --hop-fg: #7ec8e3; --bg-pattern: none; }
body { font-family: monospace; background-color: var(--bg); background-image: var(--bg-pattern); background-attachment: fixed; min-height: 100vh; color: var(--text); padding: 2em; font-size: 18px; }
h1 { color: var(--primary); }
#error { color: var(--primary); margin-top: 0.8em; }
.login-container { max-width: 360px; margin: 2em 0; }
#sso-section {
display: none;
margin-bottom: 1.5em;
}
#sso-btn {
width: 100%;
padding: 0.8em;
background: var(--accent);
color: var(--bg);
border: none;
font-family: monospace;
font-size: 1.2em;
font-weight: bold;
border-radius: 6px;
cursor: pointer;
}
#sso-btn:hover { background: var(--accent-hover); }
.api-key-toggle {
color: var(--text-muted);
font-size: 0.85em;
cursor: pointer;
user-select: none;
margin-top: 1em;
display: none;
}
.api-key-toggle:hover { color: var(--text); }
.api-key-toggle .chevron {
display: inline-block;
transition: transform 0.2s;
margin-right: 0.3em;
}
.api-key-toggle .chevron.open { transform: rotate(90deg); }
#login-form {
background: var(--surface);
padding: 1.5em;
border-radius: 6px;
margin-top: 0.5em;
}
#login-form label { display: block; margin-top: 0.8em; color: var(--text-muted); font-size: 0.9em; }
#login-form input {
display: block;
width: 100%;
padding: 0.4em;
margin-top: 0.2em;
background: var(--input);
border: 1px solid var(--border);
color: var(--text);
font-family: monospace;
font-size: 1em;
border-radius: 3px;
box-sizing: border-box;
}
#login-form input:focus { outline: none; border-color: var(--primary); }
#login-form button {
margin-top: 1.2em;
padding: 0.5em 1.5em;
background: var(--primary);
color: var(--text-on-primary);
border: none;
font-family: monospace;
font-size: 1em;
border-radius: 3px;
cursor: pointer;
}
#login-form button:hover { background: var(--primary-hover); }
#login-form button:disabled { background: var(--btn-disabled); cursor: default; }
</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>
<img id="site-logo" src="/logo.svg" style="max-height:64px;margin-bottom:0.5em" alt="">
<h1>rustguac</h1>
<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">&#9654;</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 = '/addressbook.html';
}
// Check if already authenticated via cookie
fetch('/api/me', { credentials: 'same-origin' })
.then(function(res) {
if (res.ok) window.location.href = '/addressbook.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='dark';
function initTheme(t){if(!t)return;_themePresets=t.presets||{};_adminPreset=t.admin_preset||'dark';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){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 = '/addressbook.html';
})
.catch(function(err) {
errorEl.textContent = err.message;
})
.finally(function() {
btn.disabled = false;
btn.textContent = 'Login';
});
});
</script>
</body>
</html>