mockups/admin_settings.html
Rama main · réplica de lectura del repositorio original.
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Admin settings — Cloudzilla</title>
<link rel="stylesheet" href="/assets/tailwind.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500&family=Geist:wght@400;500;600&display=swap" rel="stylesheet" />
</head>
<body class="min-h-screen flex flex-col antialiased font-sans">
<a href="#main-content" class="skip-link">Skip to main content</a>
<div id="cz-header"></div>
<main id="main-content" tabindex="-1" class="max-w-[1400px] mx-auto px-6 pt-6 pb-20 flex-1 w-full">
<div class="space-y-6">
<nav aria-label="Admin sections" class="flex items-center gap-1 border-b border-border text-sm">
<a href="admin_settings.html" aria-current="page" class="px-3 py-2.5 border-b -mb-px border-foreground text-foreground font-medium inline-flex items-center gap-1.5">Settings</a>
<a href="admin_users.html" class="px-3 py-2.5 border-b -mb-px border-transparent text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5">Users</a>
<a href="audit_log.html" class="px-3 py-2.5 border-b -mb-px border-transparent text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5">Audit log</a>
<a href="sso_settings.html" class="px-3 py-2.5 border-b -mb-px border-transparent text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5">Single sign-on</a>
</nav>
<div class="flex justify-between items-center">
<h1 class="text-2xl font-semibold tracking-tight">Admin settings</h1>
</div>
<div class="rounded-lg border border-border bg-card text-card-foreground">
<div class="flex flex-col space-y-1.5 p-5 border-b border-border">
<h2 class="text-[13px] font-semibold leading-none tracking-tight">Instance settings</h2>
<p class="text-[12px] text-muted-foreground">Toggle site-wide feature flags.</p>
</div>
<div class="p-5">
<div id="admin-settings-list" class="space-y-3">
<form onsubmit="return false" class="flex items-center justify-between py-3 border-b border-border last:border-0">
<label class="flex flex-1 items-center justify-between gap-4 cursor-pointer">
<span class="text-sm font-medium text-foreground">Allow sign-ups</span>
<span class="relative inline-flex items-center"><input type="checkbox" role="switch" id="setting-allow_registration" data-setting="allow_registration" checked class="peer sr-only" /><span aria-hidden="true" class="relative w-9 h-5 bg-input dark:bg-border-strong rounded-full transition-colors peer-checked:bg-switch dark:peer-checked:bg-switch peer-focus-visible:outline-solid peer-focus-visible:outline-2 peer-focus-visible:outline-ring peer-focus-visible:outline-offset-2 peer-disabled:opacity-50 peer-disabled:cursor-not-allowed after:absolute after:content-[''] after:top-0.5 after:left-0.5 after:bg-white after:shadow-sm after:rounded-full after:h-4 after:w-4 after:transition-transform peer-checked:after:translate-x-4"></span></span>
</label>
</form>
<form onsubmit="return false" class="flex items-center justify-between py-3 border-b border-border last:border-0">
<label class="flex flex-1 items-center justify-between gap-4 cursor-pointer">
<span class="text-sm font-medium text-foreground">Allow sign-in</span>
<span class="relative inline-flex items-center"><input type="checkbox" role="switch" id="setting-allow_login" data-setting="allow_login" checked class="peer sr-only" /><span aria-hidden="true" class="relative w-9 h-5 bg-input dark:bg-border-strong rounded-full transition-colors peer-checked:bg-switch dark:peer-checked:bg-switch peer-focus-visible:outline-solid peer-focus-visible:outline-2 peer-focus-visible:outline-ring peer-focus-visible:outline-offset-2 peer-disabled:opacity-50 peer-disabled:cursor-not-allowed after:absolute after:content-[''] after:top-0.5 after:left-0.5 after:bg-white after:shadow-sm after:rounded-full after:h-4 after:w-4 after:transition-transform peer-checked:after:translate-x-4"></span></span>
</label>
</form>
</div>
</div>
</div>
<div class="rounded-lg border border-border bg-card text-card-foreground">
<div class="flex flex-col space-y-1.5 p-5 border-b border-border">
<h2 class="text-[13px] font-semibold leading-none tracking-tight">Invitations</h2>
<p class="text-[12px] text-muted-foreground">Invite users to join this instance by email.</p>
</div>
<div class="p-5">
<form id="invite-form" class="flex gap-3 mb-6">
<label for="invite-email" class="sr-only">Invitee email</label>
<input id="invite-email" type="email" name="email" placeholder="user@example.com" required class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 flex-1" />
<button type="submit" class="inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition-colors disabled:opacity-50 disabled:pointer-events-none h-9 px-4 text-sm bg-primary text-primary-foreground hover:bg-primary/90">Send invite</button>
</form>
<div id="admin-invitations-list" class="space-y-2">
<div class="rounded-md border border-border p-4 flex justify-between items-start">
<div class="space-y-1">
<p class="text-sm font-medium text-foreground">hana@example.test</p>
<p class="text-xs text-muted-foreground font-mono break-all">/invite/k3Vt9mQx7LpB2wRd</p>
<div class="flex gap-3 text-xs text-muted-foreground">
<span>Expires: <time datetime="2026-11-13T00:00:00Z">2026-11-13</time></span>
<span class="text-warning">Pending</span>
</div>
</div>
<button type="button" data-delete-invite data-email="hana@example.test" class="inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition-colors disabled:opacity-50 disabled:pointer-events-none bg-destructive text-destructive-foreground hover:bg-destructive/90 h-8 px-3 text-[13px] ml-4 shrink-0">Delete</button>
</div>
<div class="rounded-md border border-border p-4 flex justify-between items-start">
<div class="space-y-1">
<p class="text-sm font-medium text-foreground">contractor@example.test</p>
<p class="text-xs text-muted-foreground font-mono break-all">/invite/Zr8nYc4HgF1sJe6u</p>
<div class="flex gap-3 text-xs text-muted-foreground">
<span>Expires: <time datetime="2026-11-02T00:00:00Z">2026-11-02</time></span>
<span class="text-success">Accepted</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="rounded-lg border border-border bg-card text-card-foreground">
<div class="flex flex-col space-y-1.5 p-5 border-b border-border">
<h2 class="text-[13px] font-semibold leading-none tracking-tight">Email verification</h2>
<p class="text-[12px] text-muted-foreground">Mark a user's email address verified by hand, for instances that can't send the verification link. A verified address lets the Google account with that address sign in to the user's account, so only vouch for addresses you know are theirs.</p>
</div>
<div class="p-5">
<form id="verify-form" class="flex flex-col sm:flex-row gap-3">
<label for="verify-username" class="sr-only">Username</label>
<input id="verify-username" type="text" name="username" placeholder="username" required autocomplete="off" class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 flex-1" />
<label for="verify-email" class="sr-only">Email</label>
<input id="verify-email" type="email" name="email" placeholder="user@example.com" required autocomplete="off" class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 flex-1" />
<button type="submit" class="inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition-colors disabled:opacity-50 disabled:pointer-events-none h-9 px-4 text-sm bg-primary text-primary-foreground hover:bg-primary/90">Mark verified</button>
</form>
</div>
</div>
</div>
</main>
<dialog id="cz-confirm-dialog" aria-labelledby="cz-confirm-title" class="rounded-lg border border-border bg-popover text-popover-foreground p-0 w-[420px] max-w-[calc(100vw-2rem)] mt-[28vh] mb-auto backdrop:bg-black/60 backdrop:backdrop-blur-xs">
<div class="p-5">
<h2 id="cz-confirm-title" class="text-base font-semibold tracking-tight mb-1"></h2>
<p id="cz-confirm-msg" class="text-[13px] text-muted-foreground leading-relaxed mb-5"></p>
<div class="flex justify-end gap-2">
<button type="button" id="cz-confirm-cancel" class="inline-flex items-center justify-center rounded-md text-sm font-medium border border-input bg-transparent hover:bg-accent hover:text-accent-foreground h-9 px-4">Cancel</button>
<button type="button" id="cz-confirm-ok" class="inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-4 bg-primary text-primary-foreground hover:bg-primary/90">Confirm</button>
</div>
</div>
</dialog>
<div id="cz-footer"></div>
<script>
var dlg = document.getElementById('cz-confirm-dialog');
var okBtn = document.getElementById('cz-confirm-ok');
var OK_BASE = 'inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-4 ';
var pending = null;
function confirmAction(o, done) {
document.getElementById('cz-confirm-title').textContent = o.title;
document.getElementById('cz-confirm-msg').textContent = o.msg;
okBtn.textContent = o.label;
okBtn.className = OK_BASE + (o.danger ? 'bg-destructive text-destructive-foreground hover:bg-destructive/90' : 'bg-primary text-primary-foreground hover:bg-primary/90');
pending = done;
dlg.returnValue = '';
dlg.showModal();
}
okBtn.addEventListener('click', function () { dlg.close('confirm'); });
document.getElementById('cz-confirm-cancel').addEventListener('click', function () { dlg.close('cancel'); });
dlg.addEventListener('click', function (e) { if (e.target === dlg) dlg.close('cancel'); });
dlg.addEventListener('close', function () {
var f = pending; pending = null;
if (f && dlg.returnValue === 'confirm') f();
});
document.querySelectorAll('[data-setting]').forEach(function (sw) {
sw.addEventListener('change', function () {
var on = sw.checked;
sw.checked = !on;
confirmAction({
title: (on ? 'Turn on ' : 'Turn off ') + sw.dataset.setting + '?',
msg: 'This changes the setting for the whole instance.',
label: on ? 'Turn on' : 'Turn off',
danger: !on
}, function () { sw.checked = on; czToast('Setting updated'); });
});
});
var list = document.getElementById('admin-invitations-list');
list.addEventListener('click', function (e) {
var b = e.target.closest('[data-delete-invite]');
if (!b) return;
confirmAction({
title: 'Delete this invitation?',
msg: 'The invite link for ' + b.dataset.email + ' will stop working immediately.',
label: 'Delete',
danger: true
}, function () {
b.parentElement.remove();
if (!list.children.length) list.innerHTML = '<p class="text-sm text-muted-foreground">No invitations yet.</p>';
czToast('Invitation deleted');
});
});
document.getElementById('invite-form').addEventListener('submit', function (e) {
e.preventDefault();
var input = document.getElementById('invite-email');
var email = input.value.trim();
if (!email) return;
var empty = list.querySelector(':scope > p');
if (empty) empty.remove();
var row = document.createElement('div');
row.className = 'rounded-md border border-border p-4 flex justify-between items-start';
var info = document.createElement('div');
info.className = 'space-y-1';
var name = document.createElement('p');
name.className = 'text-sm font-medium text-foreground';
name.textContent = email;
var link = document.createElement('p');
link.className = 'text-xs text-muted-foreground font-mono break-all';
link.textContent = '/invite/Qw5eT2yUiO9pAs3d';
var meta = document.createElement('div');
meta.className = 'flex gap-3 text-xs text-muted-foreground';
meta.innerHTML = '<span>Expires: <time datetime="2026-11-13T00:00:00Z">2026-11-13</time></span> <span class="text-warning">Pending</span>';
info.append(name, link, meta);
var del = document.createElement('button');
del.type = 'button';
del.setAttribute('data-delete-invite', '');
del.dataset.email = email;
del.className = 'inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition-colors disabled:opacity-50 disabled:pointer-events-none bg-destructive text-destructive-foreground hover:bg-destructive/90 h-8 px-3 text-[13px] ml-4 shrink-0';
del.textContent = 'Delete';
row.append(info, del);
list.prepend(row);
input.value = '';
czToast('Invitation sent');
});
document.getElementById('verify-form').addEventListener('submit', function (e) {
e.preventDefault();
this.reset();
czToast('Email marked verified');
});
</script>
<script src="shell.js"></script>
</body>
</html>