This commit is contained in:
jpmvaz
2026-09-13 20:07:03 +01:00
commit 5482731e78
37 changed files with 15639 additions and 0 deletions
+321
View File
@@ -0,0 +1,321 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PST Archive · Admin Panel</title>
<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=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,500;0,9..144,700;1,9..144,400&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="admin.css" />
<script>
(function() {
try {
var t = localStorage.getItem("pst-theme");
if (t !== "light" && t !== "dark") {
t = (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light";
}
document.documentElement.dataset.theme = t;
} catch (e) {
document.documentElement.dataset.theme = "light";
}
})();
</script>
</head>
<body>
<div class="grain"></div>
<!-- Floating theme toggle (visible on login/setup screens) -->
<button id="themeToggleFloat" class="theme-toggle theme-toggle-float" title="Toggle theme" aria-label="Toggle theme">
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4.5"/><line x1="12" y1="2" x2="12" y2="4.5"/><line x1="12" y1="19.5" x2="12" y2="22"/><line x1="2" y1="12" x2="4.5" y2="12"/><line x1="19.5" y1="12" x2="22" y2="12"/><line x1="4.9" y1="4.9" x2="6.7" y2="6.7"/><line x1="17.3" y1="17.3" x2="19.1" y2="19.1"/><line x1="4.9" y1="19.1" x2="6.7" y2="17.3"/><line x1="17.3" y1="6.7" x2="19.1" y2="4.9"/></svg>
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M20 14.5A9 9 0 019.5 4a7.5 7.5 0 1010.5 10.5z"/></svg>
</button>
<!-- FIRST-RUN NOTICE -->
<section id="setupNotice" class="auth-screen hidden">
<div class="auth-card">
<div class="auth-brand">
<span class="admin-badge">ADMIN</span>
<h1>Setup Required</h1>
<p class="auth-tagline">No administrator exists yet</p>
</div>
<div class="auth-form">
<p class="auth-intro">Please complete first-run setup in the main application before signing in here.</p>
<a id="gotoMainBtn" class="auth-btn" href="#">Open main app →</a>
</div>
</div>
</section>
<!-- LOGIN -->
<section id="loginScreen" class="auth-screen hidden">
<div class="auth-card">
<div class="auth-brand">
<span class="admin-badge">ADMIN</span>
<h1>PST Archive</h1>
<p class="auth-tagline">Admin panel · sign in</p>
</div>
<form id="loginForm" class="auth-form" novalidate>
<label class="auth-label">
<span>Username</span>
<input type="text" name="username" autocomplete="username" required />
</label>
<label class="auth-label">
<span>Password</span>
<input type="password" name="password" autocomplete="current-password" required />
</label>
<label id="mfaField" class="auth-label hidden">
<span>Authentication code</span>
<input type="text" name="totp_code" autocomplete="one-time-code" inputmode="numeric" pattern="[0-9]*" maxlength="6" placeholder="6-digit code" />
<small>From your authenticator app</small>
</label>
<button type="submit" class="auth-btn">Sign in</button>
<div id="loginError" class="auth-error hidden"></div>
</form>
</div>
</section>
<!-- ADMIN APP -->
<div id="adminApp" class="hidden">
<header class="masthead">
<div class="masthead-left">
<span class="admin-badge small">ADMIN</span>
<div class="masthead-title">
<h1>User Management</h1>
<p class="tagline">PST Archive · control panel</p>
</div>
</div>
<div class="masthead-right">
<button id="backToAppBtn" class="back-btn" title="Return to the archive">← Archive</button>
<span id="userChip" class="user-chip"></span>
<button id="themeToggle" class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4.5"/><line x1="12" y1="2" x2="12" y2="4.5"/><line x1="12" y1="19.5" x2="12" y2="22"/><line x1="2" y1="12" x2="4.5" y2="12"/><line x1="19.5" y1="12" x2="22" y2="12"/><line x1="4.9" y1="4.9" x2="6.7" y2="6.7"/><line x1="17.3" y1="17.3" x2="19.1" y2="19.1"/><line x1="4.9" y1="19.1" x2="6.7" y2="17.3"/><line x1="17.3" y1="6.7" x2="19.1" y2="4.9"/></svg>
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M20 14.5A9 9 0 019.5 4a7.5 7.5 0 1010.5 10.5z"/></svg>
</button>
<button id="logoutBtn" class="logout-btn">Sign out</button>
</div>
</header>
<main class="admin-main">
<nav class="admin-tabs" role="tablist">
<button class="admin-tab active" data-tab="users" role="tab">Users</button>
<button class="admin-tab" data-tab="folders" role="tab">Folders</button>
<button class="admin-tab" data-tab="files" role="tab">Files</button>
<button class="admin-tab" data-tab="permissions" role="tab">Permissions</button>
</nav>
<!-- USERS TAB -->
<section class="admin-panel" data-panel="users">
<div class="admin-toolbar">
<div>
<h2 class="section-heading">Users</h2>
<p class="toolbar-meta" id="userCount"></p>
</div>
<button id="newUserBtn" class="primary-btn">+ New user</button>
</div>
<div class="user-list-wrap">
<table class="user-table">
<thead>
<tr>
<th>Username</th>
<th>Role</th>
<th>MFA</th>
<th>Created</th>
<th>Last login</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="userTableBody">
<tr><td colspan="6" class="loading-row">Loading users…</td></tr>
</tbody>
</table>
</div>
</section>
<!-- FOLDERS TAB -->
<section class="admin-panel hidden" data-panel="folders">
<div class="admin-toolbar">
<div>
<h2 class="section-heading">PST Folders</h2>
<p class="toolbar-meta" id="folderCount"></p>
</div>
<button id="newFolderBtn" class="primary-btn">+ New folder</button>
</div>
<div class="user-list-wrap">
<table class="user-table">
<thead>
<tr>
<th>Name</th>
<th>Archives inside</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="folderTableBody">
<tr><td colspan="4" class="loading-row">Loading folders…</td></tr>
</tbody>
</table>
</div>
</section>
<!-- FILES TAB -->
<section class="admin-panel hidden" data-panel="files">
<div class="admin-toolbar">
<div>
<h2 class="section-heading">PST Files</h2>
<p class="toolbar-meta" id="fileCount"></p>
</div>
</div>
<div class="user-list-wrap">
<table class="user-table">
<thead>
<tr>
<th>Filename</th>
<th>Folder</th>
<th>Size</th>
<th>Status</th>
<th>Messages</th>
<th>Uploaded</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="fileTableBody">
<tr><td colspan="7" class="loading-row">Loading files…</td></tr>
</tbody>
</table>
</div>
</section>
<!-- PERMISSIONS TAB -->
<section class="admin-panel hidden" data-panel="permissions">
<div class="admin-toolbar">
<div>
<h2 class="section-heading">Folder Access Permissions</h2>
<p class="toolbar-meta" id="permissionsMeta"></p>
</div>
<div class="view-switch" role="tablist">
<button class="view-btn active" data-view="by-user" type="button">By user</button>
<button class="view-btn" data-view="by-folder" type="button">By folder</button>
</div>
</div>
<p class="perm-hint">
Check a box to grant a non-admin user access to a folder. Administrators
always see every folder regardless of these settings. "Uncategorized" archives
are visible to everyone by default.
</p>
<div class="perm-matrix-wrap">
<table class="user-table perm-matrix" id="permMatrix">
<thead id="permMatrixHead"></thead>
<tbody id="permMatrixBody">
<tr><td class="loading-row">Loading…</td></tr>
</tbody>
</table>
</div>
</section>
</main>
<footer class="site-footer">© 2026 Martinhal IT - Joao Vaz - Version 2.2</footer>
</div>
<!-- CREATE USER MODAL -->
<div id="createUserModal" class="modal hidden">
<div class="modal-card">
<button class="modal-close" data-close>×</button>
<h2>Create new user</h2>
<form id="createUserForm" class="auth-form" novalidate>
<label class="auth-label">
<span>Username</span>
<input type="text" name="username" autocomplete="off" required minlength="3" maxlength="64" />
</label>
<label class="auth-label">
<span>Password</span>
<input type="password" name="password" autocomplete="new-password" required minlength="8" />
<small>Minimum 8 characters</small>
</label>
<label class="auth-label">
<span>Role</span>
<select name="role">
<option value="user">User (can view archives)</option>
<option value="admin">Administrator (full access)</option>
</select>
</label>
<button type="submit" class="auth-btn">Create user</button>
<div id="createError" class="auth-error hidden"></div>
</form>
</div>
</div>
<!-- RESET PASSWORD MODAL -->
<div id="resetPasswordModal" class="modal hidden">
<div class="modal-card">
<button class="modal-close" data-close>×</button>
<h2>Reset password</h2>
<p class="modal-subtitle" id="resetPasswordSubject"></p>
<form id="resetPasswordForm" class="auth-form" novalidate>
<label class="auth-label">
<span>New password</span>
<input type="password" name="password" autocomplete="new-password" required minlength="8" />
<small>Minimum 8 characters</small>
</label>
<button type="submit" class="auth-btn">Update password</button>
<div id="resetError" class="auth-error hidden"></div>
</form>
</div>
</div>
<!-- MFA MODAL -->
<div id="mfaModal" class="modal hidden">
<div class="modal-card wide">
<button class="modal-close" data-close>×</button>
<h2>Multi-factor authentication</h2>
<p class="modal-subtitle" id="mfaSubject"></p>
<div id="mfaPanel" class="mfa-panel">
<div class="mfa-status" id="mfaCurrentStatus"></div>
<div class="mfa-actions" id="mfaActions"></div>
</div>
<div id="mfaEnrollPanel" class="mfa-enroll hidden">
<div class="enroll-grid">
<div class="enroll-qr">
<img id="mfaQrImage" src="" alt="TOTP QR code" />
</div>
<div class="enroll-details">
<p class="enroll-step">1. Scan this QR code with your authenticator app (Google Authenticator, Authy, 1Password, etc.).</p>
<p class="enroll-step">2. Or manually enter this secret:</p>
<code id="mfaSecretText" class="secret-box"></code>
<p class="enroll-step">3. Enter the 6-digit code from the app to confirm.</p>
<form id="mfaConfirmForm" class="auth-form confirm-form">
<label class="auth-label">
<span>Verification code</span>
<input type="text" name="totp_code" inputmode="numeric" pattern="[0-9]*" maxlength="6" required />
</label>
<button type="submit" class="auth-btn">Confirm & enable</button>
<div id="mfaConfirmError" class="auth-error hidden"></div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- FOLDER CREATE/RENAME MODAL -->
<div id="folderModal" class="modal hidden">
<div class="modal-card">
<button class="modal-close" data-close>×</button>
<h2 id="folderModalTitle">New folder</h2>
<form id="folderForm" class="auth-form" novalidate>
<label class="auth-label">
<span>Folder name</span>
<input type="text" name="name" maxlength="80" required autocomplete="off" />
</label>
<button type="submit" class="auth-btn" id="folderSubmitBtn">Create folder</button>
<div id="folderError" class="auth-error hidden"></div>
</form>
</div>
</div>
<div id="toast" class="toast hidden"></div>
<script src="admin.js"></script>
</body>
</html>