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
@@ -0,0 +1,256 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PST Archive · Mail Indexer</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="/styles.css" />
<script>
// Inline theme bootstrap — runs before CSS so no flash of wrong theme
(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 all 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>
<!-- SETUP WIZARD -->
<section id="setupScreen" class="auth-screen hidden">
<div class="auth-card">
<div class="auth-brand">
<svg viewBox="0 0 40 40" width="44" height="44" aria-hidden="true">
<rect x="3" y="8" width="34" height="26" fill="none" stroke="currentColor" stroke-width="1.5"/>
<path d="M3 8 L20 22 L37 8" fill="none" stroke="currentColor" stroke-width="1.5"/>
<line x1="3" y1="34" x2="14" y2="22" stroke="currentColor" stroke-width="1.5"/>
<line x1="37" y1="34" x2="26" y2="22" stroke="currentColor" stroke-width="1.5"/>
</svg>
<h1>PST Archive</h1>
<p class="auth-tagline">First-run setup</p>
</div>
<form id="setupForm" class="auth-form" novalidate>
<p class="auth-intro">Create the primary administrator account. This user will have full access to the archive and the admin panel.</p>
<label class="auth-label">
<span>Username</span>
<input type="text" name="username" autocomplete="username" 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. Use a passphrase you will remember.</small>
</label>
<label class="auth-label">
<span>Confirm password</span>
<input type="password" name="password2" autocomplete="new-password" required minlength="8" />
</label>
<button type="submit" class="auth-btn">Create administrator</button>
<div id="setupError" class="auth-error hidden"></div>
</form>
</div>
</section>
<!-- LOGIN -->
<section id="loginScreen" class="auth-screen hidden">
<div class="auth-card">
<div class="auth-brand">
<svg viewBox="0 0 40 40" width="44" height="44" aria-hidden="true">
<rect x="3" y="8" width="34" height="26" fill="none" stroke="currentColor" stroke-width="1.5"/>
<path d="M3 8 L20 22 L37 8" fill="none" stroke="currentColor" stroke-width="1.5"/>
<line x1="3" y1="34" x2="14" y2="22" stroke="currentColor" stroke-width="1.5"/>
<line x1="37" y1="34" x2="26" y2="22" stroke="currentColor" stroke-width="1.5"/>
</svg>
<h1>PST Archive</h1>
<p class="auth-tagline">Sign in to continue</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>
<!-- MAIN APP -->
<div id="mainApp" class="hidden">
<header class="masthead">
<div class="masthead-left">
<div class="logo-mark">
<svg viewBox="0 0 40 40" width="36" height="36" aria-hidden="true">
<rect x="3" y="8" width="34" height="26" fill="none" stroke="currentColor" stroke-width="1.5"/>
<path d="M3 8 L20 22 L37 8" fill="none" stroke="currentColor" stroke-width="1.5"/>
<line x1="3" y1="34" x2="14" y2="22" stroke="currentColor" stroke-width="1.5"/>
<line x1="37" y1="34" x2="26" y2="22" stroke="currentColor" stroke-width="1.5"/>
</svg>
</div>
<div class="masthead-title">
<h1>PST Archive</h1>
<p class="tagline">Forensic mail indexer · v1.0</p>
</div>
</div>
<div class="masthead-right">
<span id="statusDot" class="status-dot"></span>
<span id="statusText" class="status-text">connecting…</span>
<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" title="Sign out">Sign out</button>
</div>
</header>
<main class="workspace">
<aside class="sidebar">
<section class="upload-block">
<h2 class="section-heading">01 · Ingest</h2>
<label class="dropzone" id="dropzone">
<input type="file" id="fileInput" accept=".pst,.ost" hidden />
<div class="dropzone-inner">
<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
<path d="M12 3v12m0 0l-4-4m4 4l4-4M4 17v2a2 2 0 002 2h12a2 2 0 002-2v-2" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
<span class="dropzone-title">Drop <em>.pst</em> / <em>.ost</em></span>
<span class="dropzone-hint">or click to browse</span>
</div>
</label>
<div id="uploadProgress" class="upload-progress hidden">
<div class="progress-label"><span id="progressName"></span> <span id="progressPct">0%</span></div>
<div class="progress-track"><div id="progressBar" class="progress-bar"></div></div>
</div>
</section>
<section class="archive-block">
<div class="archive-head">
<h2 class="section-heading">02 · Archives</h2>
<div class="archive-actions">
<button id="newFolderBtn" class="icon-btn" title="New folder">
<svg viewBox="0 0 24 24" width="14" height="14"><path d="M3 7a2 2 0 012-2h4l2 2h8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" fill="none" stroke="currentColor" stroke-width="1.5"/><line x1="12" y1="11" x2="12" y2="17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><line x1="9" y1="14" x2="15" y2="14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
</button>
<button id="refreshBtn" class="icon-btn" title="Refresh list">
<svg viewBox="0 0 24 24" width="14" height="14"><path d="M4 4v6h6M20 20v-6h-6M20 10a8 8 0 00-14.9-3M4 14a8 8 0 0014.9 3" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
</button>
</div>
</div>
<ul id="pstList" class="pst-list">
<li class="empty-state">No archives yet.</li>
</ul>
</section>
</aside>
<section class="reader">
<div class="reader-head">
<div class="reader-title">
<h2 id="readerTitle">Select an archive</h2>
<p id="readerMeta" class="reader-meta">Upload a PST file or select one from the left to begin.</p>
</div>
<div class="search-wrap">
<input id="searchInput" type="search" placeholder="Search subject, sender, body…" disabled />
<select id="folderFilter" disabled>
<option value="">All folders</option>
</select>
</div>
</div>
<div class="reader-body">
<div id="messageList" class="message-list">
<div class="reader-placeholder">
<div class="placeholder-ornament"></div>
<p>The archive awaits.</p>
</div>
</div>
<article id="messageView" class="message-view hidden">
<button id="closeMessageBtn" class="close-btn" title="Close">×</button>
<header class="message-header">
<div class="message-subject" id="mvSubject"></div>
<dl class="message-meta">
<dt>From</dt><dd id="mvSender"></dd>
<dt>To</dt><dd id="mvRecipients"></dd>
<dt>Date</dt><dd id="mvDate"></dd>
<dt>Folder</dt><dd id="mvFolder"></dd>
<dt id="mvAttLabel" class="hidden">Attachments</dt>
<dd id="mvAttachments" class="hidden"></dd>
</dl>
</header>
<div class="message-body" id="mvBody"></div>
</article>
</div>
<div id="paginator" class="paginator hidden">
<button id="prevBtn">← Previous</button>
<span id="pageInfo"></span>
<button id="nextBtn">Next →</button>
</div>
</section>
</main>
</div>
<div id="toast" class="toast hidden"></div>
<!-- NEW FOLDER MODAL -->
<div id="folderModal" class="modal hidden">
<div class="modal-card">
<button class="modal-close" data-close>×</button>
<h2>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">Create folder</button>
<div id="folderError" class="auth-error hidden"></div>
</form>
</div>
</div>
<!-- MOVE PST MODAL -->
<div id="moveModal" class="modal hidden">
<div class="modal-card">
<button class="modal-close" data-close>×</button>
<h2>Move archive</h2>
<p class="modal-subtitle" id="moveSubject"></p>
<form id="moveForm" class="auth-form" novalidate>
<label class="auth-label">
<span>Folder</span>
<select name="folder_id" id="moveFolderSelect">
<option value="">Uncategorized</option>
</select>
</label>
<button type="submit" class="auth-btn">Move</button>
<div id="moveError" class="auth-error hidden"></div>
</form>
</div>
</div>
<script src="/app.js"></script>
</body>
</html>