164 lines
5.0 KiB
Python
164 lines
5.0 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import sqlite3
|
|
import time
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from urllib.error import URLError
|
|
from urllib.request import Request, urlopen
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DB_PATH = ROOT / ".data_local" / "mvp_cc.db"
|
|
OUT_DIR = ROOT / ".artifacts" / "track12-monitor"
|
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
OUT_PATH = OUT_DIR / "latest.log"
|
|
|
|
BASE_URL = "http://127.0.0.1:8080"
|
|
AUTH_BASE_URL = "http://127.0.0.1:8001"
|
|
USERNAME = "operator"
|
|
PASSWORD = "op12345"
|
|
ROLE = "operator"
|
|
WINDOW_SECONDS = 180
|
|
POLL_SECONDS = 1.0
|
|
|
|
|
|
def iso_now() -> str:
|
|
return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")
|
|
|
|
|
|
def write_line(handle, label: str, payload) -> None:
|
|
handle.write(f"[{iso_now()}] {label} {json.dumps(payload, ensure_ascii=False, sort_keys=True)}\n")
|
|
handle.flush()
|
|
|
|
|
|
def login_headers() -> dict[str, str]:
|
|
body = json.dumps(
|
|
{
|
|
"username": USERNAME,
|
|
"password": PASSWORD,
|
|
"role": ROLE,
|
|
}
|
|
).encode("utf-8")
|
|
req = Request(
|
|
f"{AUTH_BASE_URL}/auth/login",
|
|
headers={"Content-Type": "application/json"},
|
|
data=body,
|
|
method="POST",
|
|
)
|
|
with urlopen(req, timeout=10) as response:
|
|
payload = json.loads(response.read().decode("utf-8", errors="replace"))
|
|
token = str(payload.get("access_token") or "").strip()
|
|
if not token:
|
|
raise RuntimeError("auth/login did not return access_token")
|
|
return {"Authorization": f"Bearer {token}"}
|
|
|
|
|
|
def fetch_json(path: str, headers: dict[str, str]):
|
|
req = Request(
|
|
f"{BASE_URL}{path}",
|
|
headers=headers,
|
|
method="GET",
|
|
)
|
|
with urlopen(req, timeout=10) as response:
|
|
body = response.read().decode("utf-8", errors="replace")
|
|
return json.loads(body)
|
|
|
|
|
|
def latest_events(conn: sqlite3.Connection, limit: int = 6) -> list[dict]:
|
|
rows = conn.execute(
|
|
"""
|
|
select event_id, event_type, call_id, interaction_id, payload_json, created_at
|
|
from voice_events
|
|
order by id desc
|
|
limit ?
|
|
""",
|
|
(limit,),
|
|
).fetchall()
|
|
result = []
|
|
for row in rows:
|
|
payload = row["payload_json"]
|
|
try:
|
|
payload = json.loads(payload)
|
|
except Exception:
|
|
pass
|
|
result.append(
|
|
{
|
|
"event_id": row["event_id"],
|
|
"event_type": row["event_type"],
|
|
"call_id": row["call_id"],
|
|
"interaction_id": row["interaction_id"],
|
|
"payload": payload,
|
|
"created_at": row["created_at"],
|
|
}
|
|
)
|
|
return result
|
|
|
|
|
|
def latest_links(conn: sqlite3.Connection, limit: int = 4) -> list[dict]:
|
|
rows = conn.execute(
|
|
"""
|
|
select call_id, interaction_id, status, telephony_status, claimed_by_user,
|
|
operator_extension, started_at, connected_at, ended_at
|
|
from asterisk_call_links
|
|
order by id desc
|
|
limit ?
|
|
""",
|
|
(limit,),
|
|
).fetchall()
|
|
return [dict(row) for row in rows]
|
|
|
|
|
|
def main() -> int:
|
|
with OUT_PATH.open("w", encoding="utf-8") as handle:
|
|
write_line(handle, "monitor", {"status": "started", "window_seconds": WINDOW_SECONDS})
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.row_factory = sqlite3.Row
|
|
headers = login_headers()
|
|
write_line(handle, "auth", {"status": "ok", "user": USERNAME, "role": ROLE})
|
|
end_at = time.time() + WINDOW_SECONDS
|
|
seen = {
|
|
"live": None,
|
|
"recent": None,
|
|
"events": None,
|
|
"links": None,
|
|
}
|
|
try:
|
|
while time.time() < end_at:
|
|
try:
|
|
live = fetch_json("/proxy/asterisk-bridge/asterisk/live-calls", headers)
|
|
if live != seen["live"]:
|
|
seen["live"] = live
|
|
write_line(handle, "live-calls", live)
|
|
except URLError as exc:
|
|
write_line(handle, "live-calls-error", {"error": str(exc)})
|
|
|
|
try:
|
|
recent = fetch_json("/proxy/asterisk-bridge/asterisk/recent-calls", headers)
|
|
if recent != seen["recent"]:
|
|
seen["recent"] = recent
|
|
write_line(handle, "recent-calls", recent)
|
|
except URLError as exc:
|
|
write_line(handle, "recent-calls-error", {"error": str(exc)})
|
|
|
|
events = latest_events(conn)
|
|
if events != seen["events"]:
|
|
seen["events"] = events
|
|
write_line(handle, "voice-events", events)
|
|
|
|
links = latest_links(conn)
|
|
if links != seen["links"]:
|
|
seen["links"] = links
|
|
write_line(handle, "call-links", links)
|
|
|
|
time.sleep(POLL_SECONDS)
|
|
finally:
|
|
conn.close()
|
|
write_line(handle, "monitor", {"status": "finished"})
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|