From 0d413cfeaf8cc8c475b0451cb4bf987888d9ca24 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 16 Jul 2026 15:08:18 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BC=93=E5=AD=98=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=20404=20+=20=E6=97=A5=E5=BF=97=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E6=98=BE=E7=A4=BA=E4=B8=BA=E6=9C=AC=E5=9C=B0=E6=97=B6?= =?UTF-8?q?=E5=8C=BA=20+=20=E6=8C=81=E4=B9=85=E5=8C=96=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 4 个相关 bug: 1. **缓存目录配置保存后 404 (核心 bug)** - 原因: _show_config_preview 把 endpoint 名 'config_cache' 存进 session 作 为 return_to,config_preview 把它当 URL 跳到 'config_cache' 报 404 - 修法: _show_config_preview 自动用 url_for() 解析 endpoint 名 2. **日志时间显示 UTC 而不是本地时间 (次生 bug)** - 原因: log_parser 把 timestamp 解析成 UTC datetime,模板直接 e.timestamp.strftime('%H:%M:%S') 显示 UTC - 修法: LogEntry 新增 timestamp_local 属性 (astimezone()), 3 个模板改用 e.timestamp_local 3. **DB 同步永远不工作 (次生 bug)** - 原因: _should_auto_sync 调 get_config('log_sync_interval') -> _resolve_active_instance -> session.get 在 app_context 里炸, try/except 静默吞,永远返回 False - 修法: 新增 _resolve_access_log_path() 不走 session 链, _should_auto_sync 直接查 AppConfig 表 4. **default_struct 规则引用 Safe_ports/SSL_ports 但 ACL 是 safeports/sslports** - 大小写不一致导致 squid -k parse 报 FATAL - 修法: 改成小写统一 测试: cache save 流程全链路跑通 (POST -> preview -> confirm -> /config/cache 200) 所有 config 页面 (main/acls/rules/cache/refresh/auth/peers) 的 preview+confirm return_to 字段都正确解析为 URL --- app.py | 59 +++++++++++++++++++++++++++++++++----- log_parser.py | 11 +++++++ squid_config.py | 4 +-- templates/logs.html | 2 +- templates/logs_import.html | 2 +- templates/logs_live.html | 2 +- 6 files changed, 68 insertions(+), 12 deletions(-) diff --git a/app.py b/app.py index 721b1e0..ca9cbd2 100644 --- a/app.py +++ b/app.py @@ -529,8 +529,23 @@ def backup_conf(reason: str = "manual") -> str | None: def _show_config_preview(pending_content: str, return_to: str): - """Store pending content in session, redirect to GET /config/preview for confirmation.""" + """Store pending content in session, redirect to GET /config/preview for confirmation. + + ``return_to`` may be either an endpoint name (e.g. ``"config_cache"``) + or a URL path (e.g. ``"/config/cache"``). Endpoint names get resolved + via ``url_for`` so the preview page's "返回" link is always a valid URL. + """ import base64 + # Normalize return_to: if it doesn't look like a URL, treat it as an + # endpoint name and resolve to a URL. This was the root cause of the + # 404 bug after saving a config - we stored "config_cache" and + # ``redirect("config_cache")`` produced a 404. + if return_to and not return_to.startswith("/") and not return_to.startswith("http"): + try: + return_to = url_for(return_to) + except Exception: + # unknown endpoint - fall back to dashboard + return_to = url_for("dashboard") session["_pending_conf"] = base64.b64encode(pending_content.encode()).decode() session["_pending_return"] = return_to return redirect(url_for("config_preview")) @@ -648,7 +663,7 @@ def get_parsed_logs(force: bool = False) -> list[log_parser.LogEntry]: # Best-effort auto-sync (throttled by the file mtime + last sync stamp) if force or _should_auto_sync(instance_id): try: - path = access_log_path() + path = _resolve_access_log_path() if os.path.isfile(path): log_storage.sync_log_to_db( path=path, instance_id=instance_id, @@ -680,18 +695,46 @@ def get_parsed_logs(force: bool = False) -> list[log_parser.LogEntry]: return wrapped +def _resolve_access_log_path() -> str: + """Look up the access log path WITHOUT going through session-bound code. + + ``get_config`` -> ``_resolve_active_instance`` -> ``session.get`` raises + outside a request context, and ``_should_auto_sync`` is called from many + places (background threads, app shell) that don't have a request context. + Here we hit the DB directly using a DB-session that does not require + session-binding. + """ + try: + # 1) instance row, if any is active + active = SquidInstance.query.filter_by(is_active=True).first() + if active and active.access_log_path: + return active.access_log_path + # 2) AppConfig override + row = AppConfig.query.filter_by(key="access_log_path").first() + if row and row.value: + return row.value + except Exception: + pass + return DEFAULTS.get("access_log_path", "/var/log/squid/access.log") + + def _should_auto_sync(instance_id: int) -> bool: """Decide whether to re-sync the access.log tail for ``instance_id``. Cheap heuristic: check the file's mtime against the last sync stamp kept in :class:`AppConfig`. Always returns False when ``log_sync_interval`` is 0 (manual sync only). + + Must NOT depend on ``session`` (called outside request context). """ try: - interval = int(get_config("log_sync_interval", "0")) + # Use raw AppConfig instead of get_config() - get_config touches + # session which raises outside request context. + row = AppConfig.query.filter_by(key="log_sync_interval").first() + interval = int(row.value) if (row and row.value) else 0 if interval <= 0: return False - path = access_log_path() + path = _resolve_access_log_path() if not os.path.isfile(path): return False mtime = os.path.getmtime(path) @@ -709,7 +752,9 @@ def _should_auto_sync(instance_id: int) -> bool: return False _auto_sync_last_call = now return mtime > last_dt.timestamp() - except Exception: + except Exception as e: + # Don't use current_app here - this code can run outside app context + # (e.g. in test scripts). Just swallow. return False @@ -928,7 +973,7 @@ def logs_view(): # best-effort sync before serving (skipped if the file is unchanged) try: if _should_auto_sync(instance_id): - path = access_log_path() + path = _resolve_access_log_path() if os.path.isfile(path): log_storage.sync_log_to_db(path=path, instance_id=instance_id) except Exception: @@ -3115,7 +3160,7 @@ def _export_logs_payload(): import log_storage try: if _should_auto_sync(instance_id): - path = access_log_path() + path = _resolve_access_log_path() if os.path.isfile(path): log_storage.sync_log_to_db(path=path, instance_id=instance_id) except Exception: diff --git a/log_parser.py b/log_parser.py index 5bfb8ce..559ed87 100644 --- a/log_parser.py +++ b/log_parser.py @@ -119,6 +119,17 @@ class LogEntry(dict): def result_description(self) -> str: return RESULT_CODE_DESCRIPTION.get(self.result_code, self.result_code) + @property + def timestamp_local(self): + """Return a datetime converted to the server's local timezone (CST for cn).""" + ts = self.get("timestamp") + if ts is None: + return None + try: + return ts.astimezone() # use system local tz + except Exception: + return ts + def parse_line(line: str) -> LogEntry | None: """Parse a single access.log line. Returns None on unparseable lines.""" diff --git a/squid_config.py b/squid_config.py index 2a3c8a1..a2218ee 100644 --- a/squid_config.py +++ b/squid_config.py @@ -611,8 +611,8 @@ def default_struct() -> dict: ], "rules": [ Rule(action="deny", acls=["manager"], directive="http_access"), - Rule(action="deny", acls=["!Safe_ports"], directive="http_access"), - Rule(action="deny", acls=["!SSL_ports"], directive="http_access"), + Rule(action="deny", acls=["!safeports"], directive="http_access"), + Rule(action="deny", acls=["!sslports"], directive="http_access"), Rule(action="allow", acls=["localhost"], directive="http_access"), Rule(action="deny", acls=["all"], directive="http_access"), ], diff --git a/templates/logs.html b/templates/logs.html index eae30e9..a9b3413 100644 --- a/templates/logs.html +++ b/templates/logs.html @@ -129,7 +129,7 @@ {% for e in entries %} - {{ e.timestamp.strftime('%Y-%m-%d %H:%M:%S') if e.timestamp else e.time }} + {{ e.timestamp_local.strftime('%Y-%m-%d %H:%M:%S') if e.timestamp_local else e.time }} {{ e.client }} {{ e.result_code }} diff --git a/templates/logs_import.html b/templates/logs_import.html index 5f67c2f..f60ced2 100644 --- a/templates/logs_import.html +++ b/templates/logs_import.html @@ -109,7 +109,7 @@ {% for e in entries[:200] %} - {{ e.timestamp.strftime('%H:%M:%S') if e.timestamp else e.time }} + {{ e.timestamp_local.strftime('%H:%M:%S') if e.timestamp_local else e.time }} {{ e.client }} {{ e.result_code }} {{ e.http_code or '-' }} diff --git a/templates/logs_live.html b/templates/logs_live.html index aaea8d4..74e3489 100644 --- a/templates/logs_live.html +++ b/templates/logs_live.html @@ -33,7 +33,7 @@ {% for e in entries %} - {{ e.timestamp.strftime('%H:%M:%S') if e.timestamp else e.time }} + {{ e.timestamp_local.strftime('%H:%M:%S') if e.timestamp_local else e.time }} {{ e.client }} {{ e.result_code }}