feat: v1.4.1 — 数值设置改动即生效,去掉保存按钮
- 同步间隔/并发数/重试次数三个数值输入改 onchange 自动保存: 改完失焦或按回车即生效并显示「✓ 已生效」(2 秒后淡出), 不再需要点保存按钮 - 非法值(超范围/非数字)自动回滚成服务器值并红字提示原因 - 防重复保存:值没变不触发请求;空值前端拦截不发请求 - 修复 saveInterval 等包装函数未 return 内部 Promise 导致调用方 无法等待保存完成的问题 - manifest 1.4.0 → 1.4.1
This commit is contained in:
@@ -952,6 +952,10 @@ h2 .h2sub{font-size:12px;font-weight:400;color:#8a919e;margin-left:10px}
|
||||
.opttext .swsub{font-size:12px;color:#646a73;margin-top:2px;line-height:1.6}
|
||||
.numin{flex:none;width:110px;border:1px solid #d0d5dd;border-radius:10px;padding:10px 12px;font-size:14px;outline:none;text-align:center}
|
||||
.numin:focus{border-color:#7c3aed}
|
||||
.numwrap{display:flex;align-items:center;gap:8px;flex:none}
|
||||
.saved-tip{font-size:12px;font-weight:600;white-space:nowrap}
|
||||
.saved-tip.ok{color:#0a7d34}
|
||||
.saved-tip.err{color:#b91c1c}
|
||||
table.hover tbody tr:hover{background:#f8fafc}
|
||||
td.ops{white-space:nowrap}
|
||||
td.ops button,td.ops .pill{margin:2px 4px 2px 0}
|
||||
@@ -1263,24 +1267,30 @@ PAGE_SETTINGS = """
|
||||
</div>
|
||||
</div>
|
||||
<div class="optrow">
|
||||
<input id="syncInterval" type="number" min="5" max="86400" step="1" class="numin">
|
||||
<button class="b-mini" id="btnInterval" onclick="saveInterval()">保存间隔</button>
|
||||
<div class="numwrap">
|
||||
<input id="syncInterval" type="number" min="5" max="86400" step="1" class="numin" onchange="saveInterval()">
|
||||
<span class="saved-tip" id="intervalSaved"></span>
|
||||
</div>
|
||||
<div class="opttext">
|
||||
<div class="swlabel">同步间隔(秒)</div>
|
||||
<div class="swsub" id="intervalTip"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="optrow">
|
||||
<input id="syncWorkers" type="number" min="1" max="8" step="1" class="numin">
|
||||
<button class="b-mini" id="btnWorkers" onclick="saveWorkers()">保存并发</button>
|
||||
<div class="numwrap">
|
||||
<input id="syncWorkers" type="number" min="1" max="8" step="1" class="numin" onchange="saveWorkers()">
|
||||
<span class="saved-tip" id="workersSaved"></span>
|
||||
</div>
|
||||
<div class="opttext">
|
||||
<div class="swlabel">同时同步项目数</div>
|
||||
<div class="swsub" id="workersTip"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="optrow">
|
||||
<input id="syncRetries" type="number" min="0" max="20" step="1" class="numin">
|
||||
<button class="b-mini" id="btnRetries" onclick="saveRetries()">保存次数</button>
|
||||
<div class="numwrap">
|
||||
<input id="syncRetries" type="number" min="0" max="20" step="1" class="numin" onchange="saveRetries()">
|
||||
<span class="saved-tip" id="retriesSaved"></span>
|
||||
</div>
|
||||
<div class="opttext">
|
||||
<div class="swlabel">失败重试次数</div>
|
||||
<div class="swsub" id="retriesTip"></div>
|
||||
@@ -1293,6 +1303,30 @@ PAGE_SETTINGS = """
|
||||
async function jget(u){const r=await fetch(u);return r.json()}
|
||||
function esc(s){const d=document.createElement('div');d.textContent=s==null?'':String(s);return d.innerHTML}
|
||||
function showMsg(id,t,ok){const m=document.getElementById(id);m.textContent=t;m.className='msg '+(ok?'ok':'err');}
|
||||
// ---- 数值设置:改动即生效(失焦/回车自动保存,无需点按钮) ----
|
||||
let lastVals={};
|
||||
function valChanged(inputId,key){const v=document.getElementById(inputId).value;if(lastVals[key]===v)return false;lastVals[key]=v;return true;}
|
||||
function flashSaved(id,ok,msg){
|
||||
const el=document.getElementById(id);
|
||||
el.textContent=ok?'✓ 已生效':('✗ '+(msg||'保存失败'));
|
||||
el.className='saved-tip '+(ok?'ok':'err');
|
||||
if(ok)setTimeout(()=>{if(el.textContent==='✓ 已生效')el.textContent='';},2000);
|
||||
}
|
||||
async function saveSetting(key,inputId,tipId,field){
|
||||
if(!valChanged(inputId,key))return; // 没改动不重复保存
|
||||
const v=parseInt(document.getElementById(inputId).value,10);
|
||||
if(isNaN(v)){flashSaved(tipId,false,'请输入有效数字');return;}
|
||||
let r;
|
||||
try{
|
||||
r=await (await fetch('api/settings/save',{method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({[field]:v})})).json();
|
||||
}catch(e){flashSaved(tipId,false,'网络错误');return;}
|
||||
await refresh(); // 刷新后非法值会自动回滚成服务器值
|
||||
flashSaved(tipId,!!r.ok,r.msg);
|
||||
}
|
||||
async function saveInterval(){return saveSetting('interval','syncInterval','intervalSaved','interval');}
|
||||
async function saveWorkers(){return saveSetting('workers','syncWorkers','workersSaved','max_workers');}
|
||||
async function saveRetries(){return saveSetting('retries','syncRetries','retriesSaved','max_retries');}
|
||||
async function refresh(){
|
||||
try{
|
||||
const s=await jget('api/settings');
|
||||
@@ -1304,13 +1338,14 @@ async function refresh(){
|
||||
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;
|
||||
document.getElementById('intervalTip').textContent='每隔 '+fmtInterval(s.interval||10)+' 检查一次 Gitea 是否有新提交('+(s.min_interval||5)+'~'+(s.max_interval||86400)+' 秒),改完立即生效';
|
||||
document.getElementById('intervalTip').textContent='每隔 '+fmtInterval(s.interval||10)+' 检查一次 Gitea 是否有新提交('+(s.min_interval||5)+'~'+(s.max_interval||86400)+' 秒),改动即生效';
|
||||
const wk=document.getElementById('syncWorkers');
|
||||
wk.value=s.max_workers||3;wk.min=s.min_workers||1;wk.max=s.max_workers_limit||8;
|
||||
document.getElementById('workersTip').textContent='每轮最多 '+(s.max_workers||3)+' 个项目同时克隆/推送(1~'+(s.max_workers_limit||8)+'),项目多时能显著加快;网络或 NAS 负载高就调小';
|
||||
const rt=document.getElementById('syncRetries');
|
||||
rt.value=(s.max_retries==null?5:s.max_retries);rt.min=0;rt.max=20;
|
||||
document.getElementById('retriesTip').textContent='同步失败后每隔 '+(s.retry_interval||60)+' 秒自动重试失败的仓库,最多 '+(s.max_retries==null?5:s.max_retries)+' 次(0 = 不重试);与同步间隔无关,专治网络抖动';
|
||||
lastVals={interval:iv.value,workers:wk.value,retries:rt.value};
|
||||
const cur=(s.accounts||[]).find(a=>a.id===s.active);
|
||||
// 当前账号无 Token 时,顶部提示为什么不能自动建库、怎么解锁
|
||||
document.getElementById('accBanner').innerHTML=(cur&&!cur.has_token)
|
||||
@@ -1400,33 +1435,6 @@ function fmtInterval(s){
|
||||
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();}
|
||||
}
|
||||
async function saveWorkers(){
|
||||
const v=parseInt(document.getElementById('syncWorkers').value,10);
|
||||
const b=document.getElementById('btnWorkers');b.disabled=true;b.textContent='⏳ 保存中…';
|
||||
try{
|
||||
const r=await (await fetch('api/settings/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({max_workers:v})})).json();
|
||||
showMsg('msgCfg',r.msg||JSON.stringify(r),!!r.ok);
|
||||
}catch(e){showMsg('msgCfg','请求失败: '+e,false)}
|
||||
finally{b.disabled=false;b.textContent='保存并发';refresh();}
|
||||
}
|
||||
async function saveRetries(){
|
||||
const v=parseInt(document.getElementById('syncRetries').value,10);
|
||||
const b=document.getElementById('btnRetries');b.disabled=true;b.textContent='⏳ 保存中…';
|
||||
try{
|
||||
const r=await (await fetch('api/settings/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({max_retries: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>
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
appname = GiteaHubSync
|
||||
version = 1.4.0
|
||||
version = 1.4.1
|
||||
display_name = Gitea→GitHub 同步助手
|
||||
desc = 每次推送到飞牛 Gitea 的代码,10 秒内自动镜像到 GitHub 的 7 个私有仓库。内置控制面板:自动同步开关、立即同步、仓库状态、同步日志,一键打开 Gitea。数据全部保存在 NAS 本地。
|
||||
platform = all
|
||||
|
||||
@@ -16,6 +16,8 @@ NAS 重启自动恢复。控制面板带自动同步开关、立即同步、仓
|
||||
- **同步间隔可调**:设置页可改检查间隔(5 秒 ~ 1 天,默认 10 秒),改完立即生效
|
||||
- **多项目并行同步**:每轮最多 N 个项目同时克隆/推送(默认 3,可调 1~8);
|
||||
在状态页点单个仓库的「同步」也不再被整轮同步阻塞(同一仓库有防重入)
|
||||
- **设置改动即生效**:同步间隔 / 并发数 / 重试次数等数值设置改完自动保存
|
||||
(失焦或回车即生效,无需点按钮),非法值自动回滚并提示
|
||||
- **失败自动重试**:同步失败后每隔 1 分钟自动重试失败的仓库,最多 N 次
|
||||
(默认 5,可调 0~20,0 = 不重试);重试与同步间隔无关,专治网络抖动,
|
||||
重试中的仓库在状态页显示「重试中 x/N」
|
||||
|
||||
Reference in New Issue
Block a user