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 <noreply@anthropic.com>
This commit is contained in:
Brent Perteet
2026-03-02 23:26:27 +00:00
parent 8e51d36a96
commit 9521e0876d
6 changed files with 52 additions and 12 deletions

View File

@@ -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;
})();