feat: native save + auto-backup + folder customization + attachments + UX bundle
- File: native Save As dialog via Bridge.saveFile (replaces WebView2
browser download popup) for encrypted JSON + CSV exports.
- Auto-backup: silent periodic encrypted JSON to a chosen folder,
user-set interval + retention, separate DPAPI-stored password, runs
5s after unlock if due. New file/* bridge cmds (folder/pick,
file/write, file/listMatch, file/delete).
- Folders: per-folder color + icon (8-swatch palette, 8 icon presets),
drag-reorder via HTML5 DnD with insert-line indicators, edit pencil
on hover. New POST /folders/reorder + PUT /folders/{name}. Folder
chip on cards inherits custom icon + color.
- Recently used: vault_entries.accessed_at + POST /entries/{id}/touch
(debounced 2s), sidebar Tools entry showing top-10 by accessed_at.
- Encrypted attachments: per-entry file storage (5MB cap), AES-GCM
with vault key, native Save As download, paperclip upload in
slideover. New entry_attachments table + PM.Handler.Attachments.
- Password expiry: vault_entries.password_changed_at (conditional bump
via SQL CASE only when ciphertext differs), passwordExpiryDays
setting, "Aged" badge on cards + matching Filters chip.
- Recovery: Print button on generated code modal (A4 printable sheet
via @media print, code in 32px monospace + instructions).
- Audit log viewer (sidebar Tools, GET /audit with pagination cursor).
- Plaintext CSV export + Filters dropdown with 9 predicates.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+86
-3
@@ -44,6 +44,9 @@
|
||||
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></symbol>
|
||||
<symbol id="i-key" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 2-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-2 2 2 2-3 3-2-2"/></symbol>
|
||||
<symbol id="i-user" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></symbol>
|
||||
<symbol id="i-printer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></symbol>
|
||||
<symbol id="i-paperclip" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05 12.25 20.24a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></symbol>
|
||||
<symbol id="i-empty-vault" viewBox="0 0 120 120" fill="none">
|
||||
<circle cx="60" cy="60" r="50" fill="var(--accent-soft)"/>
|
||||
<rect x="40" y="50" width="40" height="35" rx="4" fill="var(--bg-elev)" stroke="var(--accent)" stroke-width="2"/>
|
||||
@@ -220,10 +223,18 @@
|
||||
<svg><use href="#i-key"/></svg>
|
||||
<span>TOTP generator</span>
|
||||
</button>
|
||||
<button class="nav-item" id="sidebarRecentBtn">
|
||||
<svg><use href="#i-rotate-ccw"/></svg>
|
||||
<span>Recently used</span>
|
||||
</button>
|
||||
<button class="nav-item" id="sidebarHealthBtn">
|
||||
<svg><use href="#i-alert"/></svg>
|
||||
<span>Vault health</span>
|
||||
</button>
|
||||
<button class="nav-item" id="sidebarAuditBtn">
|
||||
<svg><use href="#i-list"/></svg>
|
||||
<span>Audit log</span>
|
||||
</button>
|
||||
<button class="nav-item" id="sidebarImportBtn">
|
||||
<svg><use href="#i-log-in"/></svg>
|
||||
<span>Import vault</span>
|
||||
@@ -268,6 +279,13 @@
|
||||
<svg><use href="#i-table"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="filters-wrap">
|
||||
<button class="icon-btn" id="filtersBtn" title="Filters">
|
||||
<svg><use href="#i-list"/></svg>
|
||||
<span class="filters-count" id="filtersCount" style="display:none">0</span>
|
||||
</button>
|
||||
<div class="filters-menu is-hidden" id="filtersMenu"></div>
|
||||
</div>
|
||||
<button class="icon-btn" id="cheatsheetBtn" title="Keyboard shortcuts (?)">
|
||||
<span style="font-weight:700;font-size:14px">?</span>
|
||||
</button>
|
||||
@@ -320,6 +338,9 @@
|
||||
<span class="content-meta" id="contentMeta">0 items</span>
|
||||
</div>
|
||||
|
||||
<!-- Active filter chips (populated by renderFilterChips) -->
|
||||
<div id="filterChips" class="filter-chips is-hidden"></div>
|
||||
|
||||
<!-- Entry grid -->
|
||||
<div id="entryGrid" class="entry-grid"></div>
|
||||
|
||||
@@ -456,6 +477,19 @@
|
||||
<option value="90">90 days</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<span>
|
||||
Flag aged passwords
|
||||
<small class="setting-hint">Show an "Aged" badge on entries whose password hasn't changed in this many days.</small>
|
||||
</span>
|
||||
<select id="settingPasswordExpiry">
|
||||
<option value="0">Off</option>
|
||||
<option value="90">90 days</option>
|
||||
<option value="180">180 days</option>
|
||||
<option value="365">1 year</option>
|
||||
<option value="730">2 years</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<span>
|
||||
Check passwords against breach database (HIBP)
|
||||
@@ -545,9 +579,58 @@
|
||||
choose a password independent of your master password —
|
||||
save it carefully, you need it to restore.
|
||||
</p>
|
||||
<button class="btn btn-ghost btn-sm" id="exportBtn">
|
||||
<svg><use href="#i-log-out"/></svg> Export vault
|
||||
</button>
|
||||
<div style="display:flex;gap:6px;flex-wrap:wrap">
|
||||
<button class="btn btn-ghost btn-sm" id="exportBtn">
|
||||
<svg><use href="#i-log-out"/></svg> Encrypted JSON
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm is-danger" id="exportCsvBtn"
|
||||
title="Plaintext! Use only for migration">
|
||||
<svg><use href="#i-alert"/></svg> Plaintext CSV
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slideover-field" id="autoBackupField" style="display:none">
|
||||
<div class="slideover-field-label">Auto-backup</div>
|
||||
<p style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">
|
||||
Encrypted JSON dropped silently into a folder of your
|
||||
choice, on a schedule. Old backups beyond the retention
|
||||
count are pruned automatically.
|
||||
</p>
|
||||
<label style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
||||
<input type="checkbox" id="settingAutoBackupEnabled">
|
||||
<span>Enable auto-backup</span>
|
||||
</label>
|
||||
<div id="autoBackupConfig" style="display:none;border-left:2px solid var(--border);padding-left:10px;margin-left:4px">
|
||||
<div style="margin-bottom:8px">
|
||||
<div style="font-size:11px;color:var(--text-faint);margin-bottom:4px">Folder</div>
|
||||
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
||||
<code id="autoBackupDir" style="font-size:11px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--bg-2);padding:4px 6px;border-radius:4px">(not set)</code>
|
||||
<button class="btn btn-ghost btn-sm" id="autoBackupPickDirBtn">Choose…</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:8px">
|
||||
<label style="display:flex;flex-direction:column;gap:2px">
|
||||
<span style="font-size:11px;color:var(--text-faint)">Every (days)</span>
|
||||
<input type="number" id="settingAutoBackupInterval" min="1" max="365" value="7" style="width:80px">
|
||||
</label>
|
||||
<label style="display:flex;flex-direction:column;gap:2px">
|
||||
<span style="font-size:11px;color:var(--text-faint)">Keep last N</span>
|
||||
<input type="number" id="settingAutoBackupKeep" min="1" max="100" value="10" style="width:80px">
|
||||
</label>
|
||||
</div>
|
||||
<p style="font-size:11px;color:var(--text-faint);margin:0 0 8px;line-height:1.4">
|
||||
Backups are encrypted with a password derived from your
|
||||
master password (so restore needs the master at the
|
||||
time of backup). Filename pattern: <code>vault-autobackup-YYYYMMDD-HHmmss.json</code>.
|
||||
</p>
|
||||
<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center">
|
||||
<button class="btn btn-ghost btn-sm" id="autoBackupNowBtn">
|
||||
<svg><use href="#i-log-out"/></svg> Backup now
|
||||
</button>
|
||||
<span id="autoBackupLast" style="font-size:11px;color:var(--text-faint)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slideover-field">
|
||||
|
||||
Reference in New Issue
Block a user