feat: no-JS support for all core UI — registration, settings, monitor CRUD, logout
This commit is contained in:
@@ -89,7 +89,10 @@
|
||||
<div class="flex gap-2">
|
||||
<code id="login-key-display" class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-2.5 text-blue-400 text-sm font-mono select-all"><%= it.loginKey %></code>
|
||||
<button onclick="copyLoginKey()" class="px-3 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded-lg text-gray-400 hover:text-white text-xs transition-colors">Copy</button>
|
||||
<button onclick="confirmReset()" class="px-3 bg-gray-800 hover:bg-gray-700 border border-red-900/50 hover:border-red-700/50 text-red-400 rounded-lg text-xs transition-colors">Rotate</button>
|
||||
<form action="/account/reset-key" method="POST" class="inline" onsubmit="return confirm('Rotate your login key? Your current key stops working immediately.')">
|
||||
<input type="hidden" name="_form" value="1">
|
||||
<button type="submit" class="px-3 bg-gray-800 hover:bg-gray-700 border border-red-900/50 hover:border-red-700/50 text-red-400 rounded-lg text-xs transition-colors h-full py-2.5">Rotate</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
@@ -112,13 +115,18 @@
|
||||
<section class="bg-gray-900 rounded-xl border border-gray-800 p-6">
|
||||
<h2 class="text-sm font-semibold text-gray-300 mb-1">Recovery Email</h2>
|
||||
<p class="text-xs text-gray-600 mb-4">Used for account recovery only. Stored as a one-way hash — we can't read it.</p>
|
||||
<div class="flex gap-2">
|
||||
<input id="email-input" type="email" placeholder="<%= hasEmail ? '●●●●●●●● (set)' : 'you@example.com' %>"
|
||||
class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-2.5 text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 text-sm">
|
||||
<button onclick="saveEmail()" id="email-btn"
|
||||
class="px-4 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-sm font-medium transition-colors">Save</button>
|
||||
<button onclick="removeEmail()" id="remove-email-btn" class="<%= hasEmail ? '' : 'hidden' %> px-3 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded-lg text-gray-500 hover:text-red-400 text-xs transition-colors">Remove</button>
|
||||
</div>
|
||||
<form action="/account/email" method="POST">
|
||||
<input type="hidden" name="_form" value="1">
|
||||
<div class="flex gap-2">
|
||||
<input id="email-input" name="email" type="email" placeholder="<%= hasEmail ? '●●●●●●●● (set)' : 'you@example.com' %>"
|
||||
class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-2.5 text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 text-sm">
|
||||
<button type="submit" id="email-btn"
|
||||
class="px-4 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-sm font-medium transition-colors">Save</button>
|
||||
<% if (hasEmail) { %>
|
||||
<button type="submit" name="email" value="" class="px-3 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded-lg text-gray-500 hover:text-red-400 text-xs transition-colors">Remove</button>
|
||||
<% } %>
|
||||
</div>
|
||||
</form>
|
||||
<p id="email-status" class="text-xs mt-2 hidden"></p>
|
||||
</section>
|
||||
<% } %>
|
||||
@@ -133,19 +141,19 @@
|
||||
<h2 class="text-sm font-semibold text-gray-300">Sub-Keys</h2>
|
||||
<p class="text-xs text-gray-600 mt-0.5">Create separate keys for different apps, scripts, or teammates.</p>
|
||||
</div>
|
||||
<button onclick="showCreateKey()" class="px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-xs font-medium transition-colors">+ New Sub-Key</button>
|
||||
<button onclick="document.getElementById('create-key-form').classList.toggle('hidden')" class="px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-xs font-medium transition-colors">+ New Sub-Key</button>
|
||||
</div>
|
||||
|
||||
<!-- Create key form (hidden by default) -->
|
||||
<div id="create-key-form" class="hidden mb-4 p-4 bg-gray-800/50 rounded-lg border border-gray-700">
|
||||
<!-- Create key form -->
|
||||
<form id="create-key-form" action="/account/keys" method="POST" class="hidden mb-4 p-4 bg-gray-800/50 rounded-lg border border-gray-700">
|
||||
<input type="hidden" name="_form" value="1">
|
||||
<label class="block text-xs text-gray-500 mb-1.5">Label</label>
|
||||
<div class="flex gap-2">
|
||||
<input id="key-label" type="text" placeholder="e.g. ci-pipeline, mobile-app"
|
||||
<input name="label" type="text" placeholder="e.g. ci-pipeline, mobile-app" required
|
||||
class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 text-sm">
|
||||
<button onclick="createKey()" class="px-4 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-sm font-medium transition-colors">Create</button>
|
||||
<button onclick="hideCreateKey()" class="px-3 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded-lg text-gray-500 text-sm transition-colors">Cancel</button>
|
||||
<button type="submit" class="px-4 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-sm font-medium transition-colors">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Sub-keys list -->
|
||||
<div id="keys-list" class="space-y-2">
|
||||
@@ -156,7 +164,10 @@
|
||||
<div class="p-3 bg-gray-800/50 rounded-lg border border-gray-700/50">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<p class="text-sm text-gray-200"><%= k.label %></p>
|
||||
<button onclick="deleteKey('<%= k.id %>', this)" class="text-xs text-gray-600 hover:text-red-400 transition-colors px-2 py-1">Revoke</button>
|
||||
<form action="/account/keys/<%= k.id %>/delete" method="POST" class="inline" onsubmit="return confirm('Revoke this key? Any apps using it will stop working.')">
|
||||
<input type="hidden" name="_form" value="1">
|
||||
<button type="submit" class="text-xs text-gray-600 hover:text-red-400 transition-colors px-2 py-1">Revoke</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<code class="flex-1 bg-gray-900 border border-gray-800 rounded-lg px-3 py-2 text-blue-400 text-xs font-mono select-all"><%= k.key %></code>
|
||||
@@ -174,31 +185,7 @@
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
async function saveEmail() {
|
||||
const email = document.getElementById('email-input').value.trim();
|
||||
if (!email) return;
|
||||
try {
|
||||
await api('/account/email', { method: 'POST', body: { email } });
|
||||
location.reload();
|
||||
} catch (e) {
|
||||
showStatus('email-status', e.message, 'red');
|
||||
}
|
||||
}
|
||||
|
||||
async function removeEmail() {
|
||||
if (!confirm('Remove recovery email?')) return;
|
||||
await api('/account/email', { method: 'POST', body: { email: null } });
|
||||
location.reload();
|
||||
}
|
||||
|
||||
async function confirmReset() {
|
||||
if (!confirm('Rotate your login key? Your current key stops working immediately.')) return;
|
||||
const data = await api('/account/reset-key', { method: 'POST', body: {} });
|
||||
document.getElementById('login-key-display').textContent = data.key;
|
||||
}
|
||||
|
||||
// JS enhancements — clipboard copy (progressive, not required)
|
||||
function copyLoginKey() {
|
||||
const val = document.getElementById('login-key-display').textContent;
|
||||
navigator.clipboard.writeText(val);
|
||||
@@ -207,42 +194,11 @@
|
||||
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('text-green-400'); }, 1500);
|
||||
}
|
||||
|
||||
function showCreateKey() {
|
||||
document.getElementById('create-key-form').classList.remove('hidden');
|
||||
document.getElementById('new-key-reveal').classList.add('hidden');
|
||||
document.getElementById('key-label').focus();
|
||||
}
|
||||
|
||||
function hideCreateKey() {
|
||||
document.getElementById('create-key-form').classList.add('hidden');
|
||||
}
|
||||
|
||||
async function createKey() {
|
||||
const label = document.getElementById('key-label').value.trim();
|
||||
if (!label) return;
|
||||
await api('/account/keys', { method: 'POST', body: { label } });
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function copyKey(btn, val) {
|
||||
navigator.clipboard.writeText(val);
|
||||
btn.textContent = 'Copied!'; btn.classList.add('text-green-400');
|
||||
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('text-green-400'); }, 1500);
|
||||
}
|
||||
|
||||
async function deleteKey(id, btn) {
|
||||
if (!confirm('Revoke this key? Any apps using it will stop working.')) return;
|
||||
await api(`/account/keys/${id}`, { method: 'DELETE' });
|
||||
btn.closest('.p-3').remove();
|
||||
}
|
||||
|
||||
function showStatus(id, msg, color) {
|
||||
const el = document.getElementById(id);
|
||||
el.textContent = msg;
|
||||
el.className = `text-xs mt-2 text-${color}-400`;
|
||||
el.classList.remove('hidden');
|
||||
setTimeout(() => el.classList.add('hidden'), 3000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<%~ include('./partials/foot') %>
|
||||
|
||||
Reference in New Issue
Block a user