Merge pull request 'main' (#36) from main into production

Reviewed-on: #36
This commit was merged in pull request #36.
This commit is contained in:
2026-02-08 21:25:02 +00:00
7 changed files with 367 additions and 178 deletions

View File

@@ -13,7 +13,7 @@ Kindred Silo is an R&D-oriented item database with:
- **Role-based access control** (admin > editor > viewer) with API tokens and sessions - **Role-based access control** (admin > editor > viewer) with API tokens and sessions
- **ODS import/export** for items, BOMs, and project sheets - **ODS import/export** for items, BOMs, and project sheets
- **Audit/completeness scoring** with weighted per-category property validation - **Audit/completeness scoring** with weighted per-category property validation
- **Web UI** with htmx-based item browser, project management, and schema editing - **Web UI** — React SPA (Vite + TypeScript, Catppuccin Mocha theme) for item browsing, project management, schema editing, and audit
- **CAD integration** via REST API ([silo-mod](https://git.kindred-systems.com/kindred/silo-mod), [silo-calc](https://git.kindred-systems.com/kindred/silo-calc)) - **CAD integration** via REST API ([silo-mod](https://git.kindred-systems.com/kindred/silo-mod), [silo-calc](https://git.kindred-systems.com/kindred/silo-calc))
- **Physical inventory** tracking with hierarchical locations (schema ready) - **Physical inventory** tracking with hierarchical locations (schema ready)
@@ -22,24 +22,33 @@ Kindred Silo is an R&D-oriented item database with:
``` ```
silo/ silo/
├── cmd/ ├── cmd/
│ ├── silo/ # CLI tool │ ├── silo/ # CLI tool
│ └── silod/ # API server │ └── silod/ # API server
├── internal/ ├── internal/
│ ├── api/ # HTTP handlers, routes, templates (76 endpoints) │ ├── api/ # HTTP handlers and routes (75 endpoints)
│ ├── auth/ # Authentication (local, LDAP, OIDC) │ ├── auth/ # Authentication (local, LDAP, OIDC)
│ ├── config/ # Configuration loading │ ├── config/ # Configuration loading
│ ├── db/ # PostgreSQL repositories │ ├── db/ # PostgreSQL repositories
│ ├── migration/ # Property migration utilities │ ├── migration/ # Property migration utilities
│ ├── odoo/ # Odoo ERP integration │ ├── odoo/ # Odoo ERP integration
│ ├── ods/ # ODS spreadsheet library │ ├── ods/ # ODS spreadsheet library
│ ├── partnum/ # Part number generation │ ├── partnum/ # Part number generation
│ ├── schema/ # YAML schema parsing │ ├── schema/ # YAML schema parsing
── storage/ # MinIO file storage ── storage/ # MinIO file storage
├── migrations/ # Database migrations (10 files) │ └── testutil/ # Test helpers
├── schemas/ # Part numbering schemas (YAML) ├── web/ # React SPA (Vite + TypeScript)
├── deployments/ # Docker Compose and systemd configs │ └── src/
├── scripts/ # Deployment and setup scripts │ ├── api/ # API client and type definitions
└── docs/ # Documentation │ ├── components/ # Reusable UI components
│ ├── context/ # Auth context provider
│ ├── hooks/ # Custom React hooks
│ ├── pages/ # Page components (Items, Projects, Schemas, Settings, Audit, Login)
│ └── styles/ # Catppuccin Mocha theme and global styles
├── migrations/ # Database migrations (11 files)
├── schemas/ # Part numbering schemas (YAML)
├── deployments/ # Docker Compose and systemd configs
├── scripts/ # Deployment and setup scripts
└── docs/ # Documentation
``` ```
## Quick Start ## Quick Start
@@ -95,12 +104,16 @@ The server provides the REST API and ODS endpoints consumed by these clients.
| Document | Description | | Document | Description |
|----------|-------------| |----------|-------------|
| [docs/AUTH.md](docs/AUTH.md) | Authentication system design |
| [docs/AUTH_USER_GUIDE.md](docs/AUTH_USER_GUIDE.md) | User guide for login, tokens, and roles |
| [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) | Production deployment guide |
| [docs/SPECIFICATION.md](docs/SPECIFICATION.md) | Full design specification and API reference | | [docs/SPECIFICATION.md](docs/SPECIFICATION.md) | Full design specification and API reference |
| [docs/STATUS.md](docs/STATUS.md) | Implementation status | | [docs/STATUS.md](docs/STATUS.md) | Implementation status |
| [ROADMAP.md](ROADMAP.md) | Feature roadmap and gap analysis | | [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) | Production deployment guide |
| [docs/CONFIGURATION.md](docs/CONFIGURATION.md) | Configuration reference (all `config.yaml` options) |
| [docs/AUTH.md](docs/AUTH.md) | Authentication system design |
| [docs/AUTH_USER_GUIDE.md](docs/AUTH_USER_GUIDE.md) | User guide for login, tokens, and roles |
| [docs/GAP_ANALYSIS.md](docs/GAP_ANALYSIS.md) | Gap analysis and revision control roadmap |
| [docs/COMPONENT_AUDIT.md](docs/COMPONENT_AUDIT.md) | Component audit tool design |
| [ROADMAP.md](ROADMAP.md) | Feature roadmap and SOLIDWORKS PDM comparison |
| [frontend-spec.md](frontend-spec.md) | React SPA frontend specification |
## License ## License

View File

@@ -39,7 +39,7 @@ This document compares Silo's current capabilities against SOLIDWORKS PDM—the
### Implemented Features (MVP Complete) ### Implemented Features (MVP Complete)
#### Core Database System #### Core Database System
- PostgreSQL schema with 10 migrations - PostgreSQL schema with 11 migrations
- UUID-based identifiers throughout - UUID-based identifiers throughout
- Soft delete support via `archived_at` timestamps - Soft delete support via `archived_at` timestamps
- Atomic sequence generation for part numbers - Atomic sequence generation for part numbers
@@ -92,14 +92,14 @@ This document compares Silo's current capabilities against SOLIDWORKS PDM—the
- Template generation for import formatting - Template generation for import formatting
#### API & Web Interface #### API & Web Interface
- REST API with 74 endpoints - REST API with 75 endpoints
- Authentication: local (bcrypt), LDAP/FreeIPA, OIDC/Keycloak - Authentication: local (bcrypt), LDAP/FreeIPA, OIDC/Keycloak
- Role-based access control (admin > editor > viewer) - Role-based access control (admin > editor > viewer)
- API token management (SHA-256 hashed) - API token management (SHA-256 hashed)
- Session management (PostgreSQL-backed, 24h lifetime) - Session management (PostgreSQL-backed, 24h lifetime)
- CSRF protection (nosurf on web forms) - CSRF protection (nosurf on web forms)
- Middleware: logging, CORS, recovery, request ID - Middleware: logging, CORS, recovery, request ID
- Web UI for items, projects, schemas, audit (htmx) - Web UI — React SPA (Vite + TypeScript, Catppuccin Mocha theme)
- Fuzzy search - Fuzzy search
- Health and readiness probes - Health and readiness probes
@@ -123,7 +123,7 @@ This document compares Silo's current capabilities against SOLIDWORKS PDM—the
| Part number validation | Not started | API accepts but doesn't validate format | | Part number validation | Not started | API accepts but doesn't validate format |
| Location hierarchy CRUD | Schema only | Tables exist, no API endpoints | | Location hierarchy CRUD | Schema only | Tables exist, no API endpoints |
| Inventory tracking | Schema only | Tables exist, no API endpoints | | Inventory tracking | Schema only | Tables exist, no API endpoints |
| Unit tests | Minimal | 1 Go test file (`internal/ods/ods_test.go`) | | Unit tests | Partial | 9 Go test files across api, db, ods, partnum, schema packages |
### Infrastructure Status ### Infrastructure Status
@@ -255,14 +255,14 @@ CAD integration is maintained in separate repositories ([silo-mod](https://git.k
| Feature | SOLIDWORKS PDM | Silo Status | Priority | Complexity | | Feature | SOLIDWORKS PDM | Silo Status | Priority | Complexity |
|---------|---------------|-------------|----------|------------| |---------|---------------|-------------|----------|------------|
| ERP integration | SAP, Dynamics, etc. | Partial (Odoo stubs) | Medium | Complex | | ERP integration | SAP, Dynamics, etc. | Partial (Odoo stubs) | Medium | Complex |
| API access | Full COM/REST API | Full REST API (74 endpoints) | - | - | | API access | Full COM/REST API | Full REST API (75 endpoints) | - | - |
| Dispatch scripts | Automation without coding | None | Medium | Moderate | | Dispatch scripts | Automation without coding | None | Medium | Moderate |
| Task scheduler | Background processing | None | Medium | Moderate | | Task scheduler | Background processing | None | Medium | Moderate |
| Email system | SMTP integration | None | High | Simple | | Email system | SMTP integration | None | High | Simple |
| Web portal | Browser access | Full (htmx + auth) | - | - | | Web portal | Browser access | Full (React SPA + auth) | - | - |
**Gap Analysis:** **Gap Analysis:**
Silo has a comprehensive REST API (74 endpoints) and a full web UI with authentication. Odoo ERP integration has config/sync-log scaffolding but push/pull operations are stubs. Remaining gaps: email notifications, task scheduler, dispatch automation. Silo has a comprehensive REST API (75 endpoints) and a full web UI with authentication. Odoo ERP integration has config/sync-log scaffolding but push/pull operations are stubs. Remaining gaps: email notifications, task scheduler, dispatch automation.
--- ---
@@ -528,9 +528,9 @@ File storage works well. Thumbnail generation and file preview would significant
| **Data** | CSV import/export | Yes | Yes | Yes | - | | **Data** | CSV import/export | Yes | Yes | Yes | - |
| | ODS import/export | No | No | Yes | - | | | ODS import/export | No | No | Yes | - |
| | Project management | Yes | Yes | Yes | - | | | Project management | Yes | Yes | Yes | - |
| **Integration** | API | Limited | Full | Full REST (74) | - | | **Integration** | API | Limited | Full | Full REST (75) | - |
| | ERP connectors | No | Yes | Partial (Odoo stubs) | Phase 6 | | | ERP connectors | No | Yes | Partial (Odoo stubs) | Phase 6 |
| | Web access | No | Yes | Yes (htmx + auth) | - | | | Web access | No | Yes | Yes (React SPA + auth) | - |
| **Files** | Versioning | Yes | Yes | Yes | - | | **Files** | Versioning | Yes | Yes | Yes | - |
| | Preview | Yes | Yes | No | Phase 6 | | | Preview | Yes | Yes | No | Phase 6 |
| | Multi-site | No | Yes | No | Not Planned | | | Multi-site | No | Yes | No | Not Planned |

View File

@@ -1,6 +1,6 @@
# Silo Gap Analysis and Revision Control Roadmap # Silo Gap Analysis and Revision Control Roadmap
**Date:** 2026-02-06 **Date:** 2026-02-08
**Status:** Analysis Complete (Updated) **Status:** Analysis Complete (Updated)
--- ---
@@ -21,9 +21,12 @@ This document analyzes the current state of the Silo project against its specifi
| `docs/SPECIFICATION.md` | Design specification, API reference | Current | | `docs/SPECIFICATION.md` | Design specification, API reference | Current |
| `docs/STATUS.md` | Implementation status summary | Current | | `docs/STATUS.md` | Implementation status summary | Current |
| `docs/DEPLOYMENT.md` | Production deployment guide | Current | | `docs/DEPLOYMENT.md` | Production deployment guide | Current |
| `docs/GAP_ANALYSIS.md` | SOLIDWORKS PDM comparison, roadmap | Current | | `docs/CONFIGURATION.md` | Configuration reference (all config.yaml options) | Current |
| `ROADMAP.md` | Feature roadmap and phases | Current | | `docs/AUTH.md` | Authentication system design | Current |
| `silo-spec.md` | Redirect to `docs/SPECIFICATION.md` | Consolidated | | `docs/AUTH_USER_GUIDE.md` | User guide for login, tokens, and roles | Current |
| `docs/GAP_ANALYSIS.md` | Revision control roadmap | Current |
| `ROADMAP.md` | Feature roadmap and SOLIDWORKS PDM comparison | Current |
| `frontend-spec.md` | React SPA frontend specification | Current |
### 1.2 Documentation Gaps (Priority Order) ### 1.2 Documentation Gaps (Priority Order)
@@ -34,7 +37,7 @@ This document analyzes the current state of the Silo project against its specifi
| **API Reference** | Users cannot integrate programmatically | Medium | Addressed in SPECIFICATION.md Section 11 | | **API Reference** | Users cannot integrate programmatically | Medium | Addressed in SPECIFICATION.md Section 11 |
| **Deployment Guide** | Cannot deploy to production | Medium | Complete (`docs/DEPLOYMENT.md`) | | **Deployment Guide** | Cannot deploy to production | Medium | Complete (`docs/DEPLOYMENT.md`) |
| **Database Schema Guide** | Migration troubleshooting difficult | Low | Open | | **Database Schema Guide** | Migration troubleshooting difficult | Low | Open |
| **Configuration Reference** | config.yaml options undocumented | Low | Open | | **Configuration Reference** | config.yaml options undocumented | Low | Complete (`docs/CONFIGURATION.md`) |
#### Medium Priority #### Medium Priority
@@ -54,10 +57,10 @@ This document analyzes the current state of the Silo project against its specifi
### 1.3 Recommended Actions ### 1.3 Recommended Actions
1. ~~**Consolidate specs**: Remove `silo-spec.md` duplicate~~ Done 1. ~~**Consolidate specs**: Remove `silo-spec.md` duplicate~~ Done (deleted)
2. ~~**Create API reference**: Full REST endpoint documentation~~ Addressed in SPECIFICATION.md 2. ~~**Create API reference**: Full REST endpoint documentation~~ Addressed in SPECIFICATION.md
3. ~~**Create `docs/DEPLOYMENT.md`**: Production deployment guide~~ Done 3. ~~**Create `docs/DEPLOYMENT.md`**: Production deployment guide~~ Done
4. **Create configuration reference**: Document all `config.yaml` options 4. ~~**Create configuration reference**: Document all `config.yaml` options~~ Done (`docs/CONFIGURATION.md`)
5. **Create database schema guide**: Document migrations and troubleshooting 5. **Create database schema guide**: Document migrations and troubleshooting
--- ---
@@ -207,7 +210,7 @@ Audit logging is implemented via migration 009 with the `audit_log` table and co
- `GET /api/audit/completeness` — summary of all items - `GET /api/audit/completeness` — summary of all items
- `GET /api/audit/completeness/{partNumber}` — per-item scoring with weighted fields and tier classification - `GET /api/audit/completeness/{partNumber}` — per-item scoring with weighted fields and tier classification
Code: `internal/api/handlers_audit.go` Code: `internal/api/audit_handlers.go`
--- ---
@@ -304,7 +307,6 @@ Effort: Medium | Priority: Low | Risk: Low
7. **Release Management** - Product milestone tracking 7. **Release Management** - Product milestone tracking
8. **Thumbnail Generation** - Visual preview capability 8. **Thumbnail Generation** - Visual preview capability
9. **Documentation Overhaul** - API reference, deployment guide
### Long-term (Future) ### Long-term (Future)
@@ -351,19 +353,33 @@ These design decisions remain unresolved:
## Appendix A: File Structure ## Appendix A: File Structure
Revision endpoints, status, labels, authentication, and audit logging are implemented. Current structure: Revision endpoints, status, labels, authentication, audit logging, and file attachments are implemented. Current structure:
``` ```
internal/ internal/
api/ api/
handlers_audit.go # Audit/completeness endpoints (implemented) audit_handlers.go # Audit/completeness endpoints
middleware.go # Auth middleware (implemented) auth_handlers.go # Login, tokens, OIDC
bom_handlers.go # Flat BOM, cost roll-up
file_handlers.go # Presigned uploads, item files, thumbnails
handlers.go # Items, schemas, projects, revisions
middleware.go # Auth middleware
odoo_handlers.go # Odoo integration endpoints
routes.go # Route registration (75 endpoints)
search.go # Fuzzy search
auth/ auth/
auth.go # Auth service: local, LDAP, OIDC (implemented) auth.go # Auth service: local, LDAP, OIDC
db/
items.go # Item and revision repository
item_files.go # File attachment repository
relationships.go # BOM repository
projects.go # Project repository
storage/
storage.go # MinIO file storage helpers
migrations/ migrations/
008_odoo_integration.sql # Odoo ERP tables (implemented) 001_initial.sql # Core schema
009_auth.sql # Auth + audit tables (implemented) ...
010_item_extended_fields.sql # Extended item fields (implemented) 011_item_files.sql # Item file attachments (latest)
``` ```
Future features would add: Future features would add:
@@ -371,13 +387,13 @@ Future features would add:
``` ```
internal/ internal/
api/ api/
handlers_lock.go # Locking endpoints lock_handlers.go # Locking endpoints
db/ db/
locks.go # Lock repository locks.go # Lock repository
releases.go # Release repository releases.go # Release repository
migrations/ migrations/
011_item_locks.sql # Locking table 012_item_locks.sql # Locking table
012_releases.sql # Release management 013_releases.sql # Release management
``` ```
--- ---
@@ -407,6 +423,18 @@ GET /api/audit/completeness # All items completeness summary
GET /api/audit/completeness/{partNumber} # Per-item scoring GET /api/audit/completeness/{partNumber} # Per-item scoring
``` ```
**File Attachments (Implemented):**
```
GET /api/auth/config # Auth config (public)
POST /api/uploads/presign # Presigned upload URL
GET /api/items/{pn}/files # List item files
POST /api/items/{pn}/files # Associate file with item
DELETE /api/items/{pn}/files/{fileId} # Delete file association
PUT /api/items/{pn}/thumbnail # Set item thumbnail
GET /api/items/{pn}/bom/flat # Flattened BOM
GET /api/items/{pn}/bom/cost # Assembly cost roll-up
```
**Locking (Not Implemented):** **Locking (Not Implemented):**
``` ```
POST /api/items/{pn}/lock # Acquire lock POST /api/items/{pn}/lock # Acquire lock

View File

@@ -8,7 +8,7 @@
## 1. Overview ## 1. Overview
Silo is an item database with configurable part number generation, designed for R&D-oriented workflows. It provides revision tracking, BOM management, file versioning, and physical inventory location management through a REST API and web UI. CAD integration (FreeCAD workbench, LibreOffice Calc extension) is maintained in separate repositories ([silo-mod](https://git.kindred-systems.com/kindred/silo-mod), [silo-calc](https://git.kindred-systems.com/kindred/silo-calc)). Silo is an item database with configurable part number generation, designed for R&D-oriented workflows. It provides revision tracking, BOM management, file versioning, and physical inventory location management through a REST API and web UI. CAD and workflow integration (FreeCAD workbench, LibreOffice Calc extension) is maintained in separate repositories ([silo-mod](https://git.kindred-systems.com/kindred/silo-mod), [silo-calc](https://git.kindred-systems.com/kindred/silo-calc)).
### 1.1 Core Philosophy ### 1.1 Core Philosophy
@@ -37,13 +37,13 @@ Silo treats **part numbering schemas as configuration, not code**. Multiple numb
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
│ Silo Server (silod) │ │ Silo Server (silod) │
│ - REST API (76 endpoints) │ │ - REST API (75 endpoints) │
│ - Authentication (local, LDAP, OIDC) │ │ - Authentication (local, LDAP, OIDC) │
│ - Schema parsing and validation │ │ - Schema parsing and validation │
│ - Part number generation engine │ │ - Part number generation engine │
│ - Revision management │ │ - Revision management │
│ - Relationship graph / BOM │ │ - Relationship graph / BOM │
│ - Web UI (htmx) │ - Web UI (React SPA)
└─────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────┘
┌───────────────┴───────────────┐ ┌───────────────┴───────────────┐
@@ -68,7 +68,7 @@ Silo treats **part numbering schemas as configuration, not code**. Multiple numb
| CLI & API Server | Go (1.24) | chi/v5 router, pgx/v5 driver, zerolog | | CLI & API Server | Go (1.24) | chi/v5 router, pgx/v5 driver, zerolog |
| Authentication | Multi-backend | Local (bcrypt), LDAP/FreeIPA, OIDC/Keycloak | | Authentication | Multi-backend | Local (bcrypt), LDAP/FreeIPA, OIDC/Keycloak |
| Sessions | PostgreSQL pgxstore | alexedwards/scs, 24h lifetime | | Sessions | PostgreSQL pgxstore | alexedwards/scs, 24h lifetime |
| Web UI | Go html/template + htmx | Lightweight, minimal JS | | Web UI | React 19, Vite 6, TypeScript 5.7 | Catppuccin Mocha theme, inline styles |
--- ---
@@ -362,15 +362,35 @@ Recommendation: Pessimistic locking for CAD files (merge is impractical).
## 6. Web Interface ## 6. Web Interface
### 6.1 Features ### 6.1 Architecture
- **Browse**: Navigate item hierarchy (project → assembly → subassembly → part) The web UI is a React single-page application served at `/` by the Go server. The SPA communicates with the backend exclusively via the JSON REST API at `/api/*`.
- **Search**: Full-text search across part numbers, descriptions, properties
- **View**: Item details, revision history, relationships, location
- **BOM Viewer**: Expandable tree view of assembly structure
- **"Open in FreeCAD"**: Launch FreeCAD with specific item via URI handler
### 6.2 URI Handler - **Stack**: React 19, React Router 7, Vite 6, TypeScript 5.7
- **Theme**: Catppuccin Mocha (dark) via CSS custom properties
- **Styling**: Inline `React.CSSProperties` — no CSS modules, no Tailwind
- **State**: Local `useState` + custom hooks (no Redux/Zustand)
- **SPA serving**: `web/dist/` served by Go's `NotFound` handler with `index.html` fallback for client-side routing
### 6.2 Pages
| Page | Route | Description |
|------|-------|-------------|
| Items | `/` | Master-detail layout with resizable split panel, sortable table, 5-tab detail view (Main, Properties, Revisions, BOM, Where Used), in-pane CRUD forms |
| Projects | `/projects` | Project CRUD with sortable table, in-pane forms |
| Schemas | `/schemas` | Schema browser with collapsible segments, enum value CRUD |
| Settings | `/settings` | Account info, API token management |
| Audit | `/audit` | Item completeness scoring |
| Login | `/login` | Username/password form, conditional OIDC button |
### 6.3 Design Patterns
- **In-pane forms**: Create/Edit/Delete forms render in the detail pane area (Infor ERP-style), not as modal overlays
- **Permission checks**: Write actions conditionally rendered based on user role
- **Fuzzy search**: Debounced search with scope toggle (All/PN/Description)
- **Persistence**: `useLocalStorage` hook for user preferences (layout mode, column visibility)
### 6.4 URI Handler
Register `silo://` protocol handler: Register `silo://` protocol handler:
@@ -379,12 +399,7 @@ silo://open/PROTO-AS-0001 # Open latest revision
silo://open/PROTO-AS-0001?rev=3 # Open specific revision silo://open/PROTO-AS-0001?rev=3 # Open specific revision
``` ```
### 6.3 Technology See [frontend-spec.md](../frontend-spec.md) for full component specifications.
- **Backend**: Go with standard library HTTP
- **Frontend**: htmx for interactivity, minimal JavaScript
- **Templates**: Go html/template
- **Search**: PostgreSQL full-text search (pg_trgm for fuzzy matching)
--- ---
@@ -583,7 +598,7 @@ See [AUTH.md](AUTH.md) for full architecture details and [AUTH_USER_GUIDE.md](AU
## 11. API Design ## 11. API Design
### 11.1 REST Endpoints (Implemented) ### 11.1 REST Endpoints (75 Implemented)
``` ```
# Health (no auth) # Health (no auth)
@@ -597,21 +612,18 @@ POST /logout # Logout
GET /auth/oidc # OIDC login redirect GET /auth/oidc # OIDC login redirect
GET /auth/callback # OIDC callback GET /auth/callback # OIDC callback
# Web UI (auth + CSRF) # Public API (no auth required)
GET / # Items page GET /api/auth/config # Auth backend configuration (for login UI)
GET /projects # Projects page
GET /schemas # Schemas page
GET /audit # Audit/completeness page
GET /settings # User settings / token management
POST /settings/tokens # Create API token (web)
POST /settings/tokens/{id}/revoke # Revoke API token (web)
# Auth API # Auth API (require auth)
GET /api/auth/me # Current authenticated user GET /api/auth/me # Current authenticated user
GET /api/auth/tokens # List user's API tokens GET /api/auth/tokens # List user's API tokens
POST /api/auth/tokens # Create API token POST /api/auth/tokens # Create API token
DELETE /api/auth/tokens/{id} # Revoke API token DELETE /api/auth/tokens/{id} # Revoke API token
# Presigned Uploads (editor)
POST /api/uploads/presign # Get presigned MinIO upload URL [editor]
# Schemas (read: viewer, write: editor) # Schemas (read: viewer, write: editor)
GET /api/schemas # List all schemas GET /api/schemas # List all schemas
GET /api/schemas/{name} # Get schema details GET /api/schemas/{name} # Get schema details
@@ -659,9 +671,13 @@ PATCH /api/items/{partNumber}/revisions/{revision} # Update status/labe
POST /api/items/{partNumber}/revisions/{revision}/rollback # Rollback to revision [editor] POST /api/items/{partNumber}/revisions/{revision}/rollback # Rollback to revision [editor]
# Files # Files
GET /api/items/{partNumber}/files # List item file attachments
GET /api/items/{partNumber}/file # Download latest file GET /api/items/{partNumber}/file # Download latest file
GET /api/items/{partNumber}/file/{revision} # Download file at revision GET /api/items/{partNumber}/file/{revision} # Download file at revision
POST /api/items/{partNumber}/file # Upload file [editor] POST /api/items/{partNumber}/file # Upload file [editor]
POST /api/items/{partNumber}/files # Associate uploaded file with item [editor]
DELETE /api/items/{partNumber}/files/{fileId} # Remove file association [editor]
PUT /api/items/{partNumber}/thumbnail # Set item thumbnail [editor]
# BOM # BOM
GET /api/items/{partNumber}/bom # List direct children GET /api/items/{partNumber}/bom # List direct children
@@ -718,11 +734,11 @@ POST /api/inventory/{partNumber}/move
### 12.1 Implemented ### 12.1 Implemented
- [x] PostgreSQL database schema (10 migrations) - [x] PostgreSQL database schema (11 migrations)
- [x] YAML schema parser for part numbering - [x] YAML schema parser for part numbering
- [x] Part number generation engine - [x] Part number generation engine
- [x] CLI tool (`cmd/silo`) - [x] CLI tool (`cmd/silo`)
- [x] API server (`cmd/silod`) with 76 endpoints - [x] API server (`cmd/silod`) with 75 endpoints
- [x] MinIO integration for file storage with versioning - [x] MinIO integration for file storage with versioning
- [x] BOM relationships (component, alternate, reference) - [x] BOM relationships (component, alternate, reference)
- [x] Multi-level BOM (recursive expansion with configurable depth) - [x] Multi-level BOM (recursive expansion with configurable depth)
@@ -736,7 +752,8 @@ POST /api/inventory/{partNumber}/move
- [x] Project management with many-to-many item tagging - [x] Project management with many-to-many item tagging
- [x] CSV import/export with dry-run validation - [x] CSV import/export with dry-run validation
- [x] ODS spreadsheet import/export (items, BOMs, project sheets) - [x] ODS spreadsheet import/export (items, BOMs, project sheets)
- [x] Web UI for items, projects, schemas, audit (htmx) - [x] Web UI for items, projects, schemas, audit, settings (React SPA)
- [x] File attachments with presigned upload and thumbnail support
- [x] Authentication (local, LDAP, OIDC) with role-based access control - [x] Authentication (local, LDAP, OIDC) with role-based access control
- [x] API token management (SHA-256 hashed) - [x] API token management (SHA-256 hashed)
- [x] Session management (PostgreSQL-backed) - [x] Session management (PostgreSQL-backed)
@@ -757,7 +774,7 @@ POST /api/inventory/{partNumber}/move
### 12.3 Not Started ### 12.3 Not Started
- [ ] Unit tests (Go server — minimal coverage exists) - [ ] Unit tests (Go server — 9 test files exist, coverage is partial)
- [ ] Schema migration tooling - [ ] Schema migration tooling
- [ ] Checkout locking - [ ] Checkout locking
- [ ] Approval workflows - [ ] Approval workflows

View File

@@ -1,6 +1,6 @@
# Silo Development Status # Silo Development Status
**Last Updated:** 2026-02-06 **Last Updated:** 2026-02-08
--- ---
@@ -10,10 +10,10 @@
| Component | Status | Notes | | Component | Status | Notes |
|-----------|--------|-------| |-----------|--------|-------|
| PostgreSQL schema | Complete | 10 migrations applied | | PostgreSQL schema | Complete | 11 migrations applied |
| YAML schema parser | Complete | Supports enum, serial, constant, string segments | | YAML schema parser | Complete | Supports enum, serial, constant, string segments |
| Part number generator | Complete | Scoped sequences, category-based format | | Part number generator | Complete | Scoped sequences, category-based format |
| API server (`silod`) | Complete | 74 REST endpoints via chi/v5 | | API server (`silod`) | Complete | 75 REST endpoints via chi/v5 |
| CLI tool (`silo`) | Complete | Item registration and management | | CLI tool (`silo`) | Complete | Item registration and management |
| MinIO file storage | Complete | Upload, download, versioning, checksums | | MinIO file storage | Complete | Upload, download, versioning, checksums |
| Revision control | Complete | Append-only history, rollback, comparison, status/labels | | Revision control | Complete | Append-only history, rollback, comparison, status/labels |
@@ -28,7 +28,8 @@
| Audit logging | Complete | audit_log table, completeness scoring | | Audit logging | Complete | audit_log table, completeness scoring |
| CSRF protection | Complete | nosurf on web forms | | CSRF protection | Complete | nosurf on web forms |
| Fuzzy search | Complete | sahilm/fuzzy library | | Fuzzy search | Complete | sahilm/fuzzy library |
| Web UI | Complete | Items, projects, schemas, audit pages (htmx) | | Web UI | Complete | React SPA (Vite + TypeScript), 6 pages, Catppuccin Mocha theme |
| File attachments | Complete | Presigned uploads, item file association, thumbnails |
| Odoo ERP integration | Partial | Config and sync-log CRUD functional; push/pull are stubs | | Odoo ERP integration | Partial | Config and sync-log CRUD functional; push/pull are stubs |
| Docker Compose | Complete | Dev and production configurations | | Docker Compose | Complete | Dev and production configurations |
| Deployment scripts | Complete | setup-host, deploy, init-db, setup-ipa-nginx | | Deployment scripts | Complete | setup-host, deploy, init-db, setup-ipa-nginx |
@@ -46,7 +47,7 @@ FreeCAD workbench and LibreOffice Calc extension are maintained in separate repo
| Inventory API endpoints | Database tables exist, no REST handlers | | Inventory API endpoints | Database tables exist, no REST handlers |
| Date segment type | Schema parser placeholder only | | Date segment type | Schema parser placeholder only |
| Part number format validation | API accepts but does not validate format on creation | | Part number format validation | API accepts but does not validate format on creation |
| Unit tests | Minimal: 1 Go test file (`internal/ods/ods_test.go`) | | Unit tests | 9 Go test files across api, db, ods, partnum, schema packages |
--- ---
@@ -92,3 +93,4 @@ The schema defines 170 category codes across 10 groups:
| 008_odoo_integration.sql | Odoo ERP integration tables (integrations, sync_log) | | 008_odoo_integration.sql | Odoo ERP integration tables (integrations, sync_log) |
| 009_auth.sql | Authentication system (users, api_tokens, sessions, audit_log, user tracking columns) | | 009_auth.sql | Authentication system (users, api_tokens, sessions, audit_log, user tracking columns) |
| 010_item_extended_fields.sql | Extended item fields (sourcing_type, sourcing_link, standard_cost, long_description) | | 010_item_extended_fields.sql | Extended item fields (sourcing_type, sourcing_link, standard_cost, long_description) |
| 011_item_files.sql | Item file attachments (item_files table, thumbnail_key column) |

View File

@@ -1,10 +1,10 @@
# Silo Frontend Specification # Silo Frontend Specification
Current as of 2026-02-06. Tracks the React + Vite + TypeScript frontend migration (epic #6). Current as of 2026-02-08. Documents the React + Vite + TypeScript frontend (migration from Go templates is complete).
## Overview ## Overview
The Silo web UI is being migrated from server-rendered Go templates with vanilla JavaScript (~7,000 lines across 7 templates) to a React single-page application. The Go API server remains unchanged — it serves JSON at `/api/*` and the React app consumes it. The Silo web UI has been migrated from server-rendered Go templates to a React single-page application. The Go templates (~7,000 lines across 7 files) have been removed. The Go API server serves JSON at `/api/*` and the React SPA at `/`.
**Stack**: React 19, React Router 7, Vite 6, TypeScript 5.7 **Stack**: React 19, React Router 7, Vite 6, TypeScript 5.7
**Theme**: Catppuccin Mocha (dark) via CSS custom properties **Theme**: Catppuccin Mocha (dark) via CSS custom properties
@@ -19,22 +19,21 @@ The Silo web UI is being migrated from server-rendered Go templates with vanilla
| 1 | #7 | Scaffold React + Vite + TS, shared layout, auth, API client | Code complete | | 1 | #7 | Scaffold React + Vite + TS, shared layout, auth, API client | Code complete |
| 2 | #8 | Migrate Items page with UI improvements | Code complete | | 2 | #8 | Migrate Items page with UI improvements | Code complete |
| 3 | #9 | Migrate Projects, Schemas, Settings, Login pages | Code complete | | 3 | #9 | Migrate Projects, Schemas, Settings, Login pages | Code complete |
| 4 | #10 | Remove Go templates, Docker integration, cleanup | Not started | | 4 | #10 | Remove Go templates, Docker integration, cleanup | Complete |
## Architecture ## Architecture
``` ```
Browser Browser
└── React SPA (served at /app/* during transition, / after Phase 4) └── React SPA (served at /)
├── Vite dev server (development) → proxies /api/* to Go backend ├── Vite dev server (development) → proxies /api/* to Go backend
└── Static files in web/dist/ (production) → served by Go binary └── Static files in web/dist/ (production) → served by Go binary
Go Server (silod) Go Server (silod)
├── /api/* JSON REST API (unchanged) ├── /api/* JSON REST API
├── /login, /logout Session auth endpoints (form POST) ├── /login, /logout Session auth endpoints (form POST)
├── /auth/oidc OIDC redirect flow ├── /auth/oidc OIDC redirect flow
── /app/* React SPA static files (current transition) ── /* React SPA (NotFound handler serves index.html for client-side routing)
└── /* Go template pages (removed in Phase 4)
``` ```
### Auth Flow ### Auth Flow
@@ -101,7 +100,7 @@ web/
└── AuditPage.tsx Audit completeness (placeholder, expanded in Issue #5) └── AuditPage.tsx Audit completeness (placeholder, expanded in Issue #5)
``` ```
**Total**: 32 source files, ~5,300 lines of TypeScript/TSX. **Total**: ~40 source files, ~7,600 lines of TypeScript/TSX.
## Design System ## Design System
@@ -229,25 +228,17 @@ Basic table showing audit completeness data from `GET /api/audit/completeness`.
**Import**: CSVImportResult, CSVImportError **Import**: CSVImportResult, CSVImportError
**Errors**: ErrorResponse **Errors**: ErrorResponse
## Remaining Work ## Completed Work
### Issue #10: Remove Go Templates + Docker Integration ### Issue #10: Remove Go Templates + Docker Integration -- COMPLETE
The final phase completes the migration: Completed in commit `50923cf`. All Go templates deleted, `web.go` handler removed, SPA serves at `/` via `NotFound` handler with `index.html` fallback. `build/package/Dockerfile` added.
**Remove Go templates**: Delete `internal/api/templates/` (7 HTML files), remove template loading code, remove web handler route group and CSRF middleware for web routes, remove `HandleIndex`, `HandleProjects`, `HandleSchemas`, `HandleSettings` handlers. ### Remaining Work
**SPA serving**: Serve `web/dist/` at `/` with SPA fallback (non-API routes return `index.html`). Either `go:embed` for single-binary deployment or filesystem serving. `/api/*` routes must take precedence. Cache headers for Vite's hashed assets.
**Docker**: Update `build/package/Dockerfile` to multi-stage — Stage 1: Node (`npm ci && npm run build`), Stage 2: Go build with `web/dist/`, Final: minimal Alpine image with single binary.
**Makefile**: Existing `web-install`, `web-dev`, `web-build` targets need verification. `make build` should include the web build step. `make clean` should include `web/dist/`.
**Acceptance criteria**: Single Docker image serves both API and React frontend. `make build` produces working binary. No Go template code remains. All pages accessible at React Router paths.
### Issue #5: Component Audit UI (future) ### Issue #5: Component Audit UI (future)
After migration completes, the Audit page will be expanded with completeness scoring, inline editing, tier filtering, and category breakdowns. This will be built natively in React using the patterns established in the migration. The Audit page will be expanded with completeness scoring, inline editing, tier filtering, and category breakdowns. This will be built natively in React using the patterns established in the migration.
## Development ## Development
@@ -572,11 +563,11 @@ The right sidebar is divided into three sections with `borderBottom: 1px solid v
**Thumbnail**: A 4:3 aspect ratio placeholder box (`--ctp-crust` bg, `--ctp-surface0` border) with centered text "Generated from CAD file or upload manually". Clicking opens file picker filtered to images. If a thumbnail is uploaded, show it as an `<img>` with `object-fit: cover`. **Thumbnail**: A 4:3 aspect ratio placeholder box (`--ctp-crust` bg, `--ctp-surface0` border) with centered text "Generated from CAD file or upload manually". Clicking opens file picker filtered to images. If a thumbnail is uploaded, show it as an `<img>` with `object-fit: cover`.
## Backend Changes Required ## Backend Changes
The following API additions are needed. These should be tracked as sub-tasks or a separate issue. Items 1-3 and 5 below are implemented (migration `011_item_files.sql`, `internal/api/file_handlers.go`). Item 4 (hierarchical categories) remains open.
### 1. Presigned Upload URL ### 1. Presigned Upload URL -- IMPLEMENTED
``` ```
POST /api/uploads/presign POST /api/uploads/presign
@@ -586,7 +577,7 @@ Response: { "object_key": "uploads/tmp/{uuid}/{filename}", "upload_url": "https:
The Go handler generates a presigned PUT URL via the MinIO SDK. Objects are uploaded to a temporary prefix. On item creation, they're moved/linked to the item's permanent prefix. The Go handler generates a presigned PUT URL via the MinIO SDK. Objects are uploaded to a temporary prefix. On item creation, they're moved/linked to the item's permanent prefix.
### 2. File Association ### 2. File Association -- IMPLEMENTED
``` ```
POST /api/items/{id}/files POST /api/items/{id}/files
@@ -596,7 +587,7 @@ Response: { "file_id": "uuid", "filename": "...", "size": ..., "created_at": "..
Moves the object from the temp prefix to `items/{item_id}/files/{file_id}` and creates a row in a new `item_files` table. Moves the object from the temp prefix to `items/{item_id}/files/{file_id}` and creates a row in a new `item_files` table.
### 3. Thumbnail ### 3. Thumbnail -- IMPLEMENTED
``` ```
PUT /api/items/{id}/thumbnail PUT /api/items/{id}/thumbnail
@@ -606,7 +597,7 @@ Response: 204
Stores the thumbnail at `items/{item_id}/thumbnail.png` in MinIO. Updates `item.thumbnail_key` column. Stores the thumbnail at `items/{item_id}/thumbnail.png` in MinIO. Updates `item.thumbnail_key` column.
### 4. Hierarchical Categories ### 4. Hierarchical Categories -- NOT IMPLEMENTED
If schemas don't currently support a hierarchical category tree, one of these approaches: If schemas don't currently support a hierarchical category tree, one of these approaches:
@@ -634,7 +625,7 @@ DELETE /api/categories/{id} → cascade check
**Recommendation**: Option B is more flexible and keeps categories as a first-class entity. The three-tier picker doesn't need to be limited to exactly three levels — it can render as many columns as the deepest category path, but three is the practical default (Domain → Group → Subtype). **Recommendation**: Option B is more flexible and keeps categories as a first-class entity. The three-tier picker doesn't need to be limited to exactly three levels — it can render as many columns as the deepest category path, but three is the practical default (Domain → Group → Subtype).
### 5. Database Schema Addition ### 5. Database Schema Addition -- IMPLEMENTED
```sql ```sql
CREATE TABLE item_files ( CREATE TABLE item_files (

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from "react";
import { get, post, del } from '../../api/client'; import { get, post, del } from "../../api/client";
import type { Item, Project, Revision } from '../../api/types'; import type { Item, Project, Revision } from "../../api/types";
interface MainTabProps { interface MainTabProps {
item: Item; item: Item;
@@ -9,8 +9,14 @@ interface MainTabProps {
} }
function formatDate(s: string) { function formatDate(s: string) {
if (!s) return '—'; if (!s) return "—";
return new Date(s).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); return new Date(s).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
} }
function formatFileSize(bytes: number) { function formatFileSize(bytes: number) {
@@ -21,19 +27,23 @@ function formatFileSize(bytes: number) {
} }
export function MainTab({ item, onReload, isEditor }: MainTabProps) { export function MainTab({ item, onReload, isEditor }: MainTabProps) {
const [itemProjects, setItemProjects] = useState<string[]>([]); const [itemProjects, setItemProjects] = useState<Project[]>([]);
const [allProjects, setAllProjects] = useState<Project[]>([]); const [allProjects, setAllProjects] = useState<Project[]>([]);
const [latestRev, setLatestRev] = useState<Revision | null>(null); const [latestRev, setLatestRev] = useState<Revision | null>(null);
const [addProject, setAddProject] = useState(''); const [addProject, setAddProject] = useState("");
useEffect(() => { useEffect(() => {
get<string[]>(`/api/items/${encodeURIComponent(item.part_number)}/projects`) get<Project[]>(
`/api/items/${encodeURIComponent(item.part_number)}/projects`,
)
.then(setItemProjects) .then(setItemProjects)
.catch(() => setItemProjects([])); .catch(() => setItemProjects([]));
get<Project[]>('/api/projects') get<Project[]>("/api/projects")
.then(setAllProjects) .then(setAllProjects)
.catch(() => {}); .catch(() => {});
get<Revision[]>(`/api/items/${encodeURIComponent(item.part_number)}/revisions`) get<Revision[]>(
`/api/items/${encodeURIComponent(item.part_number)}/revisions`,
)
.then((revs) => { .then((revs) => {
if (revs.length > 0) setLatestRev(revs[revs.length - 1]!); if (revs.length > 0) setLatestRev(revs[revs.length - 1]!);
}) })
@@ -43,67 +53,144 @@ export function MainTab({ item, onReload, isEditor }: MainTabProps) {
const handleAddProject = async () => { const handleAddProject = async () => {
if (!addProject) return; if (!addProject) return;
try { try {
await post(`/api/items/${encodeURIComponent(item.part_number)}/projects`, { projects: [addProject] }); await post(
setItemProjects((prev) => [...prev, addProject]); `/api/items/${encodeURIComponent(item.part_number)}/projects`,
setAddProject(''); { projects: [addProject] },
);
const proj = allProjects.find((p) => p.code === addProject);
if (proj) setItemProjects((prev) => [...prev, proj]);
setAddProject("");
onReload(); onReload();
} catch (e) { } catch (e) {
alert(e instanceof Error ? e.message : 'Failed to add project'); alert(e instanceof Error ? e.message : "Failed to add project");
} }
}; };
const handleRemoveProject = async (code: string) => { const handleRemoveProject = async (code: string) => {
try { try {
await del(`/api/items/${encodeURIComponent(item.part_number)}/projects/${encodeURIComponent(code)}`); await del(
setItemProjects((prev) => prev.filter((p) => p !== code)); `/api/items/${encodeURIComponent(item.part_number)}/projects/${encodeURIComponent(code)}`,
);
setItemProjects((prev) => prev.filter((p) => p.code !== code));
onReload(); onReload();
} catch (e) { } catch (e) {
alert(e instanceof Error ? e.message : 'Failed to remove project'); alert(e instanceof Error ? e.message : "Failed to remove project");
} }
}; };
const row = (label: string, value: React.ReactNode) => ( const row = (label: string, value: React.ReactNode) => (
<div style={{ display: 'flex', gap: '1rem', padding: '0.3rem 0', fontSize: '0.85rem' }}> <div
<span style={{ width: 120, flexShrink: 0, color: 'var(--ctp-subtext0)' }}>{label}</span> style={{
<span style={{ color: 'var(--ctp-text)' }}>{value}</span> display: "flex",
gap: "1rem",
padding: "0.3rem 0",
fontSize: "0.85rem",
}}
>
<span style={{ width: 120, flexShrink: 0, color: "var(--ctp-subtext0)" }}>
{label}
</span>
<span style={{ color: "var(--ctp-text)" }}>{value}</span>
</div> </div>
); );
return ( return (
<div> <div>
{row('Part Number', <span style={{ fontFamily: "'JetBrains Mono', monospace", color: 'var(--ctp-peach)' }}>{item.part_number}</span>)} {row(
{row('Description', item.description)} "Part Number",
{row('Type', item.item_type)} <span
{row('Sourcing', item.sourcing_type || '—')} style={{
{item.sourcing_link && row('Source Link', <a href={item.sourcing_link} target="_blank" rel="noreferrer">{item.sourcing_link}</a>)} fontFamily: "'JetBrains Mono', monospace",
{item.standard_cost != null && row('Std Cost', `$${item.standard_cost.toFixed(2)}`)} color: "var(--ctp-peach)",
{row('Revision', `Rev ${item.current_revision}`)} }}
{row('Created', formatDate(item.created_at))} >
{row('Updated', formatDate(item.updated_at))} {item.part_number}
</span>,
)}
{row("Description", item.description)}
{row("Type", item.item_type)}
{row("Sourcing", item.sourcing_type || "—")}
{item.sourcing_link &&
row(
"Source Link",
<a href={item.sourcing_link} target="_blank" rel="noreferrer">
{item.sourcing_link}
</a>,
)}
{item.standard_cost != null &&
row("Std Cost", `$${item.standard_cost.toFixed(2)}`)}
{row("Revision", `Rev ${item.current_revision}`)}
{row("Created", formatDate(item.created_at))}
{row("Updated", formatDate(item.updated_at))}
{item.long_description && ( {item.long_description && (
<div style={{ marginTop: '0.75rem', padding: '0.5rem', backgroundColor: 'var(--ctp-surface0)', borderRadius: '0.4rem', fontSize: '0.85rem' }}> <div
<div style={{ color: 'var(--ctp-subtext0)', fontSize: '0.75rem', marginBottom: '0.25rem' }}>Long Description</div> style={{
<div style={{ whiteSpace: 'pre-wrap' }}>{item.long_description}</div> marginTop: "0.75rem",
padding: "0.5rem",
backgroundColor: "var(--ctp-surface0)",
borderRadius: "0.4rem",
fontSize: "0.85rem",
}}
>
<div
style={{
color: "var(--ctp-subtext0)",
fontSize: "0.75rem",
marginBottom: "0.25rem",
}}
>
Long Description
</div>
<div style={{ whiteSpace: "pre-wrap" }}>{item.long_description}</div>
</div> </div>
)} )}
{/* Project Tags */} {/* Project Tags */}
<div style={{ marginTop: '0.75rem' }}> <div style={{ marginTop: "0.75rem" }}>
<div style={{ color: 'var(--ctp-subtext0)', fontSize: '0.75rem', marginBottom: '0.25rem' }}>Projects</div> <div
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.25rem', alignItems: 'center' }}> style={{
{itemProjects.map((code) => ( color: "var(--ctp-subtext0)",
<span key={code} style={{ fontSize: "0.75rem",
display: 'inline-flex', alignItems: 'center', gap: '0.25rem', marginBottom: "0.25rem",
padding: '0.1rem 0.5rem', borderRadius: '1rem', }}
backgroundColor: 'rgba(203,166,247,0.15)', color: 'var(--ctp-mauve)', >
fontSize: '0.75rem', Projects
}}> </div>
{code} <div
style={{
display: "flex",
flexWrap: "wrap",
gap: "0.25rem",
alignItems: "center",
}}
>
{itemProjects.map((proj) => (
<span
key={proj.code}
style={{
display: "inline-flex",
alignItems: "center",
gap: "0.25rem",
padding: "0.1rem 0.5rem",
borderRadius: "1rem",
backgroundColor: "rgba(203,166,247,0.15)",
color: "var(--ctp-mauve)",
fontSize: "0.75rem",
}}
>
{proj.code}
{isEditor && ( {isEditor && (
<button <button
onClick={() => void handleRemoveProject(code)} onClick={() => void handleRemoveProject(proj.code)}
style={{ background: 'none', border: 'none', color: 'var(--ctp-overlay0)', cursor: 'pointer', fontSize: '0.8rem', padding: 0 }} style={{
background: "none",
border: "none",
color: "var(--ctp-overlay0)",
cursor: "pointer",
fontSize: "0.8rem",
padding: 0,
}}
> >
× ×
</button> </button>
@@ -116,22 +203,36 @@ export function MainTab({ item, onReload, isEditor }: MainTabProps) {
value={addProject} value={addProject}
onChange={(e) => setAddProject(e.target.value)} onChange={(e) => setAddProject(e.target.value)}
style={{ style={{
padding: '0.1rem 0.3rem', fontSize: '0.75rem', padding: "0.1rem 0.3rem",
backgroundColor: 'var(--ctp-surface0)', border: '1px solid var(--ctp-surface1)', fontSize: "0.75rem",
borderRadius: '0.3rem', color: 'var(--ctp-text)', backgroundColor: "var(--ctp-surface0)",
border: "1px solid var(--ctp-surface1)",
borderRadius: "0.3rem",
color: "var(--ctp-text)",
}} }}
> >
<option value="">+</option> <option value="">+</option>
{allProjects {allProjects
.filter((p) => !itemProjects.includes(p.code)) .filter((p) => !itemProjects.some((ip) => ip.code === p.code))
.map((p) => <option key={p.code} value={p.code}>{p.code}</option>)} .map((p) => (
<option key={p.code} value={p.code}>
{p.code}
</option>
))}
</select> </select>
{addProject && ( {addProject && (
<button onClick={() => void handleAddProject()} style={{ <button
padding: '0.1rem 0.4rem', fontSize: '0.7rem', border: 'none', onClick={() => void handleAddProject()}
backgroundColor: 'var(--ctp-mauve)', color: 'var(--ctp-crust)', style={{
borderRadius: '0.3rem', cursor: 'pointer', padding: "0.1rem 0.4rem",
}}> fontSize: "0.7rem",
border: "none",
backgroundColor: "var(--ctp-mauve)",
color: "var(--ctp-crust)",
borderRadius: "0.3rem",
cursor: "pointer",
}}
>
Add Add
</button> </button>
)} )}
@@ -142,21 +243,58 @@ export function MainTab({ item, onReload, isEditor }: MainTabProps) {
{/* File Info */} {/* File Info */}
{latestRev?.file_key && ( {latestRev?.file_key && (
<div style={{ marginTop: '0.75rem', padding: '0.5rem', backgroundColor: 'var(--ctp-surface0)', borderRadius: '0.4rem' }}> <div
<div style={{ color: 'var(--ctp-subtext0)', fontSize: '0.75rem', marginBottom: '0.25rem' }}>File Attachment (Rev {latestRev.revision_number})</div> style={{
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', fontSize: '0.85rem' }}> marginTop: "0.75rem",
{latestRev.file_size != null && <span>{formatFileSize(latestRev.file_size)}</span>} padding: "0.5rem",
backgroundColor: "var(--ctp-surface0)",
borderRadius: "0.4rem",
}}
>
<div
style={{
color: "var(--ctp-subtext0)",
fontSize: "0.75rem",
marginBottom: "0.25rem",
}}
>
File Attachment (Rev {latestRev.revision_number})
</div>
<div
style={{
display: "flex",
alignItems: "center",
gap: "0.75rem",
fontSize: "0.85rem",
}}
>
{latestRev.file_size != null && (
<span>{formatFileSize(latestRev.file_size)}</span>
)}
{latestRev.file_checksum && ( {latestRev.file_checksum && (
<span title={latestRev.file_checksum} style={{ color: 'var(--ctp-overlay1)', fontFamily: 'monospace', fontSize: '0.75rem' }}> <span
title={latestRev.file_checksum}
style={{
color: "var(--ctp-overlay1)",
fontFamily: "monospace",
fontSize: "0.75rem",
}}
>
SHA256: {latestRev.file_checksum.substring(0, 12)}... SHA256: {latestRev.file_checksum.substring(0, 12)}...
</span> </span>
)} )}
<button <button
onClick={() => { window.location.href = `/api/items/${encodeURIComponent(item.part_number)}/file/${latestRev.revision_number}`; }} onClick={() => {
window.location.href = `/api/items/${encodeURIComponent(item.part_number)}/file/${latestRev.revision_number}`;
}}
style={{ style={{
padding: '0.2rem 0.5rem', fontSize: '0.8rem', border: 'none', padding: "0.2rem 0.5rem",
backgroundColor: 'var(--ctp-surface1)', color: 'var(--ctp-text)', fontSize: "0.8rem",
borderRadius: '0.3rem', cursor: 'pointer', border: "none",
backgroundColor: "var(--ctp-surface1)",
color: "var(--ctp-text)",
borderRadius: "0.3rem",
cursor: "pointer",
}} }}
> >
Download Download