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 @@
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