First commit

This commit is contained in:
Ettore
2026-05-06 01:51:22 +02:00
commit 78fca8ebc2
56 changed files with 2584 additions and 0 deletions

384
src/static/admin.html Normal file
View File

@@ -0,0 +1,384 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0f0f1a" />
<title>Lagomare Gates Admin</title>
<link rel="icon" type="image/svg+xml" href="/static/logo.svg" />
<link rel="stylesheet" href="/static/style.css" />
<style>
/* ── Admin login ────────────────────────────────────────────────────── */
#login-view {
min-height: 100dvh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1.5rem;
}
#login-view h1 { font-size: 1.5rem; font-weight: 800; margin-bottom: .25rem; }
#login-view .sub { color: var(--text-muted); font-size: .9rem; margin-bottom: 2rem; }
#login-view .card { width: 100%; max-width: 360px; }
/* ── Admin shell ────────────────────────────────────────────────────── */
#admin-view {
display: flex;
flex-direction: column;
min-height: 100dvh;
}
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.5rem;
background: var(--surface);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 10;
}
.app-header h2 { font-size: 1.05rem; font-weight: 800; }
/* ── Tabs ───────────────────────────────────────────────────────────── */
.tabs {
display: flex;
gap: 0;
background: var(--surface);
border-bottom: 1px solid var(--border);
overflow-x: auto;
scrollbar-width: none;
}
.tab-btn {
background: none;
border: none;
border-bottom: 3px solid transparent;
color: var(--text-muted);
cursor: pointer;
font-size: .9rem;
font-weight: 600;
padding: .85rem 1.5rem;
white-space: nowrap;
transition: color .15s, border-color .15s;
}
.tab-btn:hover { color: var(--text); }
.tab-btn.active { color: var(--primary); border-bottom-color: var(--primary); }
/* ── Tab panes ──────────────────────────────────────────────────────── */
.tab-content { flex: 1; padding: 1.5rem; max-width: 960px; margin: 0 auto; width: 100%; }
.tab-pane { display: none; }
.tab-pane.active { display: block; }
/* ── Section header ─────────────────────────────────────────────────── */
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
}
.section-header h3 { font-size: 1rem; font-weight: 700; }
</style>
</head>
<body>
<!-- ── Admin login ─────────────────────────────────────────────────────── -->
<div id="login-view">
<div style="font-size:2rem;margin-bottom:.5rem">🔐</div>
<h1>Admin Panel</h1>
<p class="sub">Lagomare Gates</p>
<div class="card">
<form id="login-form">
<div class="field">
<label for="admin-username">Username</label>
<input id="admin-username" type="text" autocomplete="username" />
</div>
<div class="field">
<label for="admin-password">Password</label>
<input id="admin-password" type="password" autocomplete="current-password" />
</div>
<p id="login-error" class="error-msg hidden"></p>
<button type="submit" class="btn btn-primary btn-full" style="margin-top:.25rem">Sign in</button>
</form>
</div>
</div>
<!-- ── Admin shell ─────────────────────────────────────────────────────── -->
<div id="admin-view" class="hidden">
<header class="app-header">
<h2>⚙️ Admin Lagomare Gates</h2>
<button id="logout-btn" class="btn btn-ghost" style="font-size:.85rem;padding:.5rem 1rem">Logout</button>
</header>
<nav class="tabs">
<button class="tab-btn active" data-tab="keypasses">Keypasses</button>
<button class="tab-btn" data-tab="gates">Gates</button>
<button class="tab-btn admin-only" data-tab="credentials">AVConnect Credentials</button>
<button class="tab-btn" data-tab="stats">Statistics</button>
<button class="tab-btn admin-only" data-tab="admins">Admins</button>
</nav>
<div class="tab-content">
<!-- ── Keypasses pane ─────────────────────────────────────────────── -->
<div id="tab-keypasses" class="tab-pane active">
<div class="section-header">
<h3>Keypasses</h3>
<button id="btn-new-keypass" class="btn btn-primary">+ New Keypass</button>
</div>
<div class="table-wrap card" style="padding:0">
<table id="keypasses-table">
<thead>
<tr>
<th>Code</th>
<th>Description</th>
<th>Gates</th>
<th>Expires</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody id="keypasses-body"></tbody>
</table>
</div>
</div>
<!-- ── Gates pane ─────────────────────────────────────────────────── -->
<div id="tab-gates" class="tab-pane">
<div class="section-header">
<h3>Gates</h3>
<button id="btn-new-gate" class="btn btn-primary admin-only">+ Add Gate</button>
</div>
<div class="table-wrap card" style="padding:0">
<table id="gates-table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Type</th>
<th>AVConnect Macro ID</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody id="gates-body"></tbody>
</table>
</div>
</div>
<!-- ── Credentials pane ───────────────────────────────────────────── -->
<div id="tab-credentials" class="tab-pane">
<h3 style="margin-bottom:1rem">AVConnect Credentials</h3>
<div class="card" style="max-width:440px">
<form id="credentials-form">
<div class="field">
<label for="cred-username">Username</label>
<input id="cred-username" type="text" autocomplete="off" />
</div>
<div class="field">
<label for="cred-password">Password</label>
<input id="cred-password" type="password" autocomplete="new-password"
placeholder="Leave empty to keep current" />
</div>
<p id="cred-error" class="error-msg hidden"></p>
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</div>
<!-- ── Statistics pane ───────────────────────────────────────────── -->
<div id="tab-stats" class="tab-pane">
<div class="section-header">
<h3>Gate Access Log</h3>
<button id="btn-refresh-stats" class="btn btn-ghost" style="font-size:.85rem;padding:.5rem 1rem">↻ Refresh</button>
</div>
<div class="table-wrap card" style="padding:0">
<table id="stats-table">
<thead>
<tr>
<th>Time</th>
<th>Keypass</th>
<th>Gate</th>
<th>IP</th>
<th>User Agent</th>
<th>Result</th>
</tr>
</thead>
<tbody id="stats-body"></tbody>
</table>
</div>
</div>
<!-- ── Admins pane ──────────────────────────────────────────────────── -->
<div id="tab-admins" class="tab-pane">
<div class="section-header">
<h3>Admin Users</h3>
<button id="btn-new-admin" class="btn btn-primary">+ Add Admin</button>
</div>
<div class="table-wrap card" style="padding:0">
<table id="admins-table">
<thead>
<tr>
<th>Username</th>
<th></th>
</tr>
</thead>
<tbody id="admins-body"></tbody>
</table>
</div>
</div>
</div><!-- /.tab-content -->
</div><!-- /#admin-view -->
<!-- ── Keypass modal ───────────────────────────────────────────────────── -->
<div id="keypass-modal" class="modal-backdrop hidden">
<div class="modal">
<h3>New Keypass</h3>
<form id="keypass-form">
<div class="field">
<label for="kp-desc">Description</label>
<input id="kp-desc" type="text" placeholder="e.g. Guests June 2026" required />
</div>
<div class="field">
<label for="kp-code">Code <span style="color:var(--text-muted);font-weight:400">(leave empty to auto-generate)</span></label>
<input id="kp-code" type="text" autocomplete="off" autocorrect="off" autocapitalize="characters"
spellcheck="false" placeholder="Auto-generated"
style="font-family:monospace;letter-spacing:.1em;text-transform:uppercase" maxlength="32" />
</div>
<div class="field" id="kp-expires-field">
<label for="kp-expires">Expiry date &amp; time</label>
<input id="kp-expires" type="datetime-local" />
<label style="display:flex;align-items:center;gap:.75rem;cursor:pointer;color:var(--text);margin-top:.6rem">
<input type="checkbox" id="kp-never-expires" style="width:1rem;height:1rem;flex-shrink:0" />
<span style="font-size:.9rem">Never expires</span>
</label>
</div>
<div class="field">
<label style="margin-bottom:.5rem">Allowed gates</label>
<div id="kp-gates-container" style="display:flex;flex-direction:column;gap:.3rem;max-height:180px;overflow-y:auto">
<label id="kp-all-gates-row" style="display:flex;align-items:center;gap:.75rem;cursor:pointer;padding:.5rem .75rem;background:var(--surface2);border-radius:6px;border:1px solid var(--border);margin:0">
<input type="checkbox" id="kp-all-gates" checked style="width:1rem;height:1rem;flex-shrink:0" />
<span>All gates</span>
</label>
<div id="kp-gate-checks" style="display:flex;flex-direction:column;gap:.3rem"></div>
</div>
</div>
<p id="kp-error" class="error-msg hidden"></p>
<div class="modal-actions">
<button type="button" id="kp-cancel" class="btn btn-ghost">Cancel</button>
<button type="submit" class="btn btn-primary">Create</button>
</div>
</form>
</div>
</div>
<!-- ── Keypass edit modal ────────────────────────────────────────────────── -->
<div id="kp-edit-modal" class="modal-backdrop hidden">
<div class="modal">
<h3>Edit Keypass</h3>
<form id="kp-edit-form">
<input type="hidden" id="kp-edit-id" />
<div class="field">
<label for="kp-edit-desc">Description</label>
<input id="kp-edit-desc" type="text" required />
</div>
<div class="field">
<label for="kp-edit-expires">Expiry date &amp; time</label>
<input id="kp-edit-expires" type="datetime-local" />
<label style="display:flex;align-items:center;gap:.75rem;cursor:pointer;color:var(--text);margin-top:.6rem">
<input type="checkbox" id="kp-edit-never" style="width:1rem;height:1rem;flex-shrink:0" />
<span style="font-size:.9rem">Never expires</span>
</label>
</div>
<div class="field">
<label style="margin-bottom:.5rem">Allowed gates</label>
<div id="kp-edit-gates-container" style="display:flex;flex-direction:column;gap:.3rem;max-height:180px;overflow-y:auto">
<label id="kp-edit-all-gates-row" style="display:flex;align-items:center;gap:.75rem;cursor:pointer;padding:.5rem .75rem;background:var(--surface2);border-radius:6px;border:1px solid var(--border);margin:0">
<input type="checkbox" id="kp-edit-all-gates" style="width:1rem;height:1rem;flex-shrink:0" />
<span>All gates</span>
</label>
<div id="kp-edit-gate-checks" style="display:flex;flex-direction:column;gap:.3rem"></div>
</div>
</div>
<p id="kp-edit-error" class="error-msg hidden"></p>
<div class="modal-actions">
<button type="button" id="kp-edit-cancel" class="btn btn-ghost">Cancel</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</form>
</div>
</div>
<!-- ── Gate modal ──────────────────────────────────────────────────────── -->
<div id="gate-modal" class="modal-backdrop hidden">
<div class="modal">
<h3 id="gate-modal-title">Add Gate</h3>
<form id="gate-form">
<input type="hidden" id="gate-edit-id" />
<div class="field">
<label for="gate-name">Name</label>
<input id="gate-name" type="text" placeholder="e.g. Main entrance Car" required />
</div>
<div class="field">
<label for="gate-type">Type</label>
<select id="gate-type">
<option value="car">Car</option>
<option value="pedestrian">Pedestrian</option>
</select>
</div>
<div class="field">
<label for="gate-avconnect-macro-id">AVConnect Macro ID</label>
<input id="gate-avconnect-macro-id" type="text" placeholder="e.g. 42" required />
</div>
<div class="field">
<label for="gate-status">Status</label>
<select id="gate-status">
<option value="enabled">Enabled</option>
<option value="disabled">Disabled</option>
</select>
</div>
<p id="gate-error" class="error-msg hidden"></p>
<div class="modal-actions">
<button type="button" id="gate-cancel" class="btn btn-ghost">Cancel</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</form>
</div>
</div>
<!-- ── Admin user modal ──────────────────────────────────────────────────── -->
<div id="admin-modal" class="modal-backdrop hidden">
<div class="modal">
<h3>Add Admin</h3>
<form id="admin-form">
<div class="field">
<label for="admin-new-username">Username</label>
<input id="admin-new-username" type="text" autocomplete="off" required />
</div>
<div class="field">
<label for="admin-new-password">Password</label>
<input id="admin-new-password" type="password" autocomplete="new-password" required />
</div>
<div class="field">
<label for="admin-new-role">Role</label>
<select id="admin-new-role">
<option value="admin">Admin (full access)</option>
<option value="manager">Manager (keypasses only)</option>
</select>
</div>
<p id="admin-modal-error" class="error-msg hidden"></p>
<div class="modal-actions">
<button type="button" id="admin-cancel" class="btn btn-ghost">Cancel</button>
<button type="submit" class="btn btn-primary">Create</button>
</div>
</form>
</div>
</div>
<!-- ── Toast ───────────────────────────────────────────────────────────── -->
<div id="toast" class="toast hidden" aria-live="assertive"></div>
<script src="/static/admin.js"></script>
</body>
</html>