notXCS/public/dashboard/index.html

373 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - NOTXCS</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div class="app-shell">
<aside class="sidebar">
<h2>NOTXCS</h2>
<div class="user-info">Signed in as <strong id="username-display"></strong> <span id="role-badge" class="badge type-badge"></span></div>
<div class="org-switcher">
<label for="org-select">Organization</label>
<div class="org-switcher-row">
<select id="org-select"></select>
<button type="button" class="secondary" id="manage-org-btn" title="Manage organization">Manage</button>
</div>
<button type="button" class="secondary" id="new-org-btn" style="width:100%;margin-top:8px;">+ New organization</button>
</div>
<a id="admin-link" class="hidden" href="/admin" style="display:block;margin-bottom:12px;color:var(--accent);font-size:13px;">Manage users</a>
<button class="secondary" id="logout-btn" style="margin-bottom: 16px;">Log out</button>
<ul class="place-list" id="place-list"></ul>
<div class="card hidden" id="add-place-card" style="margin-top: 16px;">
<h3 style="margin-top:0;font-size:14px;">Add place</h3>
<form id="add-place-form">
<label for="place-name">Name <span style="color:var(--muted);font-weight:normal;">(optional)</span></label>
<input type="text" id="place-name">
<div id="add-place-custom-fields" class="hidden">
<label for="place-id">Place ID <span style="color:var(--muted);font-weight:normal;">(leave blank to auto-generate)</span></label>
<input type="text" id="place-id">
<label for="place-api-key">API Key <span style="color:var(--muted);font-weight:normal;">(leave blank to auto-generate)</span></label>
<input type="text" id="place-api-key">
</div>
<button type="submit" class="primary">Add place</button>
</form>
</div>
</aside>
<main class="main">
<div id="lockdown-banner" class="error hidden" style="display:block;margin-bottom:16px;">
Site-wide lockdown is active - every reader at every place is disabled until an admin lifts it.
</div>
<div id="no-place" class="empty-state">
Select or create a place from the sidebar to manage its readers.
</div>
<div id="place-view" class="hidden">
<div class="main-header">
<h1 id="place-title"></h1>
<button class="secondary" id="download-kit-btn">Download kit</button>
<button class="danger" id="engage-place-lockdown-btn">Engage lockdown</button>
<button class="secondary hidden" id="lift-place-lockdown-btn">Lift lockdown</button>
<button class="danger" id="delete-place-btn">Delete place</button>
</div>
<div id="place-lockdown-banner" class="error hidden" style="display:block;margin-bottom:16px;">
This place is in lockdown - every reader here is disabled until it's lifted.
</div>
<div class="card">
<div class="card-header">
<h3>Readers</h3>
<form id="add-reader-form" class="inline-form card-header-form">
<div class="field-group">
<input type="text" id="reader-name" placeholder="Reader name" required>
</div>
<button type="submit" class="primary">Add reader</button>
</form>
</div>
<div id="readers-empty" class="empty-state hidden">No readers yet for this place.</div>
<div class="readers-grid" id="readers-grid"></div>
</div>
<div class="card">
<h3>Add access group</h3>
<p style="margin-top:-8px;color:var(--muted);font-size:13px;">Access groups (e.g. "Staff") bundle multiple users/cards/rank rules so they can be assigned to multiple readers at once.</p>
<form id="add-access-group-form" class="inline-form">
<div class="field-group">
<label for="access-group-name">Name</label>
<input type="text" id="access-group-name" required>
</div>
<button type="submit" class="primary">Add group</button>
</form>
</div>
<div class="card">
<h3>Access groups</h3>
<div id="access-groups-empty" class="empty-state hidden">No access groups yet for this place.</div>
<ul class="acl-list" id="access-groups-list"></ul>
</div>
<div class="card">
<h3>Name</h3>
<form id="set-place-name-form" class="inline-form">
<div class="field-group">
<label for="place-name-custom">Place name <span style="color:var(--muted);font-weight:normal;">(leave blank to show the place ID instead)</span></label>
<input type="text" id="place-name-custom">
</div>
<button type="submit" class="primary">Save</button>
</form>
</div>
<div class="card">
<h3>API Key</h3>
<div class="inline-form">
<div class="field-group">
<label for="place-id-display">Place ID</label>
<input type="text" id="place-id-display" readonly>
</div>
<div class="field-group">
<label for="place-api-key-display">Current key</label>
<input type="password" id="place-api-key-display" readonly>
</div>
<button type="button" class="secondary" id="toggle-api-key-btn">Show</button>
<button type="button" class="secondary" id="copy-api-key-btn">Copy</button>
<button type="button" class="secondary" id="regenerate-api-key-btn">Regenerate</button>
</div>
<form id="set-api-key-form" class="inline-form hidden" style="margin-top:8px;">
<div class="field-group">
<label for="place-api-key-custom">Set custom key <span style="color:var(--muted);font-weight:normal;">(leave blank to auto-generate)</span></label>
<input type="text" id="place-api-key-custom">
</div>
<button type="submit" class="primary">Save</button>
</form>
</div>
<div class="card">
<h3>Discord webhook</h3>
<p style="margin-top:-8px;color:var(--muted);font-size:13px;">Post an alert to a Discord channel when readers at this place grant/deny access or get armed/disarmed/enabled/disabled. The alert itself is sent as an embed; the optional message below is sent as plain content alongside it (e.g. to ping a role or user).</p>
<form id="webhook-form">
<div class="field-group">
<label for="webhook-url">Webhook URL</label>
<input type="text" id="webhook-url" placeholder="https://discord.com/api/webhooks/...">
</div>
<div class="field-group">
<label for="webhook-content">Custom message content <span style="color:var(--muted);font-weight:normal;">(optional, e.g. @here or a role mention)</span></label>
<input type="text" id="webhook-content" placeholder="@here">
</div>
<div class="checkbox-row">
<input type="checkbox" id="webhook-access-granted">
<label for="webhook-access-granted">Access granted</label>
</div>
<div class="checkbox-row">
<input type="checkbox" id="webhook-access-denied">
<label for="webhook-access-denied">Access denied</label>
</div>
<div class="checkbox-row">
<input type="checkbox" id="webhook-reader-arm">
<label for="webhook-reader-arm">Reader armed / disarmed</label>
</div>
<div class="checkbox-row">
<input type="checkbox" id="webhook-reader-enable">
<label for="webhook-reader-enable">Reader enabled / disabled</label>
</div>
<div class="inline-form" style="margin-top:12px;">
<button type="submit" class="primary">Save</button>
<button type="button" class="secondary" id="webhook-test-btn">Send test</button>
</div>
</form>
</div>
<div class="card" id="move-org-card">
<h3>Move to another organization</h3>
<p style="margin-top:-8px;color:var(--muted);font-size:13px;">Move this place to a different organization you have Edit access in. Everyone in the current organization will lose access to it.</p>
<form id="move-org-form" class="inline-form">
<div class="field-group">
<label for="move-org-select">Organization</label>
<select id="move-org-select"></select>
</div>
<button type="submit" class="secondary">Move place</button>
</form>
</div>
</div>
</main>
</div>
<div id="acl-modal" class="modal-overlay hidden">
<div class="modal">
<div class="modal-header">
<h2>Allowed persons &amp; credentials — <span id="acl-reader-name"></span></h2>
<button class="secondary" id="acl-close-btn">Close</button>
</div>
<div class="card">
<h3>Add entry</h3>
<form id="add-acl-form" class="inline-form">
<div class="field-group">
<label for="acl-type">Type</label>
<select id="acl-type">
<option value="0">User ID</option>
<option value="1">Card number</option>
<option value="2">Group exact rank</option>
<option value="3">Group minimum rank</option>
<option value="4">Allow all</option>
<option value="5">Access group</option>
</select>
</div>
<div class="field-group" id="acl-data-group">
<label for="acl-data">User ID</label>
<div class="input-with-button">
<input type="text" id="acl-data">
<button type="button" class="secondary" id="acl-data-lookup-btn">Look up username</button>
</div>
<span class="field-hint hidden" id="acl-data-hint"></span>
</div>
<div class="field-group hidden" id="acl-group-id-group">
<label for="acl-group-id">Group ID</label>
<input type="text" id="acl-group-id">
</div>
<div class="field-group hidden" id="acl-group-rank-group">
<label for="acl-group-rank">Rank</label>
<input type="number" id="acl-group-rank" min="0">
</div>
<div class="field-group hidden" id="acl-access-group-group">
<label for="acl-access-group">Access group</label>
<select id="acl-access-group"></select>
</div>
<button type="submit" class="primary">Add entry</button>
</form>
</div>
<div class="card">
<h3>Entries</h3>
<div id="acl-empty" class="empty-state hidden">No entries yet. Access will be denied for everyone until an entry is added.</div>
<ul class="acl-list" id="acl-list"></ul>
</div>
</div>
</div>
<div id="logs-modal" class="modal-overlay hidden">
<div class="modal">
<div class="modal-header">
<h2>Scan logs — <span id="logs-reader-name"></span></h2>
<button class="secondary" id="logs-close-btn">Close</button>
</div>
<div class="card">
<h3>Recent scans</h3>
<div id="logs-empty" class="empty-state hidden">No scans have been recorded for this reader yet.</div>
<ul class="acl-list" id="logs-list"></ul>
</div>
</div>
</div>
<div id="group-modal" class="modal-overlay hidden">
<div class="modal">
<div class="modal-header">
<h2>Access group members — <span id="group-name"></span></h2>
<button class="secondary" id="group-close-btn">Close</button>
</div>
<div class="card">
<h3>Add member</h3>
<form id="add-group-member-form" class="inline-form">
<div class="field-group">
<label for="group-member-type">Type</label>
<select id="group-member-type">
<option value="0">User ID</option>
<option value="1">Card number</option>
<option value="2">Group exact rank</option>
<option value="3">Group minimum rank</option>
<option value="4">Allow all</option>
</select>
</div>
<div class="field-group" id="group-member-data-group">
<label for="group-member-data">User ID</label>
<div class="input-with-button">
<input type="text" id="group-member-data">
<button type="button" class="secondary" id="group-member-data-lookup-btn">Look up username</button>
</div>
<span class="field-hint hidden" id="group-member-data-hint"></span>
</div>
<div class="field-group hidden" id="group-member-group-id-group">
<label for="group-member-group-id">Group ID</label>
<input type="text" id="group-member-group-id">
</div>
<div class="field-group hidden" id="group-member-group-rank-group">
<label for="group-member-group-rank">Rank</label>
<input type="number" id="group-member-group-rank" min="0">
</div>
<button type="submit" class="primary">Add member</button>
</form>
</div>
<div class="card">
<h3>Members</h3>
<div id="group-members-empty" class="empty-state hidden">No members yet. This group grants no access until members are added.</div>
<ul class="acl-list" id="group-members-list"></ul>
</div>
</div>
</div>
<div id="new-org-modal" class="modal-overlay hidden">
<div class="modal" style="max-width:420px;">
<div class="modal-header">
<h2>New organization</h2>
<button class="secondary" id="new-org-close-btn">Close</button>
</div>
<form id="new-org-form">
<label for="new-org-name">Organization name</label>
<input type="text" id="new-org-name" required>
<button type="submit" class="primary">Create organization</button>
</form>
</div>
</div>
<div id="org-modal" class="modal-overlay hidden">
<div class="modal">
<div class="modal-header">
<h2>Manage organization — <span id="org-modal-name"></span></h2>
<button class="secondary" id="org-modal-close-btn">Close</button>
</div>
<div class="card" id="org-rename-card">
<h3>Organization name</h3>
<form id="org-rename-form" class="inline-form">
<div class="field-group">
<label for="org-rename-input">Name</label>
<input type="text" id="org-rename-input" required>
</div>
<button type="submit" class="primary">Save</button>
</form>
</div>
<div class="card" id="org-add-member-card">
<h3>Add member</h3>
<p style="margin-top:-8px;color:var(--muted);font-size:13px;">Add another user to this organization by username, and choose their role within it.</p>
<form id="org-add-member-form" class="inline-form">
<div class="field-group">
<label for="org-member-username">Username</label>
<input type="text" id="org-member-username" required>
</div>
<div class="field-group">
<label for="org-member-role">Role</label>
<select id="org-member-role">
<option value="1">View only</option>
<option value="2">Edit</option>
<option value="3">Admin</option>
</select>
</div>
<button type="submit" class="primary">Add member</button>
</form>
</div>
<div class="card">
<h3>Members</h3>
<div id="org-members-empty" class="empty-state hidden">No members yet.</div>
<ul class="acl-list" id="org-members-list"></ul>
</div>
<div class="card" id="org-delete-card">
<h3>Delete organization</h3>
<p style="margin-top:-8px;color:var(--muted);font-size:13px;">The organization must own no places before it can be deleted.</p>
<button type="button" class="danger" id="org-delete-btn">Delete organization</button>
</div>
</div>
</div>
<script src="/js/dashboard.js"></script>
</body>
</html>