diff --git a/.gitignore b/.gitignore index ab309a3..c93bc46 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,5 @@ __pycache__/ *.log *.log.[0-9] /dist/ - -# Claude Code local files -.claude/ -scripts/ +# Scratch config created by scripts/run-dev-ui .devdata/ diff --git a/scripts/run-dev-ui.ps1 b/scripts/run-dev-ui.ps1 new file mode 100644 index 0000000..255b2d3 --- /dev/null +++ b/scripts/run-dev-ui.ps1 @@ -0,0 +1,76 @@ +# run-dev-ui.ps1 - start the multiACE web UI locally, against a real +# Moonraker if one is reachable and against tests\fixtures\*.json if not. +# +# .\scripts\run-dev-ui.ps1 +# $env:MOONRAKER_URL = "http://printer:7125"; .\scripts\run-dev-ui.ps1 +# $env:MULTIACE_MOCK_MODE = "1"; .\scripts\run-dev-ui.ps1 # force mock +# $env:MULTIACE_MOCK_STATE_FILE = "mock_state_head.json"; .\scripts\run-dev-ui.ps1 +# # dashboard in head mode instead of the default multi-ACE fixture +# +# Mock mode also unlocks POST /api/debug/simulate (inject a fake error +# notification) and GET /api/debug/sample-gcode (a small 4-filament +# sample print, tests\fixtures\sample_4color.gcode). + +$ErrorActionPreference = "Stop" + +$Port = if ($env:PORT) { $env:PORT } else { "7126" } +$MoonrakerUrl = if ($env:MOONRAKER_URL) { $env:MOONRAKER_URL } else { "http://127.0.0.1:7125" } +$RepoRoot = Split-Path -Parent $PSScriptRoot +$BackendDir = Join-Path $RepoRoot "multiace\web\backend" + +if ($env:MULTIACE_MOCK_MODE) { + Write-Host "-> mock mode forced by MULTIACE_MOCK_MODE=$($env:MULTIACE_MOCK_MODE)" +} else { + try { + Invoke-WebRequest -Uri "$MoonrakerUrl/printer/info" -TimeoutSec 2 -UseBasicParsing | Out-Null + Write-Host "OK Moonraker found at $MoonrakerUrl - using real data" + $env:MULTIACE_MOCK_MODE = "0" + } catch { + Write-Host "-- Moonraker not reachable at $MoonrakerUrl - using mock data" + $env:MULTIACE_MOCK_MODE = "1" + } +} + +$env:MOONRAKER_URL = $MoonrakerUrl +if (-not $env:MULTIACE_MOCK_DIR) { + $env:MULTIACE_MOCK_DIR = Join-Path $RepoRoot "tests\fixtures" +} +# The installer copies the catalogs next to the backend (web\i18n); in a +# checkout they live at multiace\i18n. Without this every label in the UI +# renders as its raw key. +if (-not $env:MULTIACE_I18N_DIR) { + $env:MULTIACE_I18N_DIR = Join-Path $RepoRoot "multiace\i18n" +} +# A dev box has no /home/lava; point the config editor at a scratch copy +# so saving from the UI cannot 404 (and cannot touch a real printer). +if (-not $env:MULTIACE_CFG_PATH) { + $devCfg = Join-Path $RepoRoot ".devdata\ace.cfg" + $devDir = Split-Path -Parent $devCfg + if (-not (Test-Path $devDir)) { New-Item -ItemType Directory -Path $devDir | Out-Null } + if (-not (Test-Path $devCfg)) { + Copy-Item (Join-Path $RepoRoot "multiace\config\extended\ace.cfg") $devCfg + } + $env:MULTIACE_CFG_PATH = $devCfg +} +Write-Host " config: $($env:MULTIACE_CFG_PATH)" + +$Python = if ($env:PYTHON) { $env:PYTHON } else { "python" } +$Url = "http://127.0.0.1:$Port/" + +Start-Job -ScriptBlock { + param($u) + Start-Sleep -Seconds 2 + Start-Process $u +} -ArgumentList $Url | Out-Null + +if ($env:MULTIACE_MOCK_MODE -eq "1") { + Write-Host " mock: sample print at http://127.0.0.1:$Port/api/debug/sample-gcode" +} + +Write-Host "Dev server on $Url (Ctrl+C to stop)" +Push-Location $BackendDir +try { + & $Python -m uvicorn main:app --host 127.0.0.1 --port $Port --reload +} finally { + Pop-Location +} diff --git a/scripts/run-dev-ui.sh b/scripts/run-dev-ui.sh new file mode 100755 index 0000000..2376073 --- /dev/null +++ b/scripts/run-dev-ui.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# run-dev-ui.sh - start the multiACE web UI locally, against a real +# Moonraker if one is reachable and against tests/fixtures/*.json if not. +# +# ./scripts/run-dev-ui.sh # auto-detect +# MOONRAKER_URL=http://printer:7125 ./scripts/run-dev-ui.sh +# MULTIACE_MOCK_MODE=1 ./scripts/run-dev-ui.sh # force mock +# MULTIACE_MOCK_STATE_FILE=mock_state_head.json ./scripts/run-dev-ui.sh +# # dashboard in head mode instead of the default multi-ACE fixture +# +# Mock mode also unlocks POST /api/debug/simulate (inject a fake error +# notification) and GET /api/debug/sample-gcode (a small 4-filament +# sample print, tests/fixtures/sample_4color.gcode). +set -e + +PORT="${PORT:-7126}" +MOONRAKER_URL="${MOONRAKER_URL:-http://127.0.0.1:7125}" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BACKEND_DIR="$REPO_ROOT/multiace/web/backend" + +# Git Bash / MSYS hands POSIX paths ("/c/Users/…") to a NATIVE Windows +# Python, which reads them as drive-relative and silently finds nothing - +# every label in the UI then renders as its raw i18n key. Convert every +# path we export. No-op on Linux/macOS, where cygpath does not exist. +winpath() { + if command -v cygpath >/dev/null 2>&1; then cygpath -w "$1"; else printf '%s' "$1"; fi +} + +if [ -n "$MULTIACE_MOCK_MODE" ]; then + echo "-> mock mode forced by MULTIACE_MOCK_MODE=$MULTIACE_MOCK_MODE" +elif curl -fsS --max-time 2 "$MOONRAKER_URL/printer/info" >/dev/null 2>&1; then + echo "OK Moonraker found at $MOONRAKER_URL - using real data" + MULTIACE_MOCK_MODE=0 +else + echo "-- Moonraker not reachable at $MOONRAKER_URL - using mock data" + MULTIACE_MOCK_MODE=1 +fi + +export MULTIACE_MOCK_MODE +export MOONRAKER_URL +export MULTIACE_MOCK_DIR="${MULTIACE_MOCK_DIR:-$(winpath "$REPO_ROOT/tests/fixtures")}" +# The installer copies the catalogs next to the backend (web/i18n); in a +# checkout they live at multiace/i18n. Without this every label in the UI +# renders as its raw key. +export MULTIACE_I18N_DIR="${MULTIACE_I18N_DIR:-$(winpath "$REPO_ROOT/multiace/i18n")}" +# A dev box has no /home/lava; point the config editor at a scratch copy +# so saving from the UI cannot 404 (and cannot touch a real printer). +if [ -z "$MULTIACE_CFG_PATH" ]; then + MULTIACE_CFG_PATH="$REPO_ROOT/.devdata/ace.cfg" + mkdir -p "$(dirname "$MULTIACE_CFG_PATH")" + [ -f "$MULTIACE_CFG_PATH" ] || cp "$REPO_ROOT/multiace/config/extended/ace.cfg" "$MULTIACE_CFG_PATH" + MULTIACE_CFG_PATH="$(winpath "$MULTIACE_CFG_PATH")" + export MULTIACE_CFG_PATH +fi +echo " config: $MULTIACE_CFG_PATH" + +# Pick an interpreter that actually RUNS: a python3 that merely exists on +# PATH is not enough (a broken Windows Store / stale install shim is on +# most dev boxes, and it fails only once uvicorn is already "started"). +PY="" +for cand in "$PYTHON" python3 python py; do + [ -n "$cand" ] || continue + if "$cand" -c 'import sys' >/dev/null 2>&1; then PY="$cand"; break; fi +done +if [ -z "$PY" ]; then + echo "No working Python found - set PYTHON=/path/to/python" >&2 + exit 1 +fi + +URL="http://127.0.0.1:$PORT/" +( + sleep 2 + if command -v xdg-open >/dev/null 2>&1; then xdg-open "$URL" + elif command -v open >/dev/null 2>&1; then open "$URL" + fi +) & + +if [ "$MULTIACE_MOCK_MODE" = "1" ]; then + echo " mock: sample print at http://127.0.0.1:$PORT/api/debug/sample-gcode" +fi + +echo "Dev server on $URL (Ctrl+C to stop)" +cd "$BACKEND_DIR" +exec "$PY" -m uvicorn main:app --host 127.0.0.1 --port "$PORT" --reload