322 lines
13 KiB
HTML
322 lines
13 KiB
HTML
<!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>
|