Add web frontend with JWT auth, RBAC, SSE dashboard, and config editor
- FastAPI + htmx + Jinja2 web frontend, started with --web flag - JWT HS256 auth (WEB_SECRET_KEY) with httpOnly cookies; access (15 min) + refresh (7 day) tokens; refresh rotation + JTI revocation in data/web.db - RBAC: superadmin > admin > reader enforced per route - Live SSE dashboard fed by tui/events broadcast queue - Config editor: keyword groups and channel list saved to data/runtime_config.json and hot-reloaded in-process (scorer.reload_from_config, signal_channel_changed) - config.py migrated to load groups/channels from runtime_config.json; falls back to hardcoded defaults when file absent - tui/events.py: subscribe/unsubscribe broadcast, set_bot_context/signal_channel_changed - utils/scorer.py: import config as _config (fixes local binding); reload_from_config() - utils/database.py: count_by_severity, recent_for_domains, count_by_severity_for_domains - 53 new tests (events bus, JWT lifecycle, web DB CRUD, RBAC enforcement, config round-trip); total 141 passing
This commit is contained in:
56
main.py
56
main.py
@@ -2,11 +2,10 @@
|
||||
main.py — Entry point for the ULP credential monitor.
|
||||
|
||||
Usage:
|
||||
python main.py # TUI mode (default, requires textual)
|
||||
python main.py --no-tui # Plain CLI mode
|
||||
|
||||
First run will prompt for your Telegram phone number and 2FA code
|
||||
to create a session file. Subsequent runs are fully automatic.
|
||||
python main.py # TUI mode (default)
|
||||
python main.py --no-tui # Plain CLI mode
|
||||
python main.py --web # TUI + web frontend (port 8080)
|
||||
python main.py --no-tui --web # CLI + web frontend
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -14,6 +13,7 @@ import logging
|
||||
import sys
|
||||
import shutil
|
||||
import argparse
|
||||
import threading
|
||||
|
||||
import config
|
||||
from utils.database import init_db
|
||||
@@ -36,6 +36,22 @@ log = logging.getLogger(__name__)
|
||||
init_db()
|
||||
|
||||
|
||||
# ─── Web thread ───────────────────────────────────────────────────────────────
|
||||
|
||||
def _run_web(host: str, port: int) -> None:
|
||||
"""Start uvicorn in its own thread with its own asyncio loop."""
|
||||
import uvicorn
|
||||
from web.app import create_app
|
||||
uvicorn.run(create_app(), host=host, port=port, log_level="warning")
|
||||
|
||||
|
||||
def _start_web_thread(host: str, port: int) -> threading.Thread:
|
||||
t = threading.Thread(target=_run_web, args=(host, port), daemon=True, name="web")
|
||||
t.start()
|
||||
log.info(f"Web frontend started at http://{host}:{port}")
|
||||
return t
|
||||
|
||||
|
||||
# ─── Plain CLI mode ───────────────────────────────────────────────────────────
|
||||
|
||||
async def _cli_main():
|
||||
@@ -96,24 +112,29 @@ async def _cli_main():
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="ULP Credential Monitor")
|
||||
parser.add_argument(
|
||||
"--no-tui",
|
||||
action="store_true",
|
||||
help="Run in plain CLI mode (no Textual TUI)",
|
||||
)
|
||||
parser.add_argument("--no-tui", action="store_true", help="Run in plain CLI mode (no Textual TUI)")
|
||||
parser.add_argument("--web", action="store_true", help="Start web frontend")
|
||||
parser.add_argument("--web-host", default="127.0.0.1", help="Web frontend bind host (default: 127.0.0.1)")
|
||||
parser.add_argument("--web-port", type=int, default=8080, help="Web frontend port (default: 8080)")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.web:
|
||||
_start_web_thread(args.web_host, args.web_port)
|
||||
|
||||
def _cleanup():
|
||||
log.info("Cleaning up tmp/...")
|
||||
if config.TEMP_DIR.exists():
|
||||
shutil.rmtree(config.TEMP_DIR, ignore_errors=True)
|
||||
config.TEMP_DIR.mkdir()
|
||||
log.info("Done.")
|
||||
|
||||
if args.no_tui:
|
||||
try:
|
||||
asyncio.run(_cli_main())
|
||||
except KeyboardInterrupt:
|
||||
log.info("Interrupted by user.")
|
||||
finally:
|
||||
log.info("Cleaning up tmp/...")
|
||||
if config.TEMP_DIR.exists():
|
||||
shutil.rmtree(config.TEMP_DIR, ignore_errors=True)
|
||||
config.TEMP_DIR.mkdir()
|
||||
log.info("Done.")
|
||||
_cleanup()
|
||||
else:
|
||||
try:
|
||||
from tui.app import run_tui
|
||||
@@ -132,10 +153,7 @@ def main():
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
log.info("Cleaning up tmp/...")
|
||||
if config.TEMP_DIR.exists():
|
||||
shutil.rmtree(config.TEMP_DIR, ignore_errors=True)
|
||||
config.TEMP_DIR.mkdir()
|
||||
_cleanup()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user