From 9521e0876dfa7bd475b999e241cef8d4393bc5ed Mon Sep 17 00:00:00 2001 From: Brent Perteet Date: Mon, 2 Mar 2026 23:26:27 +0000 Subject: [PATCH] Add Docker/Compose setup and fix subpath routing for /toroid deployment - Add Dockerfile, .dockerignore, and docker-compose.yml to containerize the app - Add toroid.service systemd unit (uses docker compose) - Mount presets/ as a bind volume for persistence outside the container - Fix all fetch() calls in templates to use relative paths (no leading /) so they resolve correctly when served under /toroid/ via nginx proxy Co-Authored-By: Claude Sonnet 4.6 --- .dockerignore | 6 ++++++ Dockerfile | 12 ++++++++++++ docker-compose.yml | 8 ++++++++ templates/index.html | 18 +++++++++--------- templates/manual.html | 6 +++--- toroid.service | 14 ++++++++++++++ 6 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 toroid.service diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2f0a3db --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.venv/ +__pycache__/ +*.pyc +*.pyo +.git/ +*.bat diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..690824f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 5010 + +CMD ["python", "app.py"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2d6abc0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +services: + toroid: + build: . + ports: + - "5010:5010" + volumes: + - ./presets:/app/presets + restart: unless-stopped diff --git a/templates/index.html b/templates/index.html index dc67f01..e188fbe 100644 --- a/templates/index.html +++ b/templates/index.html @@ -772,7 +772,7 @@ async function runDesign() { try { const payload = buildPayload(); - const resp = await fetch('/api/design', { + const resp = await fetch('api/design', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload), @@ -901,7 +901,7 @@ async function runOperatingPoint() { constraints: collectConstraints(), }); - const resp = await fetch('/api/simulate', { + const resp = await fetch('api/simulate', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload), @@ -1001,7 +1001,7 @@ async function runSweep() { }, }); - const resp = await fetch('/api/sweep', { + const resp = await fetch('api/sweep', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload), @@ -1463,7 +1463,7 @@ function presetApply(ptype, data) { async function presetRefresh(ptype) { const sel = document.getElementById(`preset-sel-${ptype}`); if (!sel) return null; - const resp = await fetch(`/api/presets/${ptype}`); + const resp = await fetch(`api/presets/${ptype}`); const data = await resp.json(); if (!data.success) return null; sel.innerHTML = data.names.length === 0 @@ -1479,7 +1479,7 @@ async function presetLoad(ptype) { const sel = document.getElementById(`preset-sel-${ptype}`); const name = sel ? sel.value : ''; if (!name) return; - const resp = await fetch(`/api/presets/${ptype}/${encodeURIComponent(name)}`); + const resp = await fetch(`api/presets/${ptype}/${encodeURIComponent(name)}`); const data = await resp.json(); if (!data.success) { alert('Load failed: ' + data.error); return; } presetApply(ptype, data.data); @@ -1493,7 +1493,7 @@ async function presetSave(ptype) { const name = nameEl ? nameEl.value.trim() : ''; if (!name) { alert('Enter a preset name first.'); return; } const payload = presetExtract(ptype); - const resp = await fetch(`/api/presets/${ptype}/${encodeURIComponent(name)}`, { + const resp = await fetch(`api/presets/${ptype}/${encodeURIComponent(name)}`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload), @@ -1511,7 +1511,7 @@ async function presetDelete(ptype) { const name = sel ? sel.value : ''; if (!name || name === '(no saved presets)') return; if (!confirm(`Delete preset "${name}"?`)) return; - const resp = await fetch(`/api/presets/${ptype}/${encodeURIComponent(name)}`, { method: 'DELETE' }); + const resp = await fetch(`api/presets/${ptype}/${encodeURIComponent(name)}`, { method: 'DELETE' }); const data = await resp.json(); if (!data.success) { alert('Delete failed: ' + data.error); return; } await presetRefresh(ptype); @@ -1523,7 +1523,7 @@ async function initPresets() { const info = await presetRefresh(ptype); if (info && info.last) { // Auto-load last used - const resp = await fetch(`/api/presets/${ptype}/${encodeURIComponent(info.last)}`); + const resp = await fetch(`api/presets/${ptype}/${encodeURIComponent(info.last)}`); const d = await resp.json(); if (d.success) presetApply(ptype, d.data); } @@ -1536,7 +1536,7 @@ async function initPresets() { (async () => { // initPresets returns after applying last-used presets (if any) const windingsInfo = await (async () => { - const r = await fetch('/api/presets/windings'); + const r = await fetch('api/presets/windings'); const d = await r.json(); return d.success ? d : null; })(); diff --git a/templates/manual.html b/templates/manual.html index a736e1c..167afbb 100644 --- a/templates/manual.html +++ b/templates/manual.html @@ -266,7 +266,7 @@

🔧 Manual Simulation

- âš¡ Go to Optimizer + âš¡ Go to Optimizer

Directly specify tap settings and input voltage to simulate transformer performance

@@ -455,7 +455,7 @@ loading.classList.remove('hidden'); try { - const response = await fetch('/api/simulate', { + const response = await fetch('api/simulate', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -523,7 +523,7 @@ // Load transformer info on page load async function loadTransformerInfo() { try { - const response = await fetch('/api/transformer_info'); + const response = await fetch('api/transformer_info'); const data = await response.json(); // Update tap options based on actual transformer diff --git a/toroid.service b/toroid.service new file mode 100644 index 0000000..25c76a2 --- /dev/null +++ b/toroid.service @@ -0,0 +1,14 @@ +[Unit] +Description=Toroid Transformer Designer +After=docker.service +Requires=docker.service + +[Service] +Type=oneshot +RemainAfterExit=yes +WorkingDirectory=/home/brent/um-apps/toroid +ExecStart=/usr/bin/docker compose up -d +ExecStop=/usr/bin/docker compose down + +[Install] +WantedBy=multi-user.target