Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 35 additions & 27 deletions GUI/src/vast/dashboard_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

import json, time, pathlib, base64, requests
from urllib.parse import quote
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
Comment on lines +11 to +14
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: duplicate imports already present on lines 2-9

Suggested change
import json, time, pathlib, base64, requests
from urllib.parse import quote
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry


# ---------- CONFIG ----------
DB_API_BASE = "http://host.docker.internal:8001"
DB_API_BASE = "http://db_api_service:8001"
DB_API_AUTH_MODE = "service"
DB_API_TOKEN_FILE = "/app/secrets/db_api_token"
DB_API_TOKEN = "auto"
DB_API_SERVICE_NAME = "GUI_H"


# ---------- TOKEN BOOTSTRAP ----------
def _safe_join_url(base: str, path: str) -> str:
return f"{base.rstrip('/')}/{path.lstrip('/')}"

Expand All @@ -44,64 +45,71 @@ def _fetch_token_via_dev_bootstrap(base: str, retries: int = 3, backoff: float =
time.sleep(backoff * attempt)
return None


def get_or_bootstrap_token() -> str | None:
print(f"[DEBUG] Checking for existing token file at: {DB_API_TOKEN_FILE}", flush=True)

if DB_API_TOKEN and DB_API_TOKEN.lower() != "auto":
print(f"[DEBUG] Using static token from config", flush=True)
return DB_API_TOKEN

token = _read_token_from_file(DB_API_TOKEN_FILE)
if token:
print(f"[DEBUG] Loaded token from {DB_API_TOKEN_FILE}", flush=True)
return token

print(f"[DEBUG] No existing token found, bootstrapping via {DB_API_BASE}/auth/_dev_bootstrap", flush=True)
token = _fetch_token_via_dev_bootstrap(DB_API_BASE)
if token:
pathlib.Path(DB_API_TOKEN_FILE).parent.mkdir(parents=True, exist_ok=True)
pathlib.Path(DB_API_TOKEN_FILE).write_text(token, encoding="utf-8")
print(f"[BOOTSTRAP] wrote token to {DB_API_TOKEN_FILE}", flush=True)
return token

print("[BOOTSTRAP][ERROR] Failed to obtain token.", flush=True)
return None




# ---------- API CLIENT ----------
class DashboardApi:
def __init__(self):
self.base = DB_API_BASE.rstrip("/")
self.http = requests.Session()
token = get_or_bootstrap_token()
self.token = token
if token:
if DB_API_AUTH_MODE == "service":
self.http.headers.update({"X-Service-Token": token})
else:
self.http.headers.update({"Authorization": f"Bearer {token}"})
self.http.headers.update({"Content-Type": "application/json"})
self.http.mount("http://", HTTPAdapter(max_retries=Retry(total=5, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504])))
self.http.mount("http://", HTTPAdapter(max_retries=Retry(total=5, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504])))
self.http.mount("https://", HTTPAdapter(max_retries=Retry(total=5, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504])))

# ---------- METHODS ----------
self.token_type = "service" if DB_API_AUTH_MODE == "service" else "bearer"

def list_devices(self, model: str | None = None) -> list[dict]:

url = f"{self.base}/api/devices"
url = f"{self.base}/api/tables/devices"
if model:
url += f"?model={model}"
try:
r = self.http.get(url, timeout=10)
if r.status_code == 200:
return r.json()
print(f"[API ERROR] {r.status_code}: {r.text[:100]}")
except Exception as e:
print(f"[API FAIL] {e}")
return []

def get_token_info(self) -> dict:
t = self.token
if not t:
return {"type": self.token_type, "status": "missing"}
if "." in t:
try:
payload_b64 = t.split(".")[1]
padded = payload_b64 + "=" * (-len(payload_b64) % 4)
data = json.loads(base64.urlsafe_b64decode(padded))
exp = data.get("exp")
secs_left = exp - int(time.time()) if exp else None
return {"type": "jwt", "exp": exp, "secs_left": secs_left, "payload": data}
except Exception:
pass
return {"type": self.token_type, "token_length": len(t)}

def refresh_token(self):
new_token = _fetch_token_via_dev_bootstrap(self.base)
if new_token:
pathlib.Path(DB_API_TOKEN_FILE).write_text(new_token, encoding="utf-8")
self.token = new_token
self.http.headers.update({"X-Service-Token": new_token})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: only updates X-Service-Token header but doesn't handle bearer mode - tokens won't refresh properly when DB_API_AUTH_MODE is not "service"

Suggested change
self.http.headers.update({"X-Service-Token": new_token})
if DB_API_AUTH_MODE == "service":
self.http.headers.update({"X-Service-Token": new_token})
else:
self.http.headers.update({"Authorization": f"Bearer {new_token}"})

return True
return False
# ---------- THRESHOLDS ----------
def bulk_set_task_thresholds_labeled(
self,
Expand All @@ -123,7 +131,7 @@ def bulk_set_task_thresholds_labeled(
r = self.http.post(url, json=items, timeout=20)
if r.status_code in (200, 201):
data = r.json()
# ודאי שמבנה ok/fail תואם

return {
"ok": list(data.get("ok", [])),
"fail": list(data.get("fail", [])),
Expand All @@ -133,4 +141,4 @@ def bulk_set_task_thresholds_labeled(
"fail": [[ [i.get("task"), i.get("label","")], f"http-{r.status_code} {r.text[:200]}"] for i in items],
}
except Exception as e:
return {"ok": [], "fail": [[ [i.get("task"), i.get("label","")], str(e)] for i in items]}
return {"ok": [], "fail": [[ [i.get("task"), i.get("label","")], str(e)] for i in items]}