Compare commits

...

3 commits

Author SHA1 Message Date
f4b3a09151 Merge pull request 'dev-v0.7.x -> main' (#22) from dev-v0.7.x into main
All checks were successful
Build and Publish Docker Image (Semantic Cache) / build (amd64, linux/amd64, docker-amd64) (push) Successful in 37s
Build and Publish Docker Image / build (amd64, linux/amd64, docker-amd64) (push) Successful in 36s
Build and Publish Docker Image (Semantic Cache) / build (arm64, linux/arm64, docker-arm64) (push) Successful in 10m10s
Build and Publish Docker Image (Semantic Cache) / merge (push) Successful in 33s
Build and Publish Docker Image / build (arm64, linux/arm64, docker-arm64) (push) Successful in 10m2s
Build and Publish Docker Image / merge (push) Successful in 33s
Reviewed-on: https://bitfreedom.net/code/code/nomyo-ai/nomyo-router/pulls/22
2026-04-13 14:00:20 +02:00
1058f2418b
fix: security, exempt files to prevent path traversal 2026-04-10 17:40:44 +02:00
263c66aedd
feat: add hostname to dashboard 2026-04-10 17:29:43 +02:00
2 changed files with 21 additions and 3 deletions

View file

@ -6,7 +6,7 @@ version: 0.7
license: AGPL license: AGPL
""" """
# ------------------------------------------------------------- # -------------------------------------------------------------
import orjson, time, asyncio, yaml, ollama, openai, os, re, aiohttp, ssl, random, base64, io, enhance, secrets, math import orjson, time, asyncio, yaml, ollama, openai, os, re, aiohttp, ssl, random, base64, io, enhance, secrets, math, socket
try: try:
import truststore; truststore.inject_into_ssl() import truststore; truststore.inject_into_ssl()
except ImportError: except ImportError:
@ -373,7 +373,11 @@ async def enforce_router_api_key(request: Request, call_next):
return await call_next(request) return await call_next(request)
path = request.url.path path = request.url.path
if path.startswith("/static") or path in {"/", "/favicon.ico"}: # Allow static assets (CSS, JS, images, fonts) but NOT HTML pages,
# which would bypass auth by accessing /static/index.html directly.
_STATIC_ASSET_EXTS = {".css", ".js", ".ico", ".png", ".jpg", ".jpeg", ".svg", ".woff", ".woff2", ".ttf", ".map"}
is_static_asset = path.startswith("/static") and Path(path).suffix.lower() in _STATIC_ASSET_EXTS
if is_static_asset or path in {"/", "/favicon.ico"}:
return await call_next(request) return await call_next(request)
provided_key = _extract_router_api_key(request) provided_key = _extract_router_api_key(request)
@ -3776,7 +3780,15 @@ async def health_proxy(request: Request):
return JSONResponse(content=response_payload, status_code=http_status) return JSONResponse(content=response_payload, status_code=http_status)
# ------------------------------------------------------------- # -------------------------------------------------------------
# 27. SSE route for usage broadcasts # 27. Hostname endpoint
# -------------------------------------------------------------
@app.get("/api/hostname")
async def get_hostname():
"""Return the hostname of the machine running the router."""
return JSONResponse(content={"hostname": socket.gethostname()})
# -------------------------------------------------------------
# 28. SSE route for usage broadcasts
# ------------------------------------------------------------- # -------------------------------------------------------------
@app.get("/api/usage-stream") @app.get("/api/usage-stream")
async def usage_stream(request: Request): async def usage_stream(request: Request):

View file

@ -344,6 +344,7 @@
</div> </div>
<div class="header-row"> <div class="header-row">
<h1>Router Dashboard</h1> <h1>Router Dashboard</h1>
<span id="hostname" style="color:#777; font-size:0.85em;"></span>
<button id="total-tokens-btn">Stats Total</button> <button id="total-tokens-btn">Stats Total</button>
<span id="aggregation-status" class="loading" style="margin-left:8px;"></span> <span id="aggregation-status" class="loading" style="margin-left:8px;"></span>
</div> </div>
@ -1418,6 +1419,11 @@ function initStatsChart(timeSeriesData, endpointDistribution) {
</script> </script>
<script> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
authedFetch('/api/hostname').then(r => r.json()).then(data => {
const el = document.getElementById('hostname');
if (el && data.hostname) el.textContent = data.hostname;
}).catch(() => {});
const totalBtn = document.getElementById('total-tokens-btn'); const totalBtn = document.getElementById('total-tokens-btn');
if (totalBtn) { if (totalBtn) {
totalBtn.addEventListener('click', async () => { totalBtn.addEventListener('click', async () => {