ab82b5e9a9
Password management moves out of the CLI entirely. The credential is now a salted scrypt hash in the database (so it survives rebuilds, living in the /data volume) rather than an environment variable; PARTS_PASSWORD is demoted to a bootstrap value that stops working the moment a password is set in the UI. Every token carries a session epoch, so changing the password — or "sign out other devices" — invalidates outstanding cookies while keeping the browser that made the change signed in. A banner nags until the handed-over password is replaced. app/admin.py remains for the one case the UI cannot cover, a forgotten password. Audit findings: - Taxonomy update and delete scanned affected parts before taking the write lock, so a concurrent rename could leave the search index matching a name the UI no longer showed. All four routes now lock first; removing the lock again makes the new test fail exactly that way. - History of a missing part returned 200 with an empty list; now 404. - Infinity and NaN passed ge=0 and failed at the database. They are rejected as 422 now, and the validation error handler no longer chokes trying to echo a non-finite value back. Checks go from 134 to 181. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
76 lines
2.8 KiB
HTML
76 lines
2.8 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||
<meta name="color-scheme" content="dark">
|
||
<title>Parts Inventory</title>
|
||
<link rel="stylesheet" href="/static/app.css">
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><text y='13' font-size='13'>🧰</text></svg>">
|
||
</head>
|
||
<body>
|
||
|
||
<section id="login" class="hidden">
|
||
<form id="login-form">
|
||
<h1>Parts Inventory</h1>
|
||
<p>Enter the password to continue.</p>
|
||
<input type="password" id="password" autocomplete="current-password" placeholder="Password" required>
|
||
<p id="login-error" class="hidden" style="color:var(--bad);margin:10px 0 0"></p>
|
||
<button class="primary" type="submit" style="width:100%;margin-top:14px">Unlock</button>
|
||
</form>
|
||
</section>
|
||
|
||
<section id="app" class="hidden">
|
||
<header>
|
||
<span class="brand">🧰 Parts</span>
|
||
<input class="search" id="search" type="search" placeholder="Search parts, values, MPN, tags…" autocomplete="off">
|
||
<span class="stat" id="stats"></span>
|
||
<button class="ghost small mobile-only" id="filters-btn">Filters</button>
|
||
<button class="primary" id="add-btn">+ Add</button>
|
||
<button class="ghost small" id="manage-btn" title="Settings">⚙</button>
|
||
<button class="ghost small hidden" id="logout-btn" title="Log out">⏻</button>
|
||
</header>
|
||
|
||
<div id="bootstrap-banner" class="banner hidden">
|
||
<span>You're still using the password that was handed to you. Set your own.</span>
|
||
<button class="small primary" id="banner-change">Change password</button>
|
||
</div>
|
||
|
||
<div class="layout">
|
||
<aside id="sidebar">
|
||
<h3>Filters</h3>
|
||
<div class="filter" id="filter-all">All parts <span class="count" id="count-all"></span></div>
|
||
<div class="filter" id="filter-low">Low stock <span class="count" id="count-low"></span></div>
|
||
<h3>Categories</h3>
|
||
<div id="category-list"></div>
|
||
<h3>Locations</h3>
|
||
<div id="location-list"></div>
|
||
<h3>Tags</h3>
|
||
<div id="tag-list"></div>
|
||
</aside>
|
||
|
||
<main>
|
||
<div class="toolbar">
|
||
<span class="grow" id="result-summary"></span>
|
||
<select id="sort">
|
||
<option value="relevance">Best match</option>
|
||
<option value="name">Name A–Z</option>
|
||
<option value="quantity">Quantity, lowest</option>
|
||
<option value="updated">Recently updated</option>
|
||
<option value="created">Recently added</option>
|
||
<option value="location">Location</option>
|
||
</select>
|
||
</div>
|
||
<div id="results"></div>
|
||
<div style="text-align:center;margin:14px 0">
|
||
<button id="more-btn" class="hidden">Load more</button>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</section>
|
||
|
||
<div id="modal-root"></div>
|
||
<script src="/static/app.js"></script>
|
||
</body>
|
||
</html>
|