131 lines
4.7 KiB
Python
131 lines
4.7 KiB
Python
"""Browser-reachable addresses written into generated HTML pages.
|
|
|
|
The settings dialog is the source of truth. A value saved in
|
|
``system_settings.json`` wins over ``config.yaml`` and the built-in default,
|
|
so a deployment can point new pages at a domain users can actually open
|
|
without editing or restarting from a config file.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from urllib.parse import urlparse
|
|
|
|
from deerflow.config.html_runtime_config import (
|
|
DEFAULT_HTML_MAP_TILE_URL,
|
|
DEFAULT_HTML_RUNTIME_BASE_URL,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_HTML_RUNTIME_SUFFIX = "/api/public/html-runtime"
|
|
|
|
|
|
def normalize_html_runtime_base_url(value: str) -> str:
|
|
"""Return an absolute runtime base, appending the public asset path when omitted."""
|
|
|
|
raw = str(value or "").strip()
|
|
if not raw:
|
|
raise ValueError("请填写用户浏览器能够访问的 HTML 运行库地址")
|
|
if any(character.isspace() for character in raw):
|
|
raise ValueError("HTML 运行库地址不能包含空格")
|
|
parsed = urlparse(raw)
|
|
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
raise ValueError("HTML 运行库地址需要以 http:// 或 https:// 开头,并包含域名或 IP")
|
|
if parsed.username or parsed.password:
|
|
raise ValueError("HTML 运行库地址不能包含账号或密码")
|
|
if parsed.query or parsed.fragment:
|
|
raise ValueError("HTML 运行库地址不需要查询参数")
|
|
path = parsed.path.rstrip("/")
|
|
if path in {"", "/"}:
|
|
path = _HTML_RUNTIME_SUFFIX
|
|
return f"{parsed.scheme}://{parsed.netloc}{path}"
|
|
|
|
|
|
def normalize_html_map_tile_url(value: str) -> str:
|
|
"""Return a Leaflet tile template containing the x/y/z placeholders."""
|
|
|
|
raw = str(value or "").strip()
|
|
if not raw:
|
|
raise ValueError("请填写地图瓦片地址")
|
|
if any(character.isspace() for character in raw):
|
|
raise ValueError("地图瓦片地址不能包含空格")
|
|
parsed = urlparse(raw)
|
|
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
raise ValueError("地图瓦片地址需要以 http:// 或 https:// 开头,并包含域名或 IP")
|
|
if parsed.username or parsed.password:
|
|
raise ValueError("地图瓦片地址不能包含账号或密码")
|
|
lowered = raw.lower()
|
|
missing = [token for token in ("{z}", "{x}", "{y}") if token not in lowered]
|
|
if missing:
|
|
raise ValueError("地图瓦片地址需要包含 {z}、{x}、{y} 三个占位符")
|
|
return raw
|
|
|
|
|
|
def html_runtime_base_url(fallback: str | None = None) -> str:
|
|
"""Return the runtime base used when writing or serving generated HTML."""
|
|
|
|
saved = _saved("base_url")
|
|
if saved:
|
|
try:
|
|
return normalize_html_runtime_base_url(saved)
|
|
except ValueError:
|
|
logger.warning("Ignoring invalid saved HTML runtime base URL")
|
|
return _first_valid_base(fallback, _config_value("base_url"), DEFAULT_HTML_RUNTIME_BASE_URL)
|
|
|
|
|
|
def html_map_tile_url(fallback: str | None = None) -> str:
|
|
"""Return the Leaflet tile template injected into deerflow-runtime.js."""
|
|
|
|
saved = _saved("map_tile_url")
|
|
if saved:
|
|
try:
|
|
return normalize_html_map_tile_url(saved)
|
|
except ValueError:
|
|
logger.warning("Ignoring invalid saved HTML map tile URL")
|
|
return _first_valid_tile(fallback, _config_value("map_tile_url"), DEFAULT_HTML_MAP_TILE_URL)
|
|
|
|
|
|
def _saved(field: str) -> str:
|
|
try:
|
|
from deerflow.config.system_settings import load_system_settings
|
|
|
|
block = load_system_settings().html_runtime
|
|
return str(getattr(block, field, "") or "").strip()
|
|
except Exception:
|
|
logger.debug("HTML runtime settings are unavailable", exc_info=True)
|
|
return ""
|
|
|
|
|
|
def _config_value(field: str) -> str:
|
|
try:
|
|
from deerflow.config import get_app_config
|
|
|
|
runtime = get_app_config().html_runtime
|
|
return str(getattr(runtime, field, "") or "").strip()
|
|
except Exception:
|
|
logger.debug("HTML runtime config fallback is unavailable", exc_info=True)
|
|
return ""
|
|
|
|
|
|
def _first_valid_base(*candidates: str | None) -> str:
|
|
for candidate in candidates:
|
|
if not candidate or not str(candidate).strip():
|
|
continue
|
|
try:
|
|
return normalize_html_runtime_base_url(str(candidate))
|
|
except ValueError:
|
|
continue
|
|
return DEFAULT_HTML_RUNTIME_BASE_URL
|
|
|
|
|
|
def _first_valid_tile(*candidates: str | None) -> str:
|
|
for candidate in candidates:
|
|
if not candidate or not str(candidate).strip():
|
|
continue
|
|
try:
|
|
return normalize_html_map_tile_url(str(candidate))
|
|
except ValueError:
|
|
continue
|
|
return DEFAULT_HTML_MAP_TILE_URL
|