257 lines
13 KiB
HTML
257 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 · 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>
|