diff --git a/GiteaHubSync/app/backend/panel.py b/GiteaHubSync/app/backend/panel.py index b4502ea..5d50410 100644 --- a/GiteaHubSync/app/backend/panel.py +++ b/GiteaHubSync/app/backend/panel.py @@ -71,7 +71,8 @@ ENV["GIT_TERMINAL_PROMPT"] = "0" # ---------------- 应用设置(settings.json) ---------------- cfg = {"gitea_user": LEGACY_GITEA_USER, "gitea_pass": "", "auto_create": True, "interval": DEFAULT_INTERVAL, "gitea_host": DEFAULT_GITEA_HOST, - "max_workers": DEFAULT_WORKERS, "max_retries": DEFAULT_MAX_RETRIES} + "max_workers": DEFAULT_WORKERS, "max_retries": DEFAULT_MAX_RETRIES, + "open_url": ""} # 「打开 Gitea」按钮专用链接(留空则用 gitea_host + 自动推断) def norm_gitea_host(h): @@ -98,11 +99,15 @@ def gitea_host(): def gitea_ui(host_header="", referer="", xfwd_host=""): - """「打开 Gitea」按钮地址:把 127.0.0.1/localhost 替换成真实远程主机名。 - 推断顺序:X-Forwarded-Host(CGI 转发) > Host > Referer(浏览器自带)。 - 注意:仅当推断出的真实主机名不是 loopback 时才替换(本地直连保持原样)。""" - if GITEA_URL_ENV: - return GITEA_URL_ENV + """「打开 Gitea」按钮地址。 + 优先级:环境变量 GITEA_URL > 配置 open_url > gitea_host(自动推断替换)。 + 自动推断顺序:X-Forwarded-Host > Host > Referer,仅当真实主机非 loopback 时替换 gitea_host。""" + env_url = GITEA_URL_ENV + if env_url: + return env_url + ou = (cfg.get("open_url") or "").strip() + if ou: + return ou base = gitea_host() real = (xfwd_host or host_header or "").split(":")[0].strip() if not real and referer: @@ -110,7 +115,7 @@ def gitea_ui(host_header="", referer="", xfwd_host=""): if m: real = m.group(1) if not real or real in ("127.0.0.1", "localhost", "0.0.0.0", "::1"): - return base # 本地/无法推断,保留 gitea_host 原值 + return base try: p = urlsplit(base) if p.hostname in ("127.0.0.1", "localhost", "0.0.0.0", "::1", ""): @@ -184,6 +189,12 @@ def load_settings(): h = norm_gitea_host(d.get("gitea_host")) if h: cfg["gitea_host"] = h + # open_url 校验:必须是 http(s) URL 且无路径 + ou = d.get("open_url") + if isinstance(ou, str): + cfg["open_url"] = norm_gitea_host(ou.strip()) or "" + else: + cfg["open_url"] = str(ou or "").strip() or cfg.get("open_url", "") except Exception: pass write_gitcreds() @@ -1309,6 +1320,11 @@ PAGE_SETTINGS = """
+ +
+ + +
@@ -1386,6 +1402,7 @@ async function refresh(){ document.getElementById('giteaPass').placeholder=s.gitea_pass_set ?'Gitea 密码(已设置;留空保存可清除)':'Gitea 密码(私有仓库必填)'; document.getElementById('giteaUrlTip').textContent='「打开 Gitea」按钮地址:'+(s.gitea_url||'—')+'(随访问地址自动适配);密码只保存在 NAS 本地,仅用于读取私有仓库。'; + document.getElementById('openUrl').value=s.open_url||''; document.getElementById('swAuto').checked=!!s.auto_create; const iv=document.getElementById('syncInterval'); iv.value=s.interval||10;iv.min=s.min_interval||5;iv.max=s.max_interval||86400; @@ -1526,6 +1543,12 @@ async function saveGitea(withPass){ if(withPass)document.getElementById('giteaPass').value=''; refresh(); } +async function saveOpenUrl(){ + const v=document.getElementById('openUrl').value.trim(); + const r=await (await fetch('api/settings/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({open_url:v})})).json(); + showMsg('msgCfg',r.msg||JSON.stringify(r),!!r.ok); + refresh(); +} async function saveAuto(){ const auto_create=document.getElementById('swAuto').checked; const r=await (await fetch('api/settings/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({auto_create})})).json(); @@ -1713,6 +1736,7 @@ class Handler(BaseHTTPRequestHandler): "max_retries": max_retries(), "retry_interval": RETRY_INTERVAL, "gitea_host": gitea_host(), + "open_url": cfg.get("open_url", ""), "gitea_url": gitea_ui( self.headers.get("Host"), self.headers.get("Referer"), @@ -1751,6 +1775,15 @@ class Handler(BaseHTTPRequestHandler): self._json({"ok": False, "msg": "Gitea 地址不合法,应类似 http://127.0.0.1:3000"}) return new_host = h + if "open_url" in data: + v = (data.get("open_url") or "").strip() + if v: + if "://" not in v: + self._json({"ok": False, "msg": "「打开 Gitea 链接」必须带协议头,如 http://nas.local:3000 或 https://..."}) + return + if not norm_gitea_host(v): + self._json({"ok": False, "msg": "「打开 Gitea 链接」格式不合法,应类似 http://nas.local:3000(留空则用默认推断)"}) + return if "interval" in data: if not valid_interval(data.get("interval")): self._json({"ok": False, "msg": f"同步间隔须为 {MIN_INTERVAL} ~ {MAX_INTERVAL} 之间的整数(秒)"}) @@ -1802,6 +1835,8 @@ class Handler(BaseHTTPRequestHandler): log("Gitea 密码已" + ("更新" if new_pass else "清除")) if new_host is not None: cfg["gitea_host"] = new_host + if "open_url" in data: + cfg["open_url"] = (data.get("open_url") or "").strip() if "auto_create" in data: cfg["auto_create"] = bool(data.get("auto_create")) if "interval" in data: diff --git a/GiteaHubSync/manifest b/GiteaHubSync/manifest index 6d5d2f9..2a2775b 100644 --- a/GiteaHubSync/manifest +++ b/GiteaHubSync/manifest @@ -1,5 +1,5 @@ appname = GiteaHubSync -version = 1.5.5 +version = 1.5.6 display_name = Gitea→GitHub 同步助手 desc = 每次推送到飞牛 Gitea 的代码,10 秒内自动镜像到 GitHub 的 7 个私有仓库。内置控制面板:自动同步开关、立即同步、仓库状态、同步日志,一键打开 Gitea。数据全部保存在 NAS 本地。 platform = all