Commit Graph

31 Commits

Author SHA1 Message Date
27e112e7da feat(origin): add interactive open/saveAs methods to SiloOrigin
Add openDocumentInteractive() and saveDocumentAsInteractive() methods
to support the updated FileOrigin interface from Issue #10/#12.

- openDocumentInteractive: Delegates to Silo_Open command for search dialog
- saveDocumentAsInteractive: Triggers new item creation form for Save As

These methods enable the Std_* commands in FreeCAD to delegate to
SiloOrigin when Silo is the active origin.
2026-02-05 14:02:07 -06:00
914d97f9a0 feat(origin): implement SiloOrigin adapter for unified origin system
Implements Issue #11: Silo origin adapter

This commit creates the SiloOrigin class that implements the FileOrigin
interface introduced in Issue #9, enabling Silo to be used as a document
origin in the unified file origin system.

## SiloOrigin Class (silo_origin.py)

New Python module providing the FileOrigin implementation for Silo PLM:

### Identity Methods
- id(): Returns 'silo' as unique identifier
- name(): Returns 'Kindred Silo' for UI display
- nickname(): Returns 'Silo' for compact UI elements
- icon(): Returns 'silo' icon name
- type(): Returns OriginType.PLM (1)

### Workflow Characteristics
- tracksExternally(): True - Silo tracks documents in database
- requiresAuthentication(): True - Silo requires login

### Capabilities
- supportsRevisions(): True
- supportsBOM(): True
- supportsPartNumbers(): True
- supportsAssemblies(): True

### Connection State
- connectionState(): Checks auth status and API connectivity
- connect(): Triggers Silo_Auth dialog if needed
- disconnect(): Calls _client.logout()

### Document Identity (UUID-based tracking)
- documentIdentity(): Returns SiloItemId (UUID) as primary identity
- documentDisplayId(): Returns SiloPartNumber for human display
- ownsDocument(): True if document has SiloItemId or SiloPartNumber

### Core Operations (delegate to existing commands)
- newDocument(): Delegates to Silo_New command
- openDocument(): Uses find_file_by_part_number or _sync.open_item
- saveDocument(): Saves locally + uploads via _client._upload_file
- saveDocumentAs(): Triggers migration workflow for local docs

### Extended Operations
- commitDocument(): Delegates to Silo_Commit
- pullDocument(): Delegates to Silo_Pull
- pushDocument(): Delegates to Silo_Push
- showInfo(): Delegates to Silo_Info
- showBOM(): Delegates to Silo_BOM

### Module Functions
- get_silo_origin(): Returns singleton instance
- register_silo_origin(): Registers with FreeCADGui.addOrigin()
- unregister_silo_origin(): Cleanup function

## UUID Tracking (silo_commands.py)

Added SiloItemId property to all locations where Silo properties are set:

1. create_document_for_item() - Assembly objects (line 1115)
2. create_document_for_item() - Fallback Part objects (line 1131)
3. create_document_for_item() - Part objects (line 1145)
4. Silo_New.Activated() - Tagged existing objects (line 1471)

The SiloItemId stores the database UUID (Item.ID) which is immutable,
while SiloPartNumber remains the human-readable identifier that could
theoretically change.

Property structure on tracked objects:
- SiloItemId: UUID from database (primary tracking key)
- SiloPartNumber: Human-readable part number
- SiloRevision: Current revision number
- SiloItemType: 'part' or 'assembly'

## Workbench Integration (InitGui.py)

SiloOrigin is automatically registered when the Silo workbench
initializes:

    def Initialize(self):
        import silo_commands
        try:
            import silo_origin
            silo_origin.register_silo_origin()
        except Exception as e:
            FreeCAD.Console.PrintWarning(...)

This makes Silo available as a file origin via:
- FreeCADGui.listOrigins() -> includes 'silo'
- FreeCADGui.getOrigin('silo') -> returns origin info dict
- FreeCADGui.setActiveOrigin('silo') -> sets Silo as active

## Design Decisions

1. **Delegation Pattern**: SiloOrigin delegates to existing Silo
   commands rather than reimplementing logic, ensuring consistency
   and easier maintenance.

2. **UUID as Primary Identity**: documentIdentity() returns UUID
   (SiloItemId) for immutable tracking, while documentDisplayId()
   returns part number for user display.

3. **Graceful Fallback**: If SiloItemId is not present (legacy docs),
   falls back to SiloPartNumber for identity/ownership checks.

4. **Exception Handling**: All operations wrapped in try/except to
   prevent origin system failures from breaking FreeCAD.

Refs: #11
2026-02-05 13:29:45 -06:00
forbes
17a10ab1b6 feat(client): add SSE live updates, revision pull dialog, and auth dock support
- Add SiloEventListener class for SSE-based real-time notifications
- Add SiloPullDialog for revision selection when pulling
- Add conflict detection before pull (unsaved changes, stale revision, mtime)
- Add progress callback to _download_file() for UI feedback
- Integrate SSE listener into SiloAuthDockWidget with status indicator
- Refresh activity panel on remote change events
- Add top-level PySide.QtCore import for QThread/Signal usage
2026-02-01 16:26:02 -06:00
Zoe Forbes
3a67d2082b fix(api): retry item creation on duplicate part number collision
The sequence counter (sequences_by_name table) can get out of sync
with the items table if items were seeded/imported directly or if a
previous create failed after incrementing the sequence but before
the insert committed. This causes Generate() to return a part number
that already exists, hitting the unique constraint on items.part_number.

Add a retry loop (up to 5 attempts) in HandleCreateItem that detects
PostgreSQL unique violation errors (SQLSTATE 23505) via pgconn.PgError
and retries with the next sequence value. Non-duplicate errors still
fail immediately. If all retries are exhausted, returns 409 Conflict
instead of 500.
2026-02-01 14:36:41 -06:00
Zoe Forbes
071cba2ef9 fix: include parent_part_number in BOM API response
BOMEntryResponse was missing the ParentPartNumber field. The database
query populates it correctly, but the API serialization dropped it.
This caused the Calc extension to never populate the hidden
_silo_parent_pn column during pull, so push never called
_update_bom_relationship and no BOM entries were created.
2026-02-01 14:24:35 -06:00
Zoe Forbes
8f6e956fde feat: add component audit tool and category properties in create form
- New /audit page with completeness scoring engine
- Weighted scoring by sourcing type (purchased vs manufactured)
- Batch DB queries for items+properties, BOM existence, project codes
- API endpoints: GET /api/audit/completeness, GET /api/audit/completeness/{pn}
- Audit UI: tier summary bar, filterable table, split-panel inline editing
- Create item form now shows category-specific property fields on category select
- Properties collected and submitted with item creation
2026-02-01 10:41:57 -06:00
Zoe Forbes
36a8d9995d feat: LibreOffice Calc extension, ODS library, AI description, audit design
Calc extension (pkg/calc/):
- Python UNO ProtocolHandler with 8 toolbar commands
- SiloClient HTTP client adapted from FreeCAD workbench
- Pull BOM/Project: populates sheets with 28-col format, hidden property
  columns, row hash tracking, auto project tagging
- Push: row classification, create/update items, conflict detection
- Completion wizard: 3-step category/description/fields with PN conflict
  resolution dialog
- OpenRouter AI integration: generate standardized descriptions from seller
  text, configurable model/instructions, review dialog
- Settings: JSON persistence, env var fallbacks, OpenRouter fields
- 31 unit tests (no UNO/network required)

Go ODS library (internal/ods/):
- Pure Go ODS read/write (ZIP of XML, no headless LibreOffice)
- Writer, reader, 10 round-trip tests

Server ODS endpoints (internal/api/ods.go):
- GET /api/items/export.ods, template.ods, POST import.ods
- GET /api/items/{pn}/bom/export.ods
- GET /api/projects/{code}/sheet.ods
- POST /api/sheets/diff

Documentation:
- docs/CALC_EXTENSION.md: extension progress report
- docs/COMPONENT_AUDIT.md: web audit tool design with weighted scoring,
  assembly computed fields, batch AI assistance plan
2026-02-01 10:06:20 -06:00
Zoe Forbes
fc6e47437e fix: push detects locally modified files, renumber migration to 010
- Fix FreeCAD Push command: compare local file mtime against server's
  latest file revision timestamp instead of just checking file existence.
  Previously, any file already uploaded (even at an older revision) was
  skipped, causing 'All local files are already uploaded' when local
  files were newer.
- Add SiloClient.latest_file_revision() helper method
- Renumber 009_item_extended_fields.sql to 010 to avoid collision
  with 009_auth.sql from the auth system
2026-01-31 19:13:21 -06:00
Zoe Forbes
e84823227f Align client auth with backend: API tokens, session login, /api/auth/me
Rework the FreeCAD client authentication to match the Silo backend's
actual auth implementation (local, LDAP, OIDC/Keycloak, API tokens).

Auth model change:
- Remove the old AuthToken/AuthTokenExpiry session-token approach
- Unify on API tokens (ApiToken preference / SILO_API_TOKEN env var)
  as the single auth mechanism for the desktop client
- _get_auth_token() now delegates to _get_api_token() directly
- All requests use Bearer token auth via _get_auth_headers()

SiloClient.login() rewrite:
- POST form-encoded credentials to /login (matching the backend's
  form-based login handler, works with local and LDAP backends)
- Use the resulting session cookie to call GET /api/auth/me to get
  user info (username, role, auth_source)
- Create a persistent API token via POST /api/auth/tokens named
  'FreeCAD (hostname)' with 90-day expiry
- Store the raw token in ApiToken preference for all future requests
- No more ephemeral session tokens — API tokens survive restarts

New SiloClient methods:
- get_current_user(): GET /api/auth/me, returns user dict or None
- refresh_auth_info(): fetches /api/auth/me and updates cached prefs
- auth_role(): returns stored role (admin/editor/viewer)
- auth_source(): returns stored auth source (local/ldap/oidc)
- list_tokens(): GET /api/auth/tokens
- create_token(name, expires_in_days): POST /api/auth/tokens
- revoke_token(token_id): DELETE /api/auth/tokens/{id}

New preference keys:
- AuthRole: cached user role from server
- AuthSource: cached auth source (local, ldap, oidc)

Removed preference keys:
- AuthToken: replaced by ApiToken (was duplicative)
- AuthTokenExpiry: API tokens have server-side expiry

Auth helper changes:
- _save_auth_info(): stores username, role, source, and optionally token
- _clear_auth(): clears ApiToken, AuthUsername, AuthRole, AuthSource
- _get_auth_role(), _get_auth_source(): new accessors

Dock widget updates:
- New Role row showing role and auth source (e.g. 'editor (ldap)')
- Status refresh validates token against /api/auth/me on each poll
- Four status states: Connected (green), Token invalid (orange),
  Connected no auth (yellow), Disconnected (red)
- Caches validated user info back to preferences

Login dialog updates:
- Info text explains the flow (creates persistent API token)
- Placeholder text updated (removed LDAP-specific wording)
- Shows 'Logging in...' status during auth
- Displays role and auth source on success
- Disables login button during request

Settings dialog updates:
- API Token input field with show/hide toggle
- Token can be pasted directly from Silo web UI
- Hint text explains token sources (web UI, Login, env var)
- 'Clear Token and Logout' button replaces old clear credentials
- Save handler persists token changes
- Status summary shows role and auth source

check_connection() fix:
- Uses origin /health (not /api/health) matching actual route

Fixes the endpoint mismatch where client was calling a non-existent
POST /api/auth/login JSON endpoint. Now uses the actual form-based
POST /login + session cookie flow that the backend implements.
2026-01-31 16:24:07 -06:00
Zoe Forbes
2a86a79ca2 Add Silo authentication dock widget, login flow, and token management
Client-side authentication support for the Silo database API. Adds a
compact dock widget for auth status and login, bearer token management
on all HTTP requests, and an enhanced settings dialog.

Auth helper functions:
- _get_auth_token(): reads token from FreeCAD preferences, checks expiry
- _get_auth_username(): reads stored username from preferences
- _get_auth_headers(): returns Authorization bearer header dict
- _clear_auth(): clears token, username, and expiry from preferences

SiloClient auth methods:
- login(username, password): POSTs to /auth/login, stores token/user/expiry
- logout(): clears stored credentials via _clear_auth()
- is_authenticated(): checks for valid stored token
- auth_username(): returns stored username
- check_connection(): GETs /health to test server reachability

Auth header injection:
- _request(), _download_file(), _upload_file(), delete_bom_entry() all
  merge _get_auth_headers() into outgoing HTTP requests
- _request() clears stored token on 401 response

SiloAuthDockWidget:
- Status indicator dot (green=authenticated, yellow=no auth, red=disconnected)
- Displays current username and server URL
- Login/Logout toggle button
- Gear button opens Silo_Settings dialog
- 30-second QTimer polls connection and auth status
- Login dialog with username/password fields and inline error display

Silo_Auth command:
- Shows/focuses the auth dock panel from menu or toolbar
- Registered in workbench toolbar alongside existing commands

Silo_Settings enhancements:
- New Authentication section showing current auth status
- Clear Saved Credentials button
- Active values summary now includes authentication state

New icon:
- silo-auth.svg padlock icon in Catppuccin Mocha style

Graceful degradation: when backend auth endpoint does not yet exist,
login fails with a clear error and all existing unauthenticated
requests continue to work as before (empty auth headers are a no-op).
2026-01-31 14:36:37 -06:00
Zoe Forbes
1bd29e6a6a feat: add sourcing type, extended fields, and inline project tagging
- Add migration 009: sourcing_type (manufactured/purchased), sourcing_link,
  long_description, and standard_cost columns on items table
- Update Item struct, repository queries, and API handlers for new fields
- Add sourcing badge, long description block, standard cost, and sourcing
  link display to item detail panel
- Add inline project tag editor in detail panel (add/remove via dropdown)
- Add new fields to create and edit modals
- Update CSV import/export for new columns
- Merge with auth CreatedBy/UpdatedBy changes from stash
2026-01-31 14:27:11 -06:00
forbes
802368c583 fix(deploy): add database migration step and auth env vars
Add run_migrations function to deploy.sh that automatically applies
pending SQL migrations during deployment. Migrations are run after
config installation and before service restart.

Migration runner:
- Sources /etc/silo/silod.env for SILO_DB_PASSWORD
- Reads DB host/port/name/user from production config.yaml
- Waits for database connectivity (5 retries)
- Applies each migration file in order, skipping already-applied ones
- Gracefully degrades if psql is missing or DB password is not set

This fixes the missing migration 009 (auth tables) that caused:
- 'column created_by of relation projects does not exist'
- 'relation api_tokens does not exist'

Also adds auth environment variables to silod.env.example:
- SILO_SESSION_SECRET
- SILO_ADMIN_USERNAME / SILO_ADMIN_PASSWORD
- SILO_OIDC_CLIENT_SECRET, SILO_LDAP_BIND_PASSWORD
2026-01-31 12:09:17 -06:00
forbes
e92c022e80 feat(auth): add authentication, RBAC, API tokens, and default admin
Add a complete authentication and authorization system to Silo with
three pluggable backends (local bcrypt, LDAP/FreeIPA, OIDC/Keycloak),
session management, API token support, and role-based access control.

Authentication backends:
- Local: bcrypt (cost 12) password verification against users table
- LDAP: FreeIPA simple bind with group-to-role mapping
- OIDC: Keycloak redirect flow with realm role mapping
- Backends are tried in order; users upserted to DB on first login

Session and token management:
- PostgreSQL-backed sessions via alexedwards/scs + pgxstore
- Opaque API tokens (silo_ prefix, SHA-256 hashed, shown once)
- 24h session lifetime, HttpOnly/SameSite=Lax/Secure cookies

Role-based access control (admin > editor > viewer):
- RequireAuth middleware: Bearer token -> session -> redirect/401
- RequireRole middleware: per-route-group minimum role enforcement
- CSRF protection via justinas/nosurf on web forms, API exempt
- CORS locked to configured origins when auth enabled

Route restructuring:
- Public: /health, /ready, /login, /auth/oidc, /auth/callback
- Web (auth + CSRF): /, /projects, /schemas, /settings
- API read (viewer): GET /api/**
- API write (editor): POST/PUT/PATCH/DELETE /api/**

User context wiring:
- created_by/updated_by columns on items, projects, relationships
- All create/update handlers populate tracking fields from context
- CSV and BOM import handlers pass authenticated username
- Revision creation tracks user across all code paths

Default admin account:
- Configurable via auth.local.default_admin_username/password
- Env var overrides: SILO_ADMIN_USERNAME, SILO_ADMIN_PASSWORD
- Idempotent: created on first startup, skipped if exists

CLI and FreeCAD plugin:
- silo token create/list/revoke subcommands (HTTP API client)
- FreeCAD SiloClient sends Bearer token on all requests
- Token read from ApiToken preference or SILO_API_TOKEN env var

Web UI:
- Login page (Catppuccin Mocha themed, OIDC button conditional)
- Settings page with account info and API token management
- User display name, role badge, and logout button in header
- One-time token display banner with copy-to-clipboard

Database (migration 009):
- users table with role, auth_source, oidc_subject, password_hash
- api_tokens table with SHA-256 hash, prefix, expiry, scopes
- sessions table (scs pgxstore schema)
- audit_log table (schema ready for future use)
- created_by/updated_by ALTER on items, relationships, projects

New dependencies: scs/v2, scs/pgxstore, go-oidc/v3, go-ldap/v3,
justinas/nosurf, golang.org/x/oauth2
2026-01-31 11:20:12 -06:00
Zoe Forbes
c778825eb0 Add Silo mode toggle, SSL cert browsing, and BOM menu integration
- Silo_ToggleMode: checkable toolbar button that swaps Ctrl+O/S/N
  between standard FreeCAD file commands and Silo equivalents
- _swap_shortcuts() helper stores/restores original QAction shortcuts
- SSL settings: add CA certificate file browser (SslCertPath preference)
  with QFileDialog for .pem/.crt/.cer, loaded in _get_ssl_context()
- Integrate Silo_BOM into workbench toolbar (after upstream BOM merge)
- Add Silo_ToggleMode to toolbar as first item with separator
2026-01-31 09:21:48 -06:00
Zoe Forbes
8c0689991e feat: Infor-style split-panel layout, projects page, fuzzy search, Odoo scaffold
Web UI - Infor CloudSuite-style split-panel layout (items.html rewrite):
- Replace modal-based item detail with inline split-panel workspace
- Horizontal mode: item list on left, tabbed detail panel on right
- Vertical mode: detail panel on top, item list below
- Detail tabs: Main, Properties, Revisions, BOM, Where Used
- Ctrl+F opens in-page filter overlay with fuzzy search
- Column config gear icon with per-layout-mode persistence
- Search scope toggle pills (All / Part Number / Description)
- Selected row highlight with accent border
- Responsive breakpoint forces vertical below 900px
- Create/Edit/Delete remain as modal dialogs

Web UI - Projects page:
- New projects.html template with full CRUD
- Project table: Code, Name, Description, Item count, Created, Actions
- Create/Edit/Delete modals
- Click project code navigates to items filtered by project
- 3-tab navigation in base.html: Items, Projects, Schemas

Fuzzy search:
- Add sahilm/fuzzy dependency for ranked text matching
- New internal/api/search.go with SearchableItems fuzzy.Source
- GET /api/items/search endpoint with field scope and type/project filters
- Frontend routes to fuzzy endpoint when search input is non-empty

Odoo ERP integration scaffold:
- Migration 008: integrations and sync_log tables
- internal/odoo/ package: types, client stubs, sync stubs
- internal/db/integrations.go: IntegrationRepository
- internal/config/config.go: OdooConfig struct
- 6 API endpoints for config CRUD, sync log, test, push, pull
- All sync operations return stub responses

Documentation:
- docs/REPOSITORY_STATUS.md: comprehensive repository state report
  with architecture overview, API surface, feature stubs, and
  potential issues analysis
2026-01-31 09:20:27 -06:00
Zoe Forbes
bce7d5a181 Add BOM handling and routes to API and web UI 2026-01-31 08:38:02 -06:00
Zoe Forbes
3a79d89ec9 feat: add BOM system with API, database repository, and FreeCAD workbench command
Implement the full Bill of Materials stack on top of the existing
relationships table and bom_single_level view from migration 001.

API endpoints (6 new routes under /api/items/{partNumber}/bom):
- GET    /bom              Single-level BOM for an item
- GET    /bom/expanded     Multi-level BOM via recursive CTE (depth param)
- GET    /bom/where-used   Reverse lookup: which parents use this item
- POST   /bom              Add child to BOM with quantity, ref designators
- PUT    /bom/{child}      Update relationship type, quantity, ref des
- DELETE /bom/{child}       Remove child from BOM

Database layer (internal/db/relationships.go):
- RelationshipRepository with full CRUD operations
- Single-level BOM query joining relationships with items
- Multi-level BOM expansion via recursive CTE (max depth 20)
- Where-used reverse lookup query
- Cycle detection at insert time to prevent circular BOMs
- BOMEntry and BOMTreeEntry types for denormalized query results

Server wiring:
- Added RelationshipRepository to Server struct in handlers.go
- Registered BOM routes in routes.go under /{partNumber} subrouter

FreeCAD workbench (pkg/freecad/silo_commands.py):
- 9 new BOM methods on SiloClient (get, expanded, where-used, add,
  update, delete)
- Silo_BOM command class with two-tab dialog:
  - BOM tab: table of children with Add/Edit/Remove buttons
  - Where Used tab: read-only table of parent assemblies
- Add sub-dialog with fields for part number, type, qty, unit, ref des
- Edit sub-dialog pre-populated with current values
- Remove with confirmation prompt
- silo-bom.svg icon matching existing toolbar style
- Command registered in InitGui.py toolbar

No new migrations required - uses existing relationships table and
bom_single_level view from 001_initial.sql.
2026-01-31 08:09:26 -06:00
Zoe Forbes
8e44ed2f75 Fix SIGSEGV: defer document open after dialog close
Opening documents (especially assemblies) inside the QDialog nested
event loop can crash FreeCAD when the Assembly solver triggers during
document restore. Move FreeCAD.openDocument() to after dialog.exec_()
returns.
2026-01-29 22:42:15 -06:00
Zoe Forbes
e2b3f128c6 Fix API URL: only auto-append /api for bare hostnames
Use urllib.parse to check if the URL has a path component. Only append
/api when the path is empty (e.g. https://silo.kindred.internal).
URLs that already include a path (e.g. http://localhost:8080/api) are
left unchanged. Restore localhost placeholder as the default.
2026-01-29 22:27:10 -06:00
Zoe Forbes
559f61514c Fix API URL handling and SSL certificate verification
- Auto-append /api to base URL if not already present, so users can
  enter just the hostname (e.g. https://silo.kindred.internal)
- Load system CA certificate bundle in SSL context so the bundled
  Python trusts internal CAs (FreeIPA) without disabling verification
- Update settings dialog placeholder and hint text to clarify expected
  URL format
2026-01-29 22:26:26 -06:00
forbes
f08ecc14ea feat(workbench): fix icon loading and add settings dialog
Replace hardcoded FreeCAD addon path searches with __file__-relative
resolution for icons in both InitGui.py and silo_commands.py. Icons now
load correctly regardless of install location.

Add Silo_Settings command with URL and SSL verification fields. Settings
persist via FreeCAD preferences and take priority over env vars. Wire
SSL context into all SiloClient HTTP methods.
2026-01-29 19:28:11 -06:00
53b5edb25f update documentation and specs 2026-01-29 13:10:12 -06:00
Zoe Forbes
5ee88a657f update deploy.sh 2026-01-26 22:43:39 -06:00
Zoe Forbes
93add05984 improve csv import handling 2026-01-26 21:51:07 -06:00
Zoe Forbes
2d44b2aa8f add free-ipa setup 2026-01-26 21:37:53 -06:00
Zoe Forbes
11aeb87216 update deployment 2026-01-26 21:32:57 -06:00
Zoe Forbes
00c5b2370b update deployment instructions 2026-01-26 21:31:49 -06:00
Zoe Forbes
d2eb62835b Add roadmap and deployment examples 2026-01-26 06:06:21 -06:00
Zoe Forbes
7f375fec7c Add revision control and project tagging migration 2026-01-24 16:27:18 -06:00
Zoe Forbes
ed9cef67f9 update databasing system with minimum API, schema parsing and FreeCAD
integration
2026-01-24 15:03:17 -06:00
Zoe Forbes
9c9609f906 first commit 2026-01-18 12:08:14 -06:00