Files
HickLab3D_Dashboard/README.md
T
hermes d4928789c4
Deploy HickLab3D Dashboard / Deploy to production (push) Failing after 23s
Add HL3D branding, Etsy Live/Draft paging and Projects lifecycle
Replace the old touchmark/PNG identity with the supplied lockup and
monogram, segmented role-aware navigation, Live/Draft listing filters
with generation-based cache invalidation, and admin Projects covering
immutable file revisions, listing live pointers, and exact-decimal
filament/packaging/shipping costing. Additive schema only; no Etsy
publication. Isolated clone tests: 301 tests, 1162 assertions.
2026-11-08 12:50:56 +00:00

9.6 KiB

HickLab3D dashboard

Laravel 13, Tailwind CSS 4, and MySQL dashboard for the HickLab3D workshop. It includes user accounts, Etsy shop data, BamBuddy printer status, Parcel2Go orders, and filament dryer control.

Setup

Requirements: PHP 8.3+, Composer, Node.js, and MySQL. Point the web server document root at public/.

composer install
npm install
cp .env.example .env
php artisan key:generate

On Windows PowerShell, use Copy-Item .env.example .env in place of cp .env.example .env.

Set DB_* in .env to your MySQL database, then run:

php artisan migrate
php artisan app:create-admin your-username you@example.com
npm run build
php artisan serve

The admin command prompts for a password. Never pass it on the command line. For local front-end development, use npm run dev alongside php artisan serve.

The migrations use the legacy table and column names. If you point the app at the existing HickLab3D database, back it up first. The migrations skip tables already present and create Laravel's session, cache, and job tables. Existing schema variations have not been verified against a database dump. Do not run migrate:fresh on a database with production data.

Integrations

  • Enter Etsy, Parcel2Go, BamBuddy, and dryer connection details on the admin Settings page. Saved values are encrypted in the database and take precedence over matching .env values.
  • Existing P2G_*, BAMBUDDY_*, DRYER_*, and ETSY_* environment values remain available as fallbacks. Save each integration on the Settings page to import those values into the database.
  • Connect the Etsy shop with OAuth after saving its app credentials. The browser talks to authenticated Laravel routes for dryer status and controls.
  • Runtime Etsy credentials remain Settings-page rows / ETSY_* env (optional aliases: HL3D_ETSY_KEYSTRING, HL3D_ETSY_SECRET). Never put tokens in MCP config.

Etsy MCP

First-party stdio MCP for HL3D agents. It uses the stored HickLab3D shop connection — agents must not start a new OAuth dance.

php artisan etsy:mcp

Register the local .mcp.json only where PHP is installed and the working directory is this app. On the Hermes host, use the existing web container instead (after deployment):

hermes -p hl3d_teamlead mcp add etsy --command ssh --args -T H-NODE-05 'docker exec -i -u www-data -w /var/www/HickLab3D nginx-ui-php php artisan etsy:mcp --no-ansi'

This uses the managed live environment without placing credentials in MCP config and does not require host PHP. Configure positive tool allow-lists through supported hermes config set commands before exposing tools to agents: teamlead/designer should get read/status tools only; marketing may additionally create drafts. Do not expose publishing, shipment or inventory-write tools without separately authorising those operations. Disable MCP sampling for this server. Start a fresh profile session or reload MCP after configuration.

An admin must reconnect Etsy in the dashboard if status reports unknown or missing scopes (listings_r listings_w transactions_r transactions_w shops_r). Never request operator passwords or MFA codes. New listings are drafts; confirm=true is a technical publish guard, not operator authorisation. Publishing, activating, renewing or repricing live listings, inventory changes and shipping still require explicit approval for the specific action in the current conversation.

Artisan scheduler

The Settings page lets admins tune registered jobs or run them immediately. Defaults: integrations:refresh and alerts:scan every 30 seconds, etsy:sync every 15 minutes, and etsy:refresh-token checks every five minutes. Etsy is no longer force-refreshed by the bundled integration job. The refresh check rotates only within ten minutes of expiry; database row locking and a fresh read serialise rotating-token use across MCP, HTTP and scheduler processes. Lowering the token-check cadence or disabling it can leave an idle access token expired.

Settings are atomically replaced under a shared file lock at storage/app/dashboard-schedules.json. Preserve storage and the Infisical-managed .env during deployments. Only H-NODE-05 owns the systemd scheduler timer; its container working directory must be /var/www/HickLab3D, matching the actual vhost and CI target. Subminute tasks keep schedule:run alive until the end of the minute, so do not install a duplicate cron or scheduler elsewhere.

For local tests in the existing serviceengineering toolchain image, explicitly set APP_ENV=testing: the image exports APP_ENV=local, which otherwise wins over PHPUnit XML and correctly activates real CSRF enforcement. Never disable production CSRF to repair that test-environment mismatch.

Etsy Open API v3 has no conversation inbox (conversations_* scopes do not exist). Receipt notes are exposed; sending a buyer-inbox reply is not.

Projects (admin)

Projects are the product aggregate: a reversible internal lifecycle (Design, Ready, Live, Retired — distinct from Etsy's own listing state), approved immutable revisions with private STL/3MF/G-code file bundles, Etsy listing mappings verified by reading the listing from Etsy (never an arbitrary shop/listing ID), and append-only cost snapshots and releases.

  • Uploads are limited to .stl, .3mf and .gcode, 100 MiB per file, 250 MiB combined per request, and 10 files per request, and are structurally validated (STL triangle/size cross-check, bounded 3MF zip/XML with no path traversal, symlink entries, external references or DOCTYPE/ENTITY declarations, plain-text G-code). Nothing uploaded here is ever parsed as a slicer job, executed, or served from a public URL — files live on the project_files disk (storage/app/project-files, not web-served) under a random generated key, streamed rather than buffered in memory, and only stream back through the authenticated, policy-checked projects.files.download route. Any failure partway through a multi-file upload (validation or otherwise) deletes every byte already written for that request.
  • Releasing a revision to a listing mapping requires a cost snapshot for that same revision with every category costed (filament, packaging and shipping — even as an explicit 0, but not missing) and pins an exact revision + cost snapshot; uploading a newer revision never moves that pointer on its own, and both release and revert create a new append-only project_releases row under an optimistic lock (project_listing_links.lock_version) rather than overwriting history. A detached listing mapping can no longer be released or reverted to.
  • Cost snapshots use fixed-point integer arithmetic (App\Support\FixedPoint / App\Services\ProjectCosting), not floats: the bcmath/gmp PHP extensions are not installed in this project's runtime, so exact-decimal math is done by scaling decimal strings into bounded native integers with explicit overflow and storable-precision checks. Estimate and actual snapshots are both append-only and never overwrite each other; a missing cost category is stored as null ("not costed"), distinct from an intentional 0.0000. Each category accepts exactly one quantity unit (filament g, packaging unit, shipping parcel) so a mistyped unit is rejected rather than silently mis-costed. Snapshot currency must match the project (no implied FX). Shipping quantities are parcels; units_per_parcel allocates that parcel cost across the saleable batch yield (batch_units / units_per_parcel * parcel cost) independently of perUnit(). Actual shipping needs a confirmed shipment or invoice reference. The form starts with separate filament, packaging and shipping lines and can add more materials/components; Fusion project/document/version fields on a revision are optional manual provenance only.
  • Stage can only move to Live once every non-detached listing mapping has an active release (not merely one of several); moving a project's internal stage never calls the Etsy API or publishes anything. Attaching a new, not-yet-released listing mapping to a project already at Live drops its stage back to Design rather than leaving Live claiming coverage it no longer has.

Branding

The sidebar/login logo, favicon and in-app colour tokens use the supplied HL3D brand assets under public/branding/ (carbon #111316, alloy #A7ADB5, fluorescent yellow #E7FF00, ice #F4F6F8, slate #515963). The CSS font stack (resources/css/app.css) names "Avenir Next" first and falls back to the local system sans/monospace stack; no font files are bundled or fetched from Google/Bunny Fonts, and the wordmark is supplied as vector outlines so no font is needed to render the logo itself. Follow-up: no redistributable Avenir Next web-font licence has been secured — if a licensed web font becomes available, add it as a self-hosted asset (no external font CDN calls) and keep the system-stack fallback.

Backups

A restorable backup needs both of the following, taken together (the private files reference projects/revisions by ID, so restoring one without the other breaks file bundles):

  1. The database (mysqldump in production; database/database.sqlite only exists for local/dev use).
  2. The private file store at storage/app/project-files/ (dedicated, non-public disk for uploaded STL/3MF/G-code bytes — distinct from storage/app/public).

Verify a restore against an isolated copy before trusting it, and spot-check a project_files.sha256 value against the restored bytes on disk.

Verification

php artisan test --compact
npm run build

The test suite uses an in-memory SQLite database. MySQL and the external services need environment-specific smoke checks with your credentials and devices.