Skip to content

Commit e35ddad

Browse files
fix: resolve Data Explorer connectivity and path robustness for web-hosted GUI
1 parent 2c2de10 commit e35ddad

3 files changed

Lines changed: 93 additions & 3 deletions

File tree

backend/main.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,27 @@ def health_check():
108108
return {"status": "ok"}
109109

110110

111+
@app.get("/api/config")
112+
def get_config():
113+
"""Return key configuration paths and settings for the frontend."""
114+
# Try to find the tutorial database
115+
assets_path = Path("assets")
116+
tutorial_db = assets_path / "tutorial_database.sqlite"
117+
if not tutorial_db.exists():
118+
# Fallback to hardcoded absolute path if running from specific env
119+
tutorial_db = Path(
120+
"/media/Secondary/Projects/TemoaProject/temoa-web-gui/assets/tutorial_database.sqlite"
121+
)
122+
123+
return {
124+
"tutorial_database": str(tutorial_db.absolute())
125+
if tutorial_db.exists()
126+
else None,
127+
"explorer_port": 8001,
128+
"api_port": 8000,
129+
}
130+
131+
111132
@app.get("/api/files")
112133
def list_files(path: str = "."):
113134
"""Helper to browse files for input database selection."""

frontend/src/App.jsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ const WS_BASE = "ws://localhost:8000";
77

88
function App() {
99
const [config, setConfig] = useState({
10-
input_database: "assets/tutorial_database.sqlite",
10+
input_database: "",
1111
scenario_mode: "perfect_foresight",
1212
solver_name: "appsi_highs",
1313
time_sequencing: "seasonal_timeslices",
14+
explorer_port: 8001,
1415
});
1516

1617
const [logs, setLogs] = useState([]);
@@ -45,6 +46,7 @@ function App() {
4546
if (resp.ok) {
4647
setBackendStatus("connected");
4748
fetchSolvers();
49+
fetchConfig();
4850
} else {
4951
setBackendStatus("error");
5052
}
@@ -53,6 +55,28 @@ function App() {
5355
}
5456
};
5557

58+
const fetchConfig = async () => {
59+
try {
60+
const resp = await fetch(`${API_BASE}/api/config`);
61+
if (resp.ok) {
62+
const data = await resp.json();
63+
// If we don't have a path yet or it's the default, update it
64+
if (
65+
data.tutorial_database &&
66+
(!config.input_database || config.input_database.includes("tutorial"))
67+
) {
68+
setConfig((prev) => ({
69+
...prev,
70+
input_database: data.tutorial_database,
71+
explorer_port: data.explorer_port || 8001,
72+
}));
73+
}
74+
}
75+
} catch (e) {
76+
console.error("Failed to fetch config", e);
77+
}
78+
};
79+
5680
const fetchSolvers = async () => {
5781
try {
5882
const resp = await fetch(`${API_BASE}/api/solvers`);
@@ -217,12 +241,20 @@ function App() {
217241
<div className="step-number">3</div>
218242
<div className="step-content">
219243
<strong>Run Backend:</strong>
220-
<code>uv run https://temoaproject.org/temoa_runner.py</code>
244+
<code>
245+
uv run
246+
https://raw.githubusercontent.com/TemoaProject/temoa-web-gui/refs/heads/main/temoa_runner.py
247+
</code>
221248
</div>
222249
</div>
223250
</div>
224251
<div className="setup-note">
225252
<p>Ensure the local backend is running before starting a simulation.</p>
253+
<p style={{ marginTop: "10px", color: "#b11a1a" }}>
254+
<strong>Heads up:</strong> If this GUI is hosted on HTTPS (e.g. Cloudflare) but your
255+
backend is HTTP (localhost), you may need to "allow insecure content" in your browser
256+
settings for this site to let the logs and explorer load.
257+
</p>
226258
</div>
227259
</div>
228260
);
@@ -415,7 +447,7 @@ function App() {
415447
{activeTab === "explorer" && (
416448
<div className="card" style={{ padding: 0, overflow: "hidden" }}>
417449
<iframe
418-
src="http://localhost:8001"
450+
src={`http://${new URL(API_BASE).hostname}:${config.explorer_port || 8001}`}
419451
width="100%"
420452
height="100%"
421453
title="Datasette Explorer"

temoa_runner.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# "uvicorn[standard]",
77
# "tomlkit",
88
# "websockets",
9+
# "datasette",
910
# ]
1011
# ///
1112

@@ -108,6 +109,20 @@ def health_check():
108109
return {"status": "ok"}
109110

110111

112+
@app.get("/api/config")
113+
def get_config():
114+
"""Return key configuration paths and settings for the frontend."""
115+
# In the runner, we assume assets are in the current directory or nearby
116+
tutorial_db = Path("assets/tutorial_database.sqlite")
117+
return {
118+
"tutorial_database": str(tutorial_db.absolute())
119+
if tutorial_db.exists()
120+
else None,
121+
"explorer_port": 8001,
122+
"api_port": 8000,
123+
}
124+
125+
111126
@app.get("/api/files")
112127
def list_files(path: str = "."):
113128
"""Helper to browse files for input database selection."""
@@ -344,5 +359,27 @@ async def websocket_endpoint(websocket: WebSocket):
344359

345360
if __name__ == "__main__":
346361
import uvicorn
362+
import subprocess
363+
364+
# Start Datasette in a subprocess
365+
print("Starting Datasette on port 8001...")
366+
try:
367+
subprocess.Popen(
368+
[
369+
"datasette",
370+
".",
371+
"--port",
372+
"8001",
373+
"--host",
374+
"0.0.0.0",
375+
"--setting",
376+
"sql_time_limit_ms",
377+
"5000",
378+
],
379+
stdout=subprocess.DEVNULL,
380+
stderr=subprocess.DEVNULL,
381+
)
382+
except Exception as e:
383+
print(f"Warning: Could not start Datasette: {e}")
347384

348385
uvicorn.run(app, host="0.0.0.0", port=8000)

0 commit comments

Comments
 (0)