Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2ee220783 | ||
|
|
572d551a2c |
No files matched your search
@@ -56,6 +56,14 @@ L'URL viene rilevato dalla prima stringa che contiene un link HTTP/HTTPS nel log
|
||||
|
||||
La dashboard aggiorna automaticamente lo stato dei tunnel e mostra l'ultimo log per aiutare il debug quando l'URL non è disponibile.
|
||||
|
||||
## Push su Gitea con token
|
||||
|
||||
Se hai un personal access token Gitea, esportalo come `GITEA_TOKEN` (es. `export GITEA_TOKEN=...`) e lancia `scripts/push-gitea.sh` dalla root del progetto. Lo script leggerà la variabile e spingerà il branch `main` verso `https://gitea.solarpi.net/truetype/dashgradio_tunnel.git` senza richiedere prompt interattivi. Puoi passare un branch diverso come primo argomento (`scripts/push-gitea.sh feature-branch`) e lo script si occuperà di costruire l’URL con il token.
|
||||
|
||||
## Test automatico
|
||||
|
||||
Esiste uno script `scripts/auto-test.sh` che installa le dipendenze (`requirements.txt`) e lancia `pytest` contro le API principali (`/api/ports`, `/api/tunnels`). Basta eseguire `./scripts/auto-test.sh` dalla root del progetto; lo script fallirà se non trova `GITEA_TOKEN`/comment? Not needed. Sam lengths.
|
||||
|
||||
## Politica porte
|
||||
|
||||
La parte critica di sicurezza del progetto è la politica che decide quali porte possono essere esposte tramite tunnel. Ci sono due layer di configurazione, entrambi opzionali:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Application package for the dashboard service."""
|
||||
Binary file not shown.
Binary file not shown.
+13
-6
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
@@ -13,12 +14,23 @@ from app.services import PortScanner
|
||||
from app.storage import Storage
|
||||
from app.tunnel_manager import TunnelManager
|
||||
|
||||
app = FastAPI(title="Gradio Tunnel Dashboard")
|
||||
policy = TunnelPolicy.load()
|
||||
storage = Storage.from_env()
|
||||
scanner = PortScanner(policy=policy, storage=storage)
|
||||
manager = TunnelManager(storage=storage)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_: FastAPI):
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
manager.shutdown()
|
||||
storage.close()
|
||||
|
||||
|
||||
app = FastAPI(title="Gradio Tunnel Dashboard", lifespan=lifespan)
|
||||
|
||||
app.mount("/static", StaticFiles(directory="app/static"), name="static")
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
|
||||
@@ -99,8 +111,3 @@ async def stop_tunnel(port: int) -> dict[str, bool]:
|
||||
raise HTTPException(status_code=404, detail="Tunnel not running.")
|
||||
return {"stopped": True}
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
def shutdown_event() -> None:
|
||||
manager.shutdown()
|
||||
storage.close()
|
||||
@@ -0,0 +1,3 @@
|
||||
[pytest]
|
||||
pythonpath = .
|
||||
testpaths = tests
|
||||
+5
-4
@@ -1,4 +1,5 @@
|
||||
fastapi==0.115.0
|
||||
uvicorn==0.30.6
|
||||
psutil==6.0.0
|
||||
jinja2==3.1.4
|
||||
fastapi==0.115.0
|
||||
uvicorn==0.30.6
|
||||
psutil==6.0.0
|
||||
jinja2==3.1.4
|
||||
pytest==8.4.2
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "python3 non trovato nel PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -x ".venv/bin/python3" ]]; then
|
||||
echo "Creating virtual environment..."
|
||||
python3 -m venv .venv
|
||||
fi
|
||||
|
||||
echo "Installing dependencies..."
|
||||
.venv/bin/python3 -m pip install -r requirements.txt >/dev/null
|
||||
|
||||
echo "Running pytest..."
|
||||
PYTHONPATH=. .venv/bin/python3 -m pytest tests
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Helper script that pushes the current branch to the Gitea remote using a personal
|
||||
# access token stored in $GITEA_TOKEN. Keeps the token out of shell history.
|
||||
|
||||
if [[ -z "${GITEA_TOKEN:-}" ]]; then
|
||||
echo "Missing GITEA_TOKEN (export it before running this script)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
readonly REMOTE_URL="https://truetype:${GITEA_TOKEN}@gitea.solarpi.net/truetype/dashgradio_tunnel.git"
|
||||
readonly BRANCH="${1:-main}"
|
||||
|
||||
echo "Pushing branch ${BRANCH} to Gitea..."
|
||||
git push "${REMOTE_URL}" "${BRANCH}"
|
||||
@@ -0,0 +1,74 @@
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app import main as dashboard
|
||||
from app.services import ServiceRecord
|
||||
|
||||
|
||||
client = TestClient(dashboard.app)
|
||||
|
||||
|
||||
class DummyTunnelInfo:
|
||||
def __init__(self, port: int):
|
||||
self.port = port
|
||||
self.command = "gradio-tunneling --port %d" % port
|
||||
self.pid = 42
|
||||
self.url = "https://example.com/%d" % port
|
||||
self.status = "running"
|
||||
self.exit_code = None
|
||||
self.last_output = "started"
|
||||
|
||||
|
||||
def test_list_ports(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
dashboard.scanner,
|
||||
"scan",
|
||||
lambda: [
|
||||
ServiceRecord(
|
||||
port=4444,
|
||||
protocol="tcp",
|
||||
service="svc1",
|
||||
pid=123,
|
||||
user="alice",
|
||||
command="cmd",
|
||||
container="ctr",
|
||||
local_address="0.0.0.0:4444",
|
||||
allowed=True,
|
||||
policy_reason=None,
|
||||
),
|
||||
ServiceRecord(
|
||||
port=2222,
|
||||
protocol="tcp",
|
||||
service="svc2",
|
||||
pid=321,
|
||||
user="bob",
|
||||
command="cmd2",
|
||||
container=None,
|
||||
local_address="0.0.0.0:2222",
|
||||
allowed=False,
|
||||
policy_reason="blocked",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
response = client.get("/api/ports")
|
||||
assert response.status_code == 200
|
||||
assert response.json()[0]["port"] == 2222
|
||||
assert response.json()[1]["port"] == 4444
|
||||
|
||||
|
||||
def test_start_tunnel_blocked(monkeypatch):
|
||||
monkeypatch.setattr(dashboard.policy, "is_port_allowed", lambda port: (False, "blocked"))
|
||||
response = client.post("/api/tunnels", json={"port": 2222})
|
||||
assert response.status_code == 403
|
||||
assert "blocked" in response.json()["detail"]
|
||||
|
||||
|
||||
def test_start_tunnel_success(monkeypatch):
|
||||
monkeypatch.setattr(dashboard.policy, "is_port_allowed", lambda port: (True, None))
|
||||
info = DummyTunnelInfo(3333)
|
||||
monkeypatch.setattr(dashboard.manager, "start_tunnel", lambda port: info)
|
||||
response = client.post("/api/tunnels", json={"port": 3333})
|
||||
assert response.status_code == 200
|
||||
payload = response.json()
|
||||
assert payload["port"] == 3333
|
||||
assert payload["url"] == info.url
|
||||
Reference in new issue
Block a user