mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-06-30 10:00:52 +00:00
Compare commits
2 Commits
1de8b29865
...
7c14bdfe5e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c14bdfe5e | ||
| 062131608f |
|
|
@ -1,3 +1,14 @@
|
|||
secubox-toolbox (2.6.33-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Shared broadcast pin in every banner (#578). An operator-set "📌 pin"
|
||||
(or the top-1 tracker of the day) is stored in /run/secubox/pin.json and
|
||||
shown as the first chip in EVERY R2/R3 client's banner (24 h window) —
|
||||
a shared signal around all banner injections. api: GET/POST /admin/pin
|
||||
+ GET /admin/pin/ui setter (with an "use top-1 tracker" auto-fill from
|
||||
the social aggregate). inject_banner reads + renders it.
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sun, 14 Jun 2026 15:15:00 +0200
|
||||
|
||||
secubox-toolbox (2.6.32-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Shared media proxy-cache (#577) — DEFAULT OFF (opt-in filter `media_cache`).
|
||||
|
|
|
|||
|
|
@ -452,6 +452,9 @@ def _banner_html_dynamic(sha1: str, ctx: dict, csp_strict: bool,
|
|||
# renders correctly regardless of page charset (some legacy pages declare
|
||||
# iso-8859-1 which would mangle our raw UTF-8 emoji bytes).
|
||||
right_parts = [f"{_ncr(ctx['status_icon'])} {ctx['status']}"]
|
||||
# #578 — shared broadcast pin first, so every banner shows it.
|
||||
if ctx.get("pin"):
|
||||
right_parts.insert(0, "📌 " + _ncr(ctx["pin"])) # 📌
|
||||
if ctx["flag"]:
|
||||
# Phase 6.M (#496) : flags are Unicode "regional indicator" pairs
|
||||
# (🇫🇷 = U+1F1EB + U+1F1F7). NCR-encoded pairs do NOT join into a
|
||||
|
|
@ -707,6 +710,18 @@ class InjectBanner:
|
|||
except Exception:
|
||||
ctx["ghost_blocked"] = 0
|
||||
ctx["ghost_kb"] = 0
|
||||
# #578 — shared broadcast pin (operator/top-1), shown in every
|
||||
# client's banner. Fresh window 24 h.
|
||||
ctx["pin"] = ""
|
||||
try:
|
||||
import json as _json
|
||||
import time as _time
|
||||
with open("/run/secubox/pin.json", "r", encoding="utf-8") as _pf:
|
||||
_p = _json.load(_pf)
|
||||
if _p.get("text") and (_time.time() - _p.get("ts", 0)) < 86400:
|
||||
ctx["pin"] = str(_p["text"])[:80]
|
||||
except Exception:
|
||||
pass
|
||||
csp_strict = _detect_csp_strict(flow)
|
||||
report_url = _report_url_for(flow)
|
||||
level_label = _level_label(flow)
|
||||
|
|
|
|||
|
|
@ -2481,6 +2481,80 @@ async def admin_cache() -> dict:
|
|||
return out
|
||||
|
||||
|
||||
_PIN_PATH = "/run/secubox/pin.json"
|
||||
|
||||
|
||||
@router.get("/admin/pin")
|
||||
async def admin_pin() -> dict:
|
||||
"""#578 — the shared broadcast pin shown in every client's banner."""
|
||||
import json as _json
|
||||
from pathlib import Path as _P
|
||||
cur = {"text": "", "url": "", "ts": 0, "by": ""}
|
||||
try:
|
||||
p = _P(_PIN_PATH)
|
||||
if p.exists():
|
||||
cur.update(_json.loads(p.read_text()))
|
||||
except Exception:
|
||||
pass
|
||||
return cur
|
||||
|
||||
|
||||
@router.post("/admin/pin")
|
||||
async def admin_pin_set(request: Request) -> dict:
|
||||
"""#578 — set/clear the shared pin (broadcast to all banners). Empty
|
||||
text clears it."""
|
||||
import json as _json
|
||||
import time as _time
|
||||
from pathlib import Path as _P
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception:
|
||||
body = {}
|
||||
text = (str(body.get("text", "")) if isinstance(body, dict) else "")[:80].strip()
|
||||
url = (str(body.get("url", "")) if isinstance(body, dict) else "")[:300].strip()
|
||||
rec = {"text": text, "url": url, "ts": int(_time.time()) if text else 0, "by": "admin"}
|
||||
try:
|
||||
_P(_PIN_PATH).parent.mkdir(parents=True, exist_ok=True)
|
||||
_P(_PIN_PATH).write_text(_json.dumps(rec))
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
return rec
|
||||
|
||||
|
||||
@router.get("/admin/pin/ui", response_class=HTMLResponse)
|
||||
async def admin_pin_ui() -> HTMLResponse:
|
||||
"""#578 — minimal pin setter; can auto-fill the current top-1 tracker."""
|
||||
html = """<!doctype html><html lang=fr><meta charset=utf-8>
|
||||
<meta name=viewport content="width=device-width,initial-scale=1">
|
||||
<title>Pin partagé — ToolBoX</title>
|
||||
<style>
|
||||
body{background:#0a0a0f;color:#e8e6d9;font:14px system-ui,sans-serif;max-width:520px;margin:30px auto;padding:0 18px}
|
||||
h1{color:#c9a84c;font-size:18px} label{display:block;color:#6b6b7a;font-size:12px;margin:12px 0 4px}
|
||||
input{width:100%;padding:8px;border-radius:6px;border:1px solid #333;background:#14141c;color:#e8e6d9}
|
||||
button{margin:14px 8px 0 0;padding:9px 14px;border-radius:6px;border:1px solid #c9a84c;background:#c9a84c;color:#0a0a0f;font-weight:700;cursor:pointer}
|
||||
button.alt{background:transparent;color:#00d4ff;border-color:#00d4ff}
|
||||
button.danger{background:transparent;color:#e63946;border-color:#e63946}
|
||||
#msg{color:#00ff41;min-height:18px;margin-top:10px} .muted{color:#6b6b7a;font-size:12px}
|
||||
</style>
|
||||
<h1>📌 Pin partagé (toutes les bannières)</h1>
|
||||
<p class=muted>Un message épinglé, diffusé dans la bannière de TOUS les clients R2/R3 (24 h).</p>
|
||||
<label>Texte du pin <input id=text maxlength=80 placeholder="ex: traceur #1 du jour — doubleclick.net"></label>
|
||||
<label>Lien (optionnel) <input id=url maxlength=300 placeholder="https://…"></label>
|
||||
<button id=save>📌 Épingler</button>
|
||||
<button class=alt id=top>⬆ Utiliser le traceur #1</button>
|
||||
<button class=danger id=clear>Retirer</button>
|
||||
<p id=msg></p>
|
||||
<script>
|
||||
const $=s=>document.querySelector(s);
|
||||
fetch('/admin/pin').then(r=>r.json()).then(p=>{$('#text').value=p.text||'';$('#url').value=p.url||'';});
|
||||
function post(t,u){return fetch('/admin/pin',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({text:t,url:u})}).then(r=>r.json()).then(()=>{$('#msg').textContent='✓ diffusé';setTimeout(()=>$('#msg').textContent='',1500);});}
|
||||
$('#save').onclick=()=>post($('#text').value,$('#url').value);
|
||||
$('#clear').onclick=()=>{$('#text').value='';$('#url').value='';post('','');};
|
||||
$('#top').onclick=()=>fetch('/admin/social-aggregate?hours=24').then(r=>r.json()).then(d=>{const t=(d.by_tracker_domain||[])[0];if(t){$('#text').value='Traceur #1 : '+t.tracker_domain+' ('+t.hits+' hits)';}else{$('#msg').textContent='aucun traceur';}});
|
||||
</script></html>"""
|
||||
return HTMLResponse(content=html)
|
||||
|
||||
|
||||
@router.get("/admin/media")
|
||||
async def admin_media() -> dict:
|
||||
"""#570 — DPI media/content-type statistics for the donut UI."""
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user