Add Live tab world chain map with hop geo arcs.
Plots YOU through each hop to EXIT on an equirectangular map with great-circle connectors, extends exit intel with lat/lon, and auto-refreshes on chain rotation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Proxy God — GUI
|
||||
Tabs: Live | Chain Builder | Browser | Ban Tester | Signup Prep | Privacy | Settings
|
||||
(Live tab includes exit intel + world chain map)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -40,6 +41,7 @@ from .firewall import (
|
||||
from .artifact_wipe import wipe_artifacts
|
||||
from .ban_tester import BanTestResult, run_ban_tests, test_single_site
|
||||
from .exit_intel import ExitIntel, fetch_exit_intel
|
||||
from .chain_map import build_chain_map_points, render_chain_map, resize_map_display
|
||||
from .signup_prep import (
|
||||
SIGNUP_PRESET_KEYS,
|
||||
SIGNUP_PRESETS,
|
||||
@@ -139,8 +141,8 @@ def main() -> None:
|
||||
|
||||
root = ctk.CTk()
|
||||
root.title("Proxy God v2")
|
||||
root.geometry("1080x760")
|
||||
root.minsize(900, 620)
|
||||
root.geometry("1080x860")
|
||||
root.minsize(900, 720)
|
||||
root.configure(fg_color=BG)
|
||||
|
||||
# ── 3D-ish neon header banner (drawn on a Canvas for a true gradient) ────
|
||||
@@ -539,6 +541,7 @@ def main() -> None:
|
||||
color = GREEN
|
||||
v_intel_tags.set("Flags: " + " · ".join(tags))
|
||||
intel_tags_lbl.configure(text_color=color)
|
||||
root.after(200, lambda: _refresh_chain_map(silent=True))
|
||||
|
||||
def _refresh_exit_intel(silent: bool = False) -> None:
|
||||
if intel_running[0]:
|
||||
@@ -577,6 +580,92 @@ def main() -> None:
|
||||
_btn(intel_btn_row, "Refresh intel", lambda: _refresh_exit_intel(False), w=110, h=24,
|
||||
fg_color=ACCENT2, hover_color=ACCENT).pack(side="left")
|
||||
|
||||
# ── Chain world map ───────────────────────────────────────────────────────
|
||||
map_card = ctk.CTkFrame(tab_live, fg_color=CARD, corner_radius=8)
|
||||
map_card.pack(fill="x", pady=(0, 6))
|
||||
|
||||
map_head = ctk.CTkFrame(map_card, fg_color="transparent")
|
||||
map_head.pack(fill="x", padx=10, pady=(8, 2))
|
||||
ctk.CTkLabel(
|
||||
map_head, text="Chain map", font=(FONT, 11, "bold"), text_color=CYAN,
|
||||
).pack(side="left")
|
||||
v_map_status = ctk.StringVar(value="Plotting: waiting for chain…")
|
||||
ctk.CTkLabel(
|
||||
map_head, textvariable=v_map_status, font=(FONT, 10), text_color=TEXT2,
|
||||
).pack(side="right")
|
||||
|
||||
map_frame = ctk.CTkFrame(map_card, fg_color=BG, corner_radius=6)
|
||||
map_frame.pack(fill="x", padx=10, pady=(0, 8))
|
||||
map_img_label = ctk.CTkLabel(map_frame, text="")
|
||||
map_img_label.pack(padx=2, pady=2)
|
||||
|
||||
map_ctk_image: dict[str, ctk.CTkImage | None] = {"img": None}
|
||||
last_chain_state: dict[str, Any] = {
|
||||
"hops": [],
|
||||
"status": "idle",
|
||||
"exit_ip": "",
|
||||
"direct_ip": "",
|
||||
}
|
||||
map_running = [False]
|
||||
|
||||
def _show_chain_map(pil_img: Any) -> None:
|
||||
try:
|
||||
map_frame.update_idletasks()
|
||||
w = max(640, map_frame.winfo_width() - 8)
|
||||
h = max(180, min(240, int(w * 0.23)))
|
||||
resized = resize_map_display(pil_img, w, h)
|
||||
map_ctk_image["img"] = ctk.CTkImage(
|
||||
light_image=resized, dark_image=resized, size=(w, h),
|
||||
)
|
||||
map_img_label.configure(image=map_ctk_image["img"], text="")
|
||||
except Exception as e:
|
||||
logging.getLogger(__name__).debug("chain map display: %s", e)
|
||||
|
||||
def _refresh_chain_map(silent: bool = True) -> None:
|
||||
if map_running[0]:
|
||||
return
|
||||
hops = list(last_chain_state["hops"])
|
||||
status = str(last_chain_state["status"] or "idle")
|
||||
if not service_running[0] or not hops:
|
||||
v_map_status.set("Plotting: chain not running")
|
||||
_show_chain_map(render_chain_map([], status="idle"))
|
||||
return
|
||||
map_running[0] = True
|
||||
v_map_status.set("Plotting: resolving hop locations…")
|
||||
direct_ip = str(last_chain_state.get("direct_ip") or "")
|
||||
exit_ip = str(last_chain_state.get("exit_ip") or "")
|
||||
exit_intel = last_intel.get("intel")
|
||||
timeout = min(12.0, svc.settings.validation_timeout_seconds + 2.0)
|
||||
|
||||
def work() -> None:
|
||||
points = build_chain_map_points(
|
||||
hops,
|
||||
direct_ip=direct_ip,
|
||||
exit_ip=exit_ip,
|
||||
exit_intel=exit_intel if isinstance(exit_intel, ExitIntel) else None,
|
||||
timeout_seconds=timeout,
|
||||
)
|
||||
img = render_chain_map(points, width=960, height=220, status=status) # type: ignore[arg-type]
|
||||
|
||||
def done() -> None:
|
||||
_show_chain_map(img)
|
||||
if points:
|
||||
v_map_status.set(f"Plotting: {len(points)} nodes · {status}")
|
||||
if not silent:
|
||||
_log(f"Chain map updated — {len(points)} geo nodes ({status}).")
|
||||
else:
|
||||
v_map_status.set("Plotting: no geo data yet (hop DNS/geo pending)")
|
||||
map_running[0] = False
|
||||
|
||||
root.after(0, done)
|
||||
|
||||
threading.Thread(target=work, daemon=True).start()
|
||||
|
||||
_btn(map_head, "Refresh map", lambda: _refresh_chain_map(False), w=100, h=22,
|
||||
fg_color=ACCENT2, hover_color=ACCENT).pack(side="right", padx=(8, 0))
|
||||
|
||||
_show_chain_map(render_chain_map([], status="idle"))
|
||||
|
||||
# Log box
|
||||
log_frame = ctk.CTkFrame(tab_live, fg_color=CARD, corner_radius=8)
|
||||
log_frame.pack(fill="both", expand=True)
|
||||
@@ -2385,6 +2474,8 @@ def main() -> None:
|
||||
ip = str(m.get("ip", "—"))
|
||||
your_ip_lbl.configure(text=f"Direct IP: {ip}")
|
||||
v_real_ip.set(ip)
|
||||
last_chain_state["direct_ip"] = ip
|
||||
root.after(300, lambda: _refresh_chain_map(silent=True))
|
||||
|
||||
elif t == "vpn":
|
||||
nonlocal outer_hop_label
|
||||
@@ -2447,6 +2538,10 @@ def main() -> None:
|
||||
tray.set_state(tray_color, exit_ip=exit_s)
|
||||
prev_exit = v_exit_ip.get()
|
||||
v_exit_ip.set(exit_s if exit_s else "—")
|
||||
last_chain_state["hops"] = hops
|
||||
last_chain_state["status"] = status
|
||||
last_chain_state["exit_ip"] = exit_s or ""
|
||||
root.after(250, lambda: _refresh_chain_map(silent=True))
|
||||
if status == "healthy" and exit_s and exit_s != prev_exit:
|
||||
root.after(400, lambda: _refresh_exit_intel(silent=True))
|
||||
elif status != "healthy":
|
||||
|
||||
Reference in New Issue
Block a user