365 lines
14 KiB
HTML
365 lines
14 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 & 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>
|
|
<input type="text" id="acl-data">
|
|
</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>
|
|
<input type="text" id="group-member-data">
|
|
</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>
|
|
|
|
|