3m-portal/admin.html
Vadym Samoilenko 53a85c788d Add full auth system: SQLite sessions, email invites, admin console
- Real email/password login backed by SQLite (better-sqlite3)
- HttpOnly cookie sessions with 8h sliding TTL
- Admin role: invite users via Mailgun magic-link, manage roles/status
- Per-user One2Edit username mapping for job filtering
- Self-service forgot-password / reset-password via email
- Admin console (admin.html) with user table, invite modal, row actions
- New pages: change-password, forgot-password, reset-password, accept-invite
- Gated /api proxy: requires valid session, anti-hijack sessionId check
- Bootstrap initial admins from INITIAL_ADMINS env var on first boot
- Remove Oliver login button, SSO buttons, and legacy api.js/login.js
- deploy.sh: add build-essential (for native module), npm install, data dir

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 11:26:40 +01:00

87 lines
4 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3M - Admin Console</title>
<link rel="icon" type="image/png" href="Images/Favicon_logo.png">
<link rel="stylesheet" href="styles.css">
</head>
<body class="dashboard-page">
<div class="dashboard-header-bar">
<div class="dashboard-title-section">
<img src="Images/login_logo.png" alt="3M Logo" class="dashboard-logo">
<h1>Admin Console</h1>
</div>
<div class="dashboard-actions">
<button class="refresh-button" onclick="window.location.href='dashboard.html'">&#8592; Dashboard</button>
<button id="inviteBtn" class="admin-button">Invite User</button>
</div>
</div>
<div class="dashboard-content">
<div id="adminError" class="error-message" style="display:none;"></div>
<div id="adminSuccess" class="success-message" style="display:none;"></div>
<div class="admin-card">
<h2 class="admin-section-title">Users</h2>
<div id="usersLoading" class="loading-message" style="padding:30px 0;">Loading users...</div>
<div id="usersTableWrap" style="display:none; overflow-x:auto;">
<table class="admin-table" id="usersTable">
<thead>
<tr>
<th>Email</th>
<th>One2Edit User</th>
<th>Role</th>
<th>Status</th>
<th>Last Login</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="usersTableBody"></tbody>
</table>
</div>
</div>
</div>
<!-- Invite Modal -->
<div id="inviteModal" class="modal-overlay" style="display:none;" role="dialog" aria-modal="true" aria-labelledby="inviteModalTitle">
<div class="modal-box">
<div class="modal-header">
<h3 id="inviteModalTitle">Invite New User</h3>
<button class="modal-close" id="inviteModalClose" aria-label="Close">&times;</button>
</div>
<div class="modal-body">
<div id="inviteError" class="error-message" style="display:none;"></div>
<div id="inviteSuccess" class="success-message" style="display:none;"></div>
<div id="inviteFormWrap">
<div class="form-group">
<label class="form-label" for="inviteEmail">Email address</label>
<input type="email" id="inviteEmail" class="form-input" placeholder="user@example.com" autocomplete="off">
</div>
<div class="form-group">
<label class="form-label" for="inviteO2EUser">One2Edit username</label>
<input type="text" id="inviteO2EUser" class="form-input" placeholder="firstname.lastname" autocomplete="off">
</div>
<div class="form-group">
<label class="form-label" for="inviteRole">Role</label>
<select id="inviteRole" class="form-input">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</div>
</div>
</div>
<div class="modal-footer" id="inviteFooter">
<button class="btn-secondary" id="inviteModalCancel">Cancel</button>
<button class="tmm-button login-button" id="inviteSubmitBtn" style="flex:0 0 auto; padding:10px 24px;">
<span id="inviteSubmitText">Send Invite</span>
<span id="inviteSubmitSpinner" class="spinner" style="display:none;"></span>
</button>
</div>
</div>
</div>
<script src="admin.js"></script>
</body>
</html>