feat: v1.1.1 — 同步间隔可调(5 秒 ~ 1 天,默认 10 秒,改完立即生效)

- 间隔存入 settings.json,带范围校验(5~86400 秒),非法值明确拒绝
- 设置页新增「同步间隔」输入框,附人性化时长提示(秒/分钟/小时)
- 同步循环每轮读取最新间隔,保存后下一轮生效,无需重启
- 首页/状态页的间隔文案改为动态显示(原来硬编码 10 秒)
- manifest 1.1.0 → 1.1.1
This commit is contained in:
cbb
2026-10-13 19:37:06 +08:00
parent 6dcd246567
commit 3ec3fd01c0
3 changed files with 69 additions and 9 deletions
+66 -7
View File
@@ -47,7 +47,9 @@ DEFAULT_REPOS = ["SMB_Android", "DCU_APP", "GD32E50X_BOOT", "DCU_DesktopControll
"Motor_v2", "DCU_ECAT_Master", "GD32_BOOT"]
LEGACY_GH_USER = "cbb2625274797" # 旧版硬编码账号,仅用于首次运行的迁移
LEGACY_GITEA_USER = "cbb" # 旧版硬编码的 Gitea 用户名,仅作默认值
INTERVAL = 10
DEFAULT_INTERVAL = 10 # 同步间隔默认值(秒)
MIN_INTERVAL = 5 # 最小同步间隔(秒)
MAX_INTERVAL = 86400 # 最大同步间隔(秒,1 天)
PANEL_PORT = int(os.environ.get("PANEL_PORT", "8899"))
START_TS = time.time()
NAME_RE = re.compile(r"^[A-Za-z0-9._-]{1,100}$")
@@ -59,7 +61,24 @@ ENV["GIT_SSH_COMMAND"] = (f"ssh -i {SSH_KEY} -o IdentitiesOnly=yes "
ENV["GIT_TERMINAL_PROMPT"] = "0"
# ---------------- 应用设置(settings.json) ----------------
cfg = {"gitea_user": LEGACY_GITEA_USER, "gitea_pass": "", "auto_create": True}
cfg = {"gitea_user": LEGACY_GITEA_USER, "gitea_pass": "", "auto_create": True,
"interval": DEFAULT_INTERVAL}
def sync_interval():
"""当前同步间隔(秒),始终返回合法值"""
try:
iv = int(cfg.get("interval", DEFAULT_INTERVAL))
except Exception:
iv = DEFAULT_INTERVAL
return min(MAX_INTERVAL, max(MIN_INTERVAL, iv))
def valid_interval(v):
try:
return MIN_INTERVAL <= int(v) <= MAX_INTERVAL
except Exception:
return False
def load_settings():
@@ -69,6 +88,8 @@ def load_settings():
cfg["gitea_user"] = d["gitea_user"]
cfg["gitea_pass"] = str(d.get("gitea_pass") or "")
cfg["auto_create"] = bool(d.get("auto_create", True))
if valid_interval(d.get("interval")):
cfg["interval"] = int(d["interval"])
except Exception:
pass
write_gitcreds()
@@ -464,7 +485,7 @@ def loop():
state["loop_last"] = time.time()
if state["enabled"]:
run_sync()
time.sleep(INTERVAL)
time.sleep(sync_interval())
def load_panel_state():
@@ -544,14 +565,14 @@ def global_status():
now = time.time()
next_in = None
if state["enabled"] and state["loop_last"]:
next_in = max(0, int(state["loop_last"] + INTERVAL - now))
next_in = max(0, int(state["loop_last"] + sync_interval() - now))
gitea_all = list_gitea_repos()
acc = active_account()
return {
"enabled": state["enabled"],
"syncing": state["syncing"],
"uptime": fmt_uptime(now - START_TS),
"interval": INTERVAL,
"interval": sync_interval(),
"last_check": state["last_check"],
"next_in": next_in,
"checks": state["checks"],
@@ -707,7 +728,7 @@ PAGE_HOME = """
<label class="switch"><input type="checkbox" id="sw" onchange="toggle()"><span class="slider"></span></label>
<div>
<div class="swlabel" id="swlabel">自动同步:开</div>
<div class="swsub">开启后每 10 秒检查一次 Gitea,有新提交自动推送到 GitHub</div>
<div class="swsub" id="swsub">开启后定期检查 Gitea,有新提交自动推送到 GitHub</div>
</div>
</div>
<div class="btns">
@@ -738,9 +759,10 @@ async function refresh(){
const s=await jget('api/state');
document.getElementById('sw').checked=s.enabled;
document.getElementById('swlabel').textContent='自动同步:'+(s.enabled?'开':'关');
document.getElementById('swsub').textContent='开启后每 '+(s.interval||10)+' 秒检查一次 Gitea,有新提交自动推送到 GitHub(间隔可在设置页修改)';
const p=document.getElementById('pill');
p.textContent=s.enabled?'运行中':'已停止';p.className='pill '+(s.enabled?'on':'off');
document.getElementById('sEnabled').textContent=s.enabled?'开启(10秒/次)':'已停止';
document.getElementById('sEnabled').textContent=s.enabled?('开启('+(s.interval||10)+'秒/次)'):'已停止';
document.getElementById('sAccount').textContent=s.account?(s.account.gh_user+'('+s.account.method+')'):'未配置';
document.getElementById('sRepos').textContent=s.repo_count+' 个(已同步 '+s.synced_count+')';
document.getElementById('sUptime').textContent=s.uptime;
@@ -927,6 +949,11 @@ PAGE_SETTINGS = """
<div class="swsub">需要当前账号已配置 Token;关闭后添加不存在的仓库会提示手动创建</div>
</div>
</div>
<div class="addrow" style="margin-top:16px;align-items:center">
<input id="syncInterval" type="number" min="5" max="86400" step="1" placeholder="同步间隔(秒)" style="max-width:200px">
<button class="b-mini" id="btnInterval" onclick="saveInterval()">保存间隔</button>
<span class="tip" style="margin:0" id="intervalTip"></span>
</div>
<div class="msg" id="msgCfg"></div>
</div>
<script>
@@ -940,6 +967,10 @@ async function refresh(){
document.getElementById('giteaPass').placeholder=s.gitea_pass_set
?'Gitea 密码(已设置;留空保存可清除)':'Gitea 密码(私有仓库必填)';
document.getElementById('swAuto').checked=!!s.auto_create;
document.getElementById('syncInterval').value=s.interval||10;
document.getElementById('syncInterval').min=s.min_interval||5;
document.getElementById('syncInterval').max=s.max_interval||86400;
document.getElementById('intervalTip').textContent='每隔 '+fmtInterval(s.interval||10)+' 检查一次 Gitea 是否有新提交('+(s.min_interval||5)+'~'+(s.max_interval||86400)+' 秒),改完立即生效';
document.getElementById('tbAcc').innerHTML=(s.accounts||[]).map(a=>{
const cur=a.id===s.active;
const ops=cur
@@ -997,6 +1028,21 @@ async function saveAuto(){
const r=await (await fetch('api/settings/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({auto_create})})).json();
showMsg('msgCfg',r.msg||JSON.stringify(r),!!r.ok);refresh();
}
function fmtInterval(s){
s=parseInt(s,10)||0;
if(s>=3600)return (s/3600)+' 小时';
if(s>=60)return Math.round(s/6)/10+' 分钟';
return s+' 秒';
}
async function saveInterval(){
const v=parseInt(document.getElementById('syncInterval').value,10);
const b=document.getElementById('btnInterval');b.disabled=true;b.textContent='⏳ 保存中…';
try{
const r=await (await fetch('api/settings/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({interval:v})})).json();
showMsg('msgCfg',r.msg||JSON.stringify(r),!!r.ok);
}catch(e){showMsg('msgCfg','请求失败: '+e,false)}
finally{b.disabled=false;b.textContent='保存间隔';refresh();}
}
refresh();
</script>
"""
@@ -1051,6 +1097,7 @@ class Handler(BaseHTTPRequestHandler):
self._json({"enabled": state["enabled"], "syncing": state["syncing"],
"last_check": state["last_check"], "checks": state["checks"],
"last_result": state["last_result"],
"interval": sync_interval(),
"repo_count": len(repos),
"synced_count": sum(1 for r in repos if state_path(acc, r).exists()),
"uptime": fmt_uptime(time.time() - START_TS),
@@ -1063,6 +1110,9 @@ class Handler(BaseHTTPRequestHandler):
"gitea_pass": mask_token(cfg.get("gitea_pass")),
"gitea_pass_set": bool(cfg.get("gitea_pass")),
"auto_create": bool(cfg.get("auto_create", True)),
"interval": sync_interval(),
"min_interval": MIN_INTERVAL,
"max_interval": MAX_INTERVAL,
"gitea_url": GITEA_UI,
"active": active_id,
"accounts": [account_public(a) for a in accounts]})
@@ -1092,6 +1142,10 @@ class Handler(BaseHTTPRequestHandler):
new_user = u
if "gitea_pass" in data:
new_pass = str(data.get("gitea_pass") or "") # 空串 = 清除密码
if "interval" in data:
if not valid_interval(data.get("interval")):
self._json({"ok": False, "msg": f"同步间隔须为 {MIN_INTERVAL} ~ {MAX_INTERVAL} 之间的整数(秒)"})
return
# 预校验:涉及 Gitea 账号的改动,先用新凭据调一次 /user 验证
trial = dict(cfg)
if new_user is not None:
@@ -1123,6 +1177,11 @@ class Handler(BaseHTTPRequestHandler):
log("Gitea 密码已" + ("更新" if new_pass else "清除"))
if "auto_create" in data:
cfg["auto_create"] = bool(data.get("auto_create"))
if "interval" in data:
old_iv = sync_interval()
cfg["interval"] = int(data.get("interval"))
if cfg["interval"] != old_iv:
log(f"同步间隔已调整: {old_iv}s -> {cfg['interval']}s")
save_settings()
if cfg.get("gitea_pass"):
msg = "已保存(Gitea 密码验证通过 ✓,可同步私有仓库)"
+1 -1
View File
@@ -1,5 +1,5 @@
appname = GiteaHubSync
version = 1.1.0
version = 1.1.1
display_name = Gitea→GitHub 同步助手
desc = 每次推送到飞牛 Gitea 的代码,10 秒内自动镜像到 GitHub 的 7 个私有仓库。内置控制面板:自动同步开关、立即同步、仓库状态、同步日志,一键打开 Gitea。数据全部保存在 NAS 本地。
platform = all
+2 -1
View File
@@ -1,6 +1,6 @@
# GiteaHubSync — Gitea → GitHub 镜像同步(飞牛原生应用)
每次推送到飞牛 Gitea(3000 端口)的代码,**10 秒内自动镜像到 GitHub**(默认 7 个仓库:
每次推送到飞牛 Gitea(3000 端口)的代码,**自动镜像到 GitHub**(默认 10 秒一轮,间隔 5 秒 ~ 1 天可在设置页调整;默认 7 个仓库:
SMB_Android / DCU_APP / GD32E50X_BOOT / DCU_DesktopController / Motor_v2 / DCU_ECAT_Master / GD32_BOOT,
可任意增减)。
@@ -13,6 +13,7 @@ NAS 重启自动恢复。控制面板带自动同步开关、立即同步、仓
- **多 GitHub 账号**:设置页可添加多个 GitHub 账号(用户名 + Personal Access Token),
一键切换推送目标;每个账号独立记录同步进度,切回原账号不会重复推送
- **同步间隔可调**:设置页可改检查间隔(5 秒 ~ 1 天,默认 10 秒),改完立即生效
- **自动创建仓库**:Gitea 上的新仓库加入同步时,若 GitHub 上不存在同名仓库,
自动按账号设置为私有/公开创建(可开关)
- **两种推送方式**:配置了 Token 的账号走 HTTPS 推送(通用,任何人都能用自己的