chore: complete v1.0 milestone
Archive v1.0 MVP: 7 phases, 13 plans, 27/27 requirements. - Archive roadmap to milestones/v1.0-ROADMAP.md - Archive requirements to milestones/v1.0-REQUIREMENTS.md - Move milestone audit into milestones/ - Create MILESTONES.md with v1.0 entry - Evolve PROJECT.md: move shipped requirements to Validated, update Context with stack/LOC, log Key Decisions with outcomes - Collapse ROADMAP.md to one-line milestone summary - Update STATE.md to shipped status - Back-fill stale requirements-completed frontmatter on 02-01, 04-01, 05-01, 06-01 SUMMARY.md files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,6 +13,7 @@ import imptune.config as cfg
|
||||
from imptune.db.models import Icon, Printer
|
||||
from imptune.generators.intunewin_builder import build_intunewin
|
||||
from imptune.generators.script_generator import render_detect, render_install, render_uninstall
|
||||
from imptune.storage.driver_store import DriverStore
|
||||
|
||||
router = APIRouter(prefix="/printers")
|
||||
|
||||
@@ -43,8 +44,7 @@ def _get_printer_and_driver(printer_id: int):
|
||||
|
||||
|
||||
def _get_driver_zip_path(driver) -> str:
|
||||
"""Return the on-disk path of the driver ZIP file."""
|
||||
return os.path.join(cfg.DRIVERS_DIR, f"{driver.sha256}.zip")
|
||||
return str(DriverStore(cfg.DRIVERS_DIR).get_path(driver.sha256))
|
||||
|
||||
|
||||
@router.get("/{printer_id}/packages/ninja")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10
-6
@@ -1,5 +1,9 @@
|
||||
"""Peewee ORM models — full schema for phases 1-5."""
|
||||
from datetime import datetime
|
||||
from datetime import UTC, datetime
|
||||
|
||||
|
||||
def _utcnow():
|
||||
return datetime.now(UTC).replace(tzinfo=None)
|
||||
|
||||
from peewee import (
|
||||
BooleanField,
|
||||
@@ -24,7 +28,7 @@ class Client(BaseModel):
|
||||
"""Represents a deployment target (AD client / OU)."""
|
||||
|
||||
name = CharField(unique=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
created_at = DateTimeField(default=_utcnow)
|
||||
|
||||
class Meta:
|
||||
table_name = "client"
|
||||
@@ -36,7 +40,7 @@ class Driver(BaseModel):
|
||||
sha256 = CharField(unique=True, index=True)
|
||||
original_filename = CharField()
|
||||
size_bytes = IntegerField()
|
||||
uploaded_at = DateTimeField(default=datetime.utcnow)
|
||||
uploaded_at = DateTimeField(default=_utcnow)
|
||||
driver_desc = CharField(null=True)
|
||||
inf_filename = CharField(null=True)
|
||||
architecture = CharField(null=True)
|
||||
@@ -58,8 +62,8 @@ class Printer(BaseModel):
|
||||
color_mode = BooleanField(default=True)
|
||||
paper_size = CharField(default="A4")
|
||||
collate = BooleanField(default=True)
|
||||
created_at = DateTimeField(default=datetime.utcnow)
|
||||
updated_at = DateTimeField(default=datetime.utcnow)
|
||||
created_at = DateTimeField(default=_utcnow)
|
||||
updated_at = DateTimeField(default=_utcnow)
|
||||
|
||||
class Meta:
|
||||
table_name = "printer"
|
||||
@@ -72,7 +76,7 @@ class Icon(BaseModel):
|
||||
sha256 = CharField()
|
||||
original_filename = CharField()
|
||||
size_bytes = IntegerField()
|
||||
uploaded_at = DateTimeField(default=datetime.utcnow)
|
||||
uploaded_at = DateTimeField(default=_utcnow)
|
||||
|
||||
class Meta:
|
||||
table_name = "icon"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
+5
File diff suppressed because one or more lines are too long
+49
-6
@@ -3,16 +3,42 @@
|
||||
.layout {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
.layout > * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Sidebar — override Pico's default nav styling (which is horizontal flex) */
|
||||
nav.sidebar {
|
||||
width: 220px;
|
||||
flex-shrink: 0;
|
||||
min-width: 220px;
|
||||
max-width: 220px;
|
||||
flex: 0 0 220px;
|
||||
border-right: 1px solid var(--pico-muted-border-color, #e0e0e0);
|
||||
padding: 1rem 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
nav.sidebar ul,
|
||||
nav.sidebar li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
nav.sidebar ul {
|
||||
display: block;
|
||||
}
|
||||
|
||||
nav.sidebar li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
@@ -22,22 +48,37 @@
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Override Pico's horizontal nav ul default */
|
||||
.sidebar nav,
|
||||
.sidebar-nav {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-nav li {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar-nav a {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 0.6rem 1rem;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.sidebar-nav a:hover {
|
||||
@@ -47,13 +88,15 @@
|
||||
.sidebar-nav a.active {
|
||||
font-weight: 600;
|
||||
background-color: var(--pico-primary-background, rgba(0,0,0,0.08));
|
||||
border-left: 3px solid var(--pico-primary, #1a73e8);
|
||||
border-left-color: var(--pico-primary, #1a73e8);
|
||||
}
|
||||
|
||||
/* Main content */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 1.5rem;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
padding: 1.5rem 2rem;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+4
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,8 +6,9 @@ from pathlib import Path
|
||||
class DriverStore:
|
||||
"""SHA256 content-addressed file storage for printer driver packages.
|
||||
|
||||
Files are stored as DRIVERS_DIR/{sha256} so identical uploads are
|
||||
deduplicated automatically.
|
||||
Files are stored as DRIVERS_DIR/{sha256}.zip so identical uploads are
|
||||
deduplicated automatically. The .zip suffix lets operators identify
|
||||
stored driver packages by type when browsing the volume directly.
|
||||
"""
|
||||
|
||||
def __init__(self, base_dir: str) -> None:
|
||||
@@ -15,20 +16,14 @@ class DriverStore:
|
||||
self._base.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def save(self, data: bytes) -> str:
|
||||
"""Persist *data* and return its SHA256 hex digest.
|
||||
|
||||
If the file already exists the write is skipped (deduplication).
|
||||
"""
|
||||
digest = hashlib.sha256(data).hexdigest()
|
||||
dest = self._base / digest
|
||||
dest = self.get_path(digest)
|
||||
if not dest.exists():
|
||||
dest.write_bytes(data)
|
||||
return digest
|
||||
|
||||
def get_path(self, sha256: str) -> Path:
|
||||
"""Return the filesystem path for a given SHA256 digest."""
|
||||
return self._base / sha256
|
||||
return self._base / f"{sha256}.zip"
|
||||
|
||||
def exists(self, sha256: str) -> bool:
|
||||
"""Return True if the file for *sha256* exists on disk."""
|
||||
return (self._base / sha256).exists()
|
||||
return self.get_path(sha256).exists()
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<li><a href="/packages" {% if request.url.path == "/packages" %}class="active"{% endif %}>Packages</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<main class="main-content container">
|
||||
<main class="main-content">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
const text = document.getElementById('uninstall-cmd').innerText;
|
||||
navigator.clipboard.writeText(text).then(() => { copiedUninstall = true; setTimeout(() => copiedUninstall = false, 2000) })
|
||||
.catch(() => { /* fallback: text is visible for manual copy */ })
|
||||
" x-text="copiedUninstall ? 'Copied!' : 'Uninstall copy'" class="secondary outline"></button>
|
||||
" x-text="copiedUninstall ? 'Copied!' : 'Copy'" class="secondary outline"></button>
|
||||
</div>
|
||||
|
||||
<h2>Export</h2>
|
||||
|
||||
Reference in New Issue
Block a user