From f90613da169554d4a1488090fdf5150e3c00a2aa Mon Sep 17 00:00:00 2001 From: DAProgs Date: Fri, 13 Mar 2026 16:44:12 -0400 Subject: [PATCH] 2603.7 added dark/light mode --- portspoof_py/__init__.py | 2 +- portspoof_py/admin.py | 140 +++++++++++++++++++++++++++------------ pyproject.toml | 2 +- 3 files changed, 100 insertions(+), 44 deletions(-) diff --git a/portspoof_py/__init__.py b/portspoof_py/__init__.py index db3ebc1..4495ff1 100644 --- a/portspoof_py/__init__.py +++ b/portspoof_py/__init__.py @@ -1,2 +1,2 @@ """portspoof_py — asyncio Python rewrite of the portspoof TCP honeypot.""" -__version__ = '2603.6' +__version__ = '2603.7' diff --git a/portspoof_py/admin.py b/portspoof_py/admin.py index a062bc0..c3f5393 100644 --- a/portspoof_py/admin.py +++ b/portspoof_py/admin.py @@ -82,10 +82,11 @@ _PASSWD_HTML = """\ portspoof — change password +{theme_script}

portspoof admin

-

← dashboard

+

← dashboard {theme_btn}

{banner_html} @@ -126,7 +127,8 @@ def _render_passwd(msg: str = '', msg_ok: bool = True) -> str: if msg: cls = 'ok' if msg_ok else 'err' banner_html = f'' - return _PASSWD_HTML.format(css=_CONFIG_CSS, banner_html=banner_html) + return _PASSWD_HTML.format(css=_CONFIG_CSS, banner_html=banner_html, + theme_script=_THEME_SCRIPT, theme_btn=_THEME_BTN) # ── settings page ───────────────────────────────────────────────────────────── @@ -139,10 +141,11 @@ _SETTINGS_HTML = """\ portspoof — settings +{theme_script}

portspoof admin

-

← dashboard

+

← dashboard {theme_btn}

{banner_html} @@ -182,45 +185,66 @@ def _render_settings(settings: Settings, msg: str = '', msg_ok: bool = True) -> banner_html=banner_html, delay_min=settings.delay_min, delay_max=settings.delay_max, + theme_script=_THEME_SCRIPT, + theme_btn=_THEME_BTN, ) # ── config page ─────────────────────────────────────────────────────────────── _CONFIG_CSS = """ +:root { + --bg: #0d1117; --bg2: #161b22; --border: #30363d; + --text: #c9d1d9; --dim: #8b949e; --heading: #e6edf3; --blue: #58a6ff; + --green: #238636; --green2: #2ea043; + --btn-sec: #21262d; --btn-sec2: #30363d; + --ok-bg: #0f2a1a; --ok-border: #238636; --ok-text: #3fb950; + --err-bg: #2d1212; --err-border: #f85149; --err-text: #f85149; +} +html.light { + --bg: #f6f8fa; --bg2: #ffffff; --border: #d0d7de; + --text: #24292f; --dim: #57606a; --heading: #1f2328; --blue: #0969da; + --green: #1f883d; --green2: #2ea043; + --btn-sec: #f6f8fa; --btn-sec2: #eaeef2; + --ok-bg: #dafbe1; --ok-border: #1f883d; --ok-text: #1a7f37; + --err-bg: #fff0ee; --err-border: #ff8182; --err-text: #cf222e; +} * { box-sizing: border-box; margin: 0; padding: 0; } -body { font-family: 'Courier New', monospace; background: #0d1117; color: #c9d1d9; +body { font-family: 'Courier New', monospace; background: var(--bg); color: var(--text); padding: 24px; font-size: 13px; line-height: 1.6; } -a { color: #58a6ff; text-decoration: none; } -h1 { color: #58a6ff; font-size: 20px; margin-bottom: 4px; } -h2 { color: #e6edf3; font-size: 14px; margin: 24px 0 12px; } -.sub { color: #8b949e; font-size: 12px; margin-bottom: 24px; } -.card { background: #161b22; border: 1px solid #30363d; border-radius: 6px; +a { color: var(--blue); text-decoration: none; } +h1 { color: var(--blue); font-size: 20px; margin-bottom: 4px; } +h2 { color: var(--heading); font-size: 14px; margin: 24px 0 12px; } +.sub { color: var(--dim); font-size: 12px; margin-bottom: 24px; } +.card { background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; padding: 20px; max-width: 600px; } .field { margin-bottom: 14px; } -label { display: block; color: #8b949e; font-size: 11px; text-transform: uppercase; +label { display: block; color: var(--dim); font-size: 11px; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 4px; } input[type=text], input[type=number], input[type=password], input[type=email] { - width: 100%; background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; + width: 100%; background: var(--bg); border: 1px solid var(--border); color: var(--text); padding: 7px 10px; border-radius: 4px; font-family: inherit; font-size: 13px; } input[type=text]:focus, input[type=number]:focus, input[type=password]:focus, input[type=email]:focus { - outline: none; border-color: #58a6ff; } -.hint { color: #8b949e; font-size: 11px; margin-top: 3px; } + outline: none; border-color: var(--blue); } +.hint { color: var(--dim); font-size: 11px; margin-top: 3px; } .row2 { display: flex; gap: 12px; } .row2 .field { flex: 1; } .check-row { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; } -input[type=checkbox] { width: 15px; height: 15px; accent-color: #238636; } +input[type=checkbox] { width: 15px; height: 15px; accent-color: var(--green); } .actions { display: flex; gap: 10px; margin-top: 20px; flex-wrap: wrap; } -button { background: #238636; border: 1px solid #2ea043; color: #fff; +button { background: var(--green); border: 1px solid var(--green2); color: #fff; padding: 7px 16px; border-radius: 4px; cursor: pointer; font-family: inherit; font-size: 13px; } -button:hover { background: #2ea043; } -button.secondary { background: #21262d; border-color: #30363d; color: #c9d1d9; } -button.secondary:hover { background: #30363d; } +button:hover { background: var(--green2); } +button.secondary { background: var(--btn-sec); border-color: var(--border); color: var(--text); } +button.secondary:hover { background: var(--btn-sec2); } .banner { padding: 10px 14px; border-radius: 4px; margin-bottom: 16px; font-size: 12px; } -.banner.ok { background: #0f2a1a; border: 1px solid #238636; color: #3fb950; } -.banner.err { background: #2d1212; border: 1px solid #f85149; color: #f85149; } +.banner.ok { background: var(--ok-bg); border: 1px solid var(--ok-border); color: var(--ok-text); } +.banner.err { background: var(--err-bg); border: 1px solid var(--err-border); color: var(--err-text); } +.link-btn { background: none; border: none; color: var(--blue); padding: 0; + font-family: inherit; font-size: 13px; cursor: pointer; } +.link-btn:hover { text-decoration: underline; } """ _CONFIG_HTML = """\ @@ -231,10 +255,11 @@ _CONFIG_HTML = """\ portspoof — email alerts +{theme_script}

portspoof admin

-

← dashboard

+

← dashboard {theme_btn}

{banner_html} @@ -338,6 +363,8 @@ def _render_config(notifier: Notifier, msg: str = '', msg_ok: bool = True) -> st return _CONFIG_HTML.format( css=_CONFIG_CSS, banner_html=banner_html, + theme_script=_THEME_SCRIPT, + theme_btn=_THEME_BTN, enabled_chk='checked' if cfg.get('enabled') else '', smtp_host=html.escape(cfg.get('smtp_host', '')), smtp_port=cfg.get('smtp_port', 587), @@ -377,58 +404,84 @@ def _redirect(location: str) -> bytes: ) +# ── shared theme script ─────────────────────────────────────────────────────── + +_THEME_SCRIPT = """\ + """ + +_THEME_BTN = '· ' + # ── HTML template ───────────────────────────────────────────────────────────── _CSS = """ +:root { + --bg: #0d1117; --bg2: #161b22; --border: #30363d; + --text: #c9d1d9; --dim: #8b949e; --heading: #e6edf3; --blue: #58a6ff; + --red: #f78166; --ip-color: #79c0ff; + --hover: #1c2128; --badge: #21262d; --sep: #21262d; --footer: #484f58; +} +html.light { + --bg: #f6f8fa; --bg2: #ffffff; --border: #d0d7de; + --text: #24292f; --dim: #57606a; --heading: #1f2328; --blue: #0969da; + --red: #cf222e; --ip-color: #0550ae; + --hover: #f3f4f6; --badge: #eaeef2; --sep: #d8dee4; --footer: #8c959f; +} * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Courier New', monospace; - background: #0d1117; color: #c9d1d9; + background: var(--bg); color: var(--text); padding: 24px; font-size: 13px; line-height: 1.5; } -a { color: #58a6ff; text-decoration: none; } -h1 { color: #58a6ff; font-size: 20px; margin-bottom: 4px; } -.sub { color: #8b949e; font-size: 12px; margin-bottom: 24px; } +a { color: var(--blue); text-decoration: none; } +h1 { color: var(--blue); font-size: 20px; margin-bottom: 4px; } +.sub { color: var(--dim); font-size: 12px; margin-bottom: 24px; } .row { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 20px; } .card { - background: #161b22; border: 1px solid #30363d; + background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; padding: 16px; } .card.stat { min-width: 155px; flex: 1; } .card.half { flex: 1; min-width: 260px; } .card.full { width: 100%; } .card h3 { - color: #8b949e; font-size: 11px; + color: var(--dim); font-size: 11px; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px; } -.card .val { font-size: 28px; color: #58a6ff; font-weight: bold; } +.card .val { font-size: 28px; color: var(--blue); font-weight: bold; } .card .val.last { font-size: 13px; margin-top: 4px; } -h2 { color: #e6edf3; font-size: 14px; margin-bottom: 12px; } +h2 { color: var(--heading); font-size: 14px; margin-bottom: 12px; } table { width: 100%; border-collapse: collapse; } th { - color: #8b949e; font-size: 11px; text-transform: uppercase; + color: var(--dim); font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; padding: 6px 10px; text-align: left; - border-bottom: 1px solid #21262d; + border-bottom: 1px solid var(--sep); } -td { padding: 5px 10px; border-bottom: 1px solid #21262d; } +td { padding: 5px 10px; border-bottom: 1px solid var(--sep); } tr:last-child td { border-bottom: none; } -tr:hover td { background: #1c2128; } -.port { color: #f78166; } -.ip { color: #79c0ff; } -.ts { color: #8b949e; font-size: 11px; } -.cc { color: #8b949e; font-size: 11px; } +tr:hover td { background: var(--hover); } +.port { color: var(--red); } +.ip { color: var(--ip-color); } +.ts { color: var(--dim); font-size: 11px; } +.cc { color: var(--dim); font-size: 11px; } .badge { - display: inline-block; background: #21262d; + display: inline-block; background: var(--badge); border-radius: 3px; padding: 1px 7px; font-size: 11px; margin-left: 6px; vertical-align: middle; } -.empty { color: #8b949e; font-style: italic; padding: 8px 0; } -.footer { color: #484f58; font-size: 11px; margin-top: 24px; } +.empty { color: var(--dim); font-style: italic; padding: 8px 0; } +.footer { color: var(--footer); font-size: 11px; margin-top: 24px; } .link-btn { - background: none; border: none; color: #58a6ff; padding: 0; + background: none; border: none; color: var(--blue); padding: 0; font-family: inherit; font-size: inherit; cursor: pointer; } .link-btn:hover { text-decoration: underline; } +html.light svg line { stroke: var(--sep); } +html.light svg text { fill: var(--dim); } +html.light svg polygon { fill: rgba(9,105,218,0.12); opacity: 1; } +html.light svg polyline { stroke: var(--blue); } """ _HTML = """\ @@ -440,6 +493,7 @@ _HTML = """\ portspoof admin +{theme_script}

portspoof admin

@@ -451,7 +505,7 @@ _HTML = """\ · settings · email alerts · JSON stats · - change password + change password {theme_btn}

@@ -627,6 +681,8 @@ async def _render_dashboard(stats: Stats, cfg: Config) -> str: return _HTML.format( css=_CSS, + theme_script=_THEME_SCRIPT, + theme_btn=_THEME_BTN, total=stats.total, cpm=stats.connections_per_minute(), uptime=stats.uptime_str(), diff --git a/pyproject.toml b/pyproject.toml index 13c4d76..ba5d621 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi" [project] name = "portspoof-py" -version = "2603.6" +version = "2603.7" description = "Python asyncio rewrite of the portspoof TCP honeypot" readme = "README.md" requires-python = ">=3.11"