feat(02-02): add driver upload endpoint with INF parsing and dedup
- POST /drivers/upload: validates ZIP, parses INF, persists via DriverStore + Peewee - SHA256 dedup: get_or_create prevents duplicate Driver records - GET /drivers route added to pages.py with driver_data context - drivers.router registered in main.py - drivers.html template with HTMX upload form - partials/driver_list.html HTMX target with select dropdown and unused-file notice - Fixed conftest client fixture to use context manager (triggers lifespan/init_db) - [Rule 3] conftest: TestClient context manager required for lifespan trigger - [Rule 1] drivers.py: dynamic DRIVERS_DIR read so monkeypatch works in tests
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h1>Drivers</h1>
|
||||
|
||||
<section>
|
||||
<h2>Upload Driver Package</h2>
|
||||
<form
|
||||
hx-post="/drivers/upload"
|
||||
hx-encoding="multipart/form-data"
|
||||
hx-target="#driver-list"
|
||||
hx-swap="outerHTML"
|
||||
hx-indicator="#upload-spinner"
|
||||
>
|
||||
<label for="driver-file">Driver Package (ZIP containing .inf + driver files)</label>
|
||||
<input type="file" id="driver-file" name="file" accept=".zip" required>
|
||||
<button type="submit">Upload</button>
|
||||
<span id="upload-spinner" class="htmx-indicator" aria-busy="true">Uploading...</span>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Driver Library</h2>
|
||||
{% include "partials/driver_list.html" %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,50 @@
|
||||
<div id="driver-list">
|
||||
{% if parsed is defined and parsed.unused_files %}
|
||||
<p class="notice">
|
||||
{{ parsed.unused_files | length }} file(s) may be unused (not referenced by the INF):
|
||||
<details>
|
||||
<summary>Show unused files</summary>
|
||||
<ul>
|
||||
{% for f in parsed.unused_files %}
|
||||
<li>{{ f }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</details>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if driver_data %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Filename</th>
|
||||
<th>Driver Name(s)</th>
|
||||
<th>Architecture</th>
|
||||
<th>Uploaded</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in driver_data %}
|
||||
<tr>
|
||||
<td>{{ item.driver.original_filename }}</td>
|
||||
<td>
|
||||
{% if item.names %}
|
||||
<select aria-label="Driver names">
|
||||
{% for name in item.names %}
|
||||
<option>{{ name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<em>Unknown</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ item.driver.architecture or "Unknown" }}</td>
|
||||
<td>{{ item.driver.uploaded_at }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No drivers uploaded yet.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
Reference in New Issue
Block a user