--- phase: 05-package-export plan: 02 subsystem: ui tags: [fastapi, pillow, htmx, alpine.js, icon-upload, png-validation, printer-detail] # Dependency graph requires: - phase: 05-01 provides: NinjaRMM ZIP and .intunewin package export endpoints - phase: 04-02 provides: script generation endpoints (install/uninstall/detect) - phase: 03-02 provides: printer detail page foundation in pages.py provides: - Icon upload endpoint with PNG format/dimension/size validation - SHA256-addressed icon storage under DATA_DIR/icons/ - Icon ORM record tracking (one per printer, replace-on-upload) - Printer detail page with Intune Commands section (install/uninstall command strings with copy buttons) - Printer detail page with Export section (NinjaRMM ZIP and .intunewin download links) - Printer detail page with Icon Upload form (HTMX submission) affects: [deployment, ui, export] # Tech tracking tech-stack: added: [Pillow>=10.0 (PNG dimension/format validation)] patterns: - "Read cfg.DATA_DIR at call time (not import time) for monkeypatch compatibility" - "SHA256-addressed icon storage — dedup automatic, filename = sha256 hash" - "Icon replace pattern: delete existing record then create new (unique FK)" - "Alpine.js copy-to-clipboard with copied state and 2-second timeout" - "HTMX icon upload form with #icon-status swap target" key-files: created: - imptune/api/icons.py - tests/test_icon_upload.py modified: - imptune/config.py - imptune/main.py - imptune/api/pages.py - imptune/templates/printer_detail.html - tests/conftest.py - tests/test_packages.py - requirements.txt key-decisions: - "Pillow used for PNG validation — provides format, dimension, and byte-read in one library" - "Icons stored SHA256-addressed (not by printer ID) — enables dedup if same PNG used for multiple printers" - "Read cfg.DATA_DIR dynamically in icons.py endpoint, not at import time — consistent with monkeypatch pattern established in Phase 02" - "Icon replace via delete-then-create rather than get_or_create — unique FK makes upsert awkward, simpler to delete first" - "Export and command sections conditionally shown only when has_driver is true — avoids confusing 422 before driver is assigned" patterns-established: - "Icon upload: read MAX+1 bytes, check len > MAX for oversized detection" - "Printer detail page sections gated on has_driver boolean from view context" requirements-completed: [PKG-04, PKG-05] # Metrics duration: 15min completed: 2026-04-10 --- # Phase 05 Plan 02: Icon Upload and Printer Detail Export UI Summary **PNG icon upload endpoint with 750KB/256x256/format validation, SHA256 storage, and printer detail page with Intune command preview and download links** ## Performance - **Duration:** ~15 min - **Started:** 2026-04-10T12:00:00Z - **Completed:** 2026-04-10T12:15:00Z - **Tasks:** 2 - **Files modified:** 9 ## Accomplishments - Icon upload endpoint (POST /printers/{id}/icon) with full validation: PNG format, 256x256 dimensions, 750KB max, 404 on missing printer - Re-upload replaces previous Icon ORM record (unique FK constraint handled via delete-then-create) - Printer detail page rewritten with three new sections: Intune Commands (install/uninstall with Alpine.js copy-to-clipboard), Export (NinjaRMM ZIP and .intunewin download links), Icon (HTMX upload form) - Full test suite green: 94 tests pass ## Task Commits Each task was committed atomically: 1. **Task 1 RED: Icon upload tests** - `d8ce223` (test) 2. **Task 1 GREEN: Icon upload implementation** - `f9e13ba` (feat) 3. **Task 2: Printer detail page with export UI** - `f96ea6f` (feat) ## Files Created/Modified - `imptune/api/icons.py` - Icon upload endpoint with PNG format/dimension/size validation - `imptune/config.py` - Added ICONS_DIR constant - `imptune/main.py` - Registered icons.router, added ICONS_DIR makedirs in lifespan - `imptune/api/pages.py` - Updated printer_detail() with install_cmd, uninstall_cmd, has_driver, has_icon context - `imptune/templates/printer_detail.html` - Added Intune Commands, Export, Icon Upload sections; removed placeholder button - `tests/test_icon_upload.py` - 6 TDD integration tests for icon upload validation - `tests/test_packages.py` - Added TestCommandPreview class (4 tests) - `tests/conftest.py` - Patched cfg.ICONS_DIR in tmp_data_dir fixture - `requirements.txt` - Added Pillow>=10.0 ## Decisions Made - Used Pillow for PNG validation — single library handles format detection, dimension check, and byte reading in one pass - Icons stored SHA256-addressed under DATA_DIR/icons/ — consistent with DRIVERS_DIR content-addressing pattern from Phase 02 - cfg.DATA_DIR read at call time in icons.py endpoint — consistent with monkeypatch pattern established in Phase 02 for DRIVERS_DIR - Export and command sections conditionally shown only when has_driver is true — prevents confusing broken download links before driver is assigned ## Deviations from Plan None - plan executed exactly as written. ## Issues Encountered - Pillow was not yet installed in the environment (requirements.txt addition needed `python -m pip install` before tests could run). Resolved automatically. ## User Setup Required None - no external service configuration required. ## Next Phase Readiness - Icon upload and command preview complete — export UI is fully functional - Phase 05 is the final phase; all requirements PKG-01 through PKG-05 are now implemented - Remaining validation: byte-level .intunewin format compliance against real Intune tenant (noted as MEDIUM confidence concern) --- *Phase: 05-package-export* *Completed: 2026-04-10*