Files
kubernetes/argos/configmaps/configmap-panel.yaml
2025-08-20 01:16:53 +02:00

141 lines
5.0 KiB
YAML

apiVersion: v1
kind: ConfigMap
metadata:
name: argos-panel-config
namespace: argos-core
data:
app.py: |
import os
from fastapi import FastAPI, HTTPException, Query
from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
from minio import Minio
from urllib.parse import quote
from datetime import timezone
import io
BUCKET = os.getenv("MINIO_BUCKET", "argos")
endpoint = os.getenv("MINIO_ENDPOINT", "minio.argos-core.svc.cluster.local:9000")
access_key = os.getenv("MINIO_ACCESS_KEY")
secret_key = os.getenv("MINIO_SECRET_KEY")
secure = os.getenv("MINIO_SECURE", "false").lower() == "true"
mc = Minio(endpoint, access_key=access_key, secret_key=secret_key, secure=secure)
app = FastAPI()
@app.get("/health")
def health():
# simple check contra MinIO
try:
mc.list_buckets()
return {"ok": True}
except Exception as e:
return {"ok": False, "error": str(e)}
def _iter_events(prefix: str | None, limit: int):
# list_objects devuelve en orden “no garantizado”; recolectamos y ordenamos por last_modified desc
objs = []
for obj in mc.list_objects(BUCKET, prefix=prefix, recursive=True):
# omitimos directorios “lógicos” y thumbs por defecto en el listado principal
if obj.is_dir:
continue
if "/thumbs/" in obj.object_name:
continue
objs.append(obj)
objs.sort(key=lambda o: o.last_modified or 0, reverse=True)
return objs[:limit]
@app.get("/api/events")
def api_events(prefix: str | None = Query(default=None), limit: int = 50):
data = []
for o in _iter_events(prefix, limit):
ts = None
if o.last_modified:
ts = o.last_modified.astimezone(timezone.utc).isoformat()
# si existe thumb, la inferimos (cam/... → cam/thumbs/...)
thumb = None
parts = o.object_name.split("/")
if len(parts) >= 2:
maybe = f"{parts[0]}/thumbs/" + parts[-1].rsplit(".",1)[0] + ".jpg"
try:
mc.stat_object(BUCKET, maybe)
thumb = maybe
except:
pass
data.append({
"key": o.object_name,
"size": o.size,
"last_modified": ts,
"thumb": thumb
})
return JSONResponse(data)
@app.get("/file")
def file(key: str):
try:
resp = mc.get_object(BUCKET, key)
except Exception as e:
raise HTTPException(status_code=404, detail=f"not found: {e}")
# stream al cliente sin exponer MinIO
def gen():
for d in resp.stream(32*1024):
yield d
# tipo de contenido básico por extensión
ctype = "application/octet-stream"
k = key.lower()
if k.endswith(".mp4"): ctype = "video/mp4"
elif k.endswith(".jpg") or k.endswith(".jpeg"): ctype = "image/jpeg"
return StreamingResponse(gen(), media_type=ctype)
@app.get("/view", response_class=HTMLResponse)
def view(key: str):
keyq = quote(key)
return f"""
<!doctype html>
<html><head><meta charset="utf-8"><title>Ver {key}</title>
<style>body{{font-family:sans-serif;padding:16px}} video{{max-width:100%;height:auto}}</style>
</head><body>
<h2>{key}</h2>
<video controls src="/file?key={keyq}"></video>
<p><a href="/file?key={keyq}" download>Descargar</a></p>
<p><a href="/">Volver</a></p>
</body></html>
"""
@app.get("/", response_class=HTMLResponse)
def home(prefix: str | None = None, limit: int = 20):
# HTML mínimo que consume /api/events y pinta thumbs/links
p = f"&prefix={quote(prefix)}" if prefix else ""
items = []
for e in _iter_events(prefix, limit):
thumb_html = ""
# intentar thumb
parts = e.object_name.split("/")
if len(parts) >= 2:
maybe = f"{parts[0]}/thumbs/" + parts[-1].rsplit(".",1)[0] + ".jpg"
try:
mc.stat_object(BUCKET, maybe)
thumb_html = f'<img src="/file?key={quote(maybe)}" style="height:80px;border-radius:6px;margin-right:8px" />'
except:
pass
items.append(f"""
<li style="margin:8px 0;list-style:none;display:flex;align-items:center">
{thumb_html}
<a href="/view?key={quote(e.object_name)}">{e.object_name}</a>
</li>
""")
items_html = "\n".join(items) or "<p><i>Sin eventos aún.</i></p>"
return f"""
<!doctype html><html><head><meta charset="utf-8"><title>Argos Panel</title>
<style>body{{font-family:sans-serif;max-width:1000px;margin:24px auto;padding:0 16px}}</style>
</head><body>
<h1>Argos Panel</h1>
<form>
<label>Prefijo (cámara): <input type="text" name="prefix" value="{prefix or ""}"></label>
<label style="margin-left:12px">Límite: <input type="number" name="limit" value="{limit}" min="1" max="200"></label>
<button type="submit">Filtrar</button>
</form>
<ul style="padding:0">{items_html}</ul>
</body></html>
"""