portal: favoritter+visning, app-/cookie-/ops-admin, ⌘K-palette, ikoner, grupper
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,11 @@ MATOMO_SITE = os.environ.get("MATOMO_SITE", "2")
|
||||
IP_LABELS_LOCAL = DATA / "ip_labels.json"
|
||||
IP_LABELS_REMOTE = os.environ.get("IP_LABELS_REMOTE", "") # fx root@192.168.10.201:/opt/npmplus/custom_nginx/ip_labels.json
|
||||
SETTINGS_PATH = DATA / "settings.json"
|
||||
RP_BASE_URL = os.environ.get("RP_BASE_URL", "https://rp.weircon.dk").rstrip("/")
|
||||
RP_API_KEY = os.environ.get("RP_API_KEY", "")
|
||||
ASSETS_VERSION_URL = os.environ.get("ASSETS_VERSION_URL", "https://assets.weircon.dk/VERSION")
|
||||
GEOIP_DB = os.environ.get("GEOIP_DB", str(DATA / "dbip-country.mmdb"))
|
||||
_geoip_reader = None
|
||||
|
||||
app = FastAPI(title="Weircon Portal")
|
||||
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY, https_only=True, same_site="lax", max_age=60 * 60 * 12)
|
||||
@@ -60,6 +65,8 @@ ADMIN_MODULES = [
|
||||
"desc": "Hurtigt overblik fra Matomo."},
|
||||
{"key": "apps", "title": "App-beskrivelser & grupper", "icon": "🧩",
|
||||
"desc": "Rediger beskrivelse og gruppe på dine Authentik-apps."},
|
||||
{"key": "ops", "title": "Drift / egress", "icon": "🛰️",
|
||||
"desc": "Static-assets-version + random-proxy egress, rollover & inflight."},
|
||||
]
|
||||
|
||||
|
||||
@@ -187,6 +194,49 @@ async def patch_apps(rows):
|
||||
return n
|
||||
|
||||
|
||||
def geoip_country(ip):
|
||||
global _geoip_reader
|
||||
try:
|
||||
if _geoip_reader is None:
|
||||
import maxminddb
|
||||
_geoip_reader = maxminddb.open_database(GEOIP_DB)
|
||||
rec = _geoip_reader.get(ip) or {}
|
||||
c = rec.get("country") or {}
|
||||
iso = c.get("iso_code") or ""
|
||||
name = (c.get("names") or {}).get("en") or ""
|
||||
flag = "".join(chr(0x1F1E6 + ord(ch) - 65) for ch in iso) if len(iso) == 2 and iso.isalpha() else ""
|
||||
return {"iso": iso, "name": name, "flag": flag}
|
||||
except Exception:
|
||||
return {"iso": "", "name": "", "flag": ""}
|
||||
|
||||
|
||||
async def assets_version():
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=6) as c:
|
||||
r = await c.get(ASSETS_VERSION_URL)
|
||||
return r.text.strip()[:40]
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
async def rp_status():
|
||||
if not RP_API_KEY:
|
||||
return {"error": "ingen RP_API_KEY konfigureret"}
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=6) as c:
|
||||
r = await c.get(RP_BASE_URL + "/status", headers={"X-WEIRCON-RANDOM-IP": RP_API_KEY})
|
||||
d = r.json()
|
||||
for p in d.get("proxies", []):
|
||||
p["geo"] = geoip_country(p.get("egress_ip") or "")
|
||||
rot = d.get("rotation") or {}
|
||||
if rot.get("next_egress_ip"):
|
||||
rot["geo"] = geoip_country(rot["next_egress_ip"])
|
||||
d["inflight_total"] = sum(int(p.get("inflight") or 0) for p in d.get("proxies", []))
|
||||
return d
|
||||
except Exception as exc:
|
||||
return {"error": str(exc)[:200]}
|
||||
|
||||
|
||||
async def matomo_summary():
|
||||
if not MATOMO_TOKEN:
|
||||
return None
|
||||
@@ -332,6 +382,18 @@ async def admin_cookies_save(request: Request, cookie_banner: str = Form(""), co
|
||||
return RedirectResponse("/admin/cookies?saved=Gemt", status_code=303)
|
||||
|
||||
|
||||
@app.get("/admin/ops", response_class=HTMLResponse)
|
||||
async def admin_ops(request: Request):
|
||||
user = current_user(request)
|
||||
if not is_admin(user):
|
||||
raise HTTPException(403, "Kun for administratorer")
|
||||
version = await assets_version()
|
||||
rp = await rp_status()
|
||||
return templates.TemplateResponse(request, "admin_ops.html", {
|
||||
"request": request, "user": user, "is_admin": True, "version": version, "rp": rp,
|
||||
})
|
||||
|
||||
|
||||
@app.get("/admin/stats", response_class=HTMLResponse)
|
||||
async def admin_stats(request: Request):
|
||||
user = current_user(request)
|
||||
|
||||
Reference in New Issue
Block a user