From ca2fb7e73570f5f78f9cbf07bc07cf76bd5f4176 Mon Sep 17 00:00:00 2001 From: cbb Date: Thu, 15 Oct 2026 01:03:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20v1.5.0=20=E2=80=94=20GitHub=20=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E6=B7=BB=E5=8A=A0/=E7=BC=96=E8=BE=91=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=BC=B9=E7=AA=97=E5=BC=8F=EF=BC=88=E5=B8=A6=E2=9C=8F?= =?UTF-8?q?=EF=B8=8F=E7=BC=96=E8=BE=91=E6=8C=89=E9=92=AE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 「添加 GitHub 账号」改为卡片里的按钮,点击弹出填写弹窗 (模态:备注/用户名/Token/私有,含取消/验证并添加/ESC/点背景关闭) - 表格每行新增 ✏️ 编辑按钮:弹出编辑弹窗,预填当前值 (Token 字段空着+占位符显示当前掩码,留空则保留原 Token, 只改备注/私有时无需重新输入 Token) - 后端新增 /api/accounts/edit 端点(按 id 改备注/私有/Token), add 改为严格新增(gh_user 重复则拒绝并引导用编辑功能) - 顶部横幅「去添加 Token」直接打开当前账号的编辑弹窗并聚焦 Token - 移除了原来表内的「添加 Token」按钮(编辑弹窗已覆盖该流程) - manifest 1.4.1 → 1.5.0 --- GiteaHubSync/app/backend/panel.py | 235 +++++++++++++++++++++++------- GiteaHubSync/manifest | 2 +- 2 files changed, 187 insertions(+), 50 deletions(-) diff --git a/GiteaHubSync/app/backend/panel.py b/GiteaHubSync/app/backend/panel.py index 64527fc..3097665 100644 --- a/GiteaHubSync/app/backend/panel.py +++ b/GiteaHubSync/app/backend/panel.py @@ -956,6 +956,29 @@ h2 .h2sub{font-size:12px;font-weight:400;color:#8a919e;margin-left:10px} .saved-tip{font-size:12px;font-weight:600;white-space:nowrap} .saved-tip.ok{color:#0a7d34} .saved-tip.err{color:#b91c1c} +input[readonly]{background:#f6f8fa;color:#646a73;cursor:not-allowed} +.b-icon{padding:5px 9px;min-width:auto;font-size:13px} +/* ---------- 模态弹窗 ---------- */ +.modal{display:none;position:fixed;inset:0;background:rgba(15,23,42,.45);z-index:1000;align-items:center;justify-content:center;padding:20px} +.modal.show{display:flex} +.modal-box{background:#fff;border-radius:14px;width:100%;max-width:480px;box-shadow:0 20px 60px rgba(0,0,0,.25);max-height:90vh;overflow:auto;animation:modalIn .15s ease-out} +@keyframes modalIn{from{transform:translateY(-8px);opacity:.6}to{transform:translateY(0);opacity:1}} +.modal-head{display:flex;align-items:center;justify-content:space-between;padding:18px 22px;border-bottom:1px solid #eef0f3} +.modal-head h3{font-size:17px;margin:0} +.modal-close{background:none;border:none;font-size:24px;color:#646a73;cursor:pointer;padding:2px 10px;border-radius:6px;line-height:1} +.modal-close:hover{background:#f0f2f5;color:#1f2329} +.modal-body{padding:22px;display:flex;flex-direction:column;gap:8px} +.modal-body input[type=text],.modal-body input[type=password]{ + border:1px solid #d0d5dd;border-radius:10px;padding:11px 14px;font-size:14px;outline:none;width:100%;box-sizing:border-box;font-family:inherit} +.modal-body input:focus{border-color:#7c3aed} +.modal-foot{padding:14px 22px;border-top:1px solid #eef0f3;display:flex;align-items:center;gap:10px;flex-wrap:wrap} +.modal-msg{font-size:13px;flex:1;min-width:0;word-break:break-word} +.modal-msg.ok{color:#0a7d34} +.modal-msg.err{color:#b91c1c} +.modal-msg:empty{display:none} +@media (max-width:760px){ + .modal-box{max-width:none} +} table.hover tbody tr:hover{background:#f8fafc} td.ops{white-space:nowrap} td.ops button,td.ops .pill{margin:2px 4px 2px 0} @@ -1219,21 +1242,9 @@ PAGE_SETTINGS = """

添加 / 更新 GitHub 账号Token 验证通过后自动识别用户名

- -
- -
- -
- -
- -
-
- -
-
-
+

添加多个账号后可随时切换推送目标,每个账号独立记录同步进度。已存在的账号可点表格行右边的 ✏️ 编辑。

+ +
Token 获取:GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token,勾选 repo 权限(即可自动创建仓库并推送)。
链接:github.com/settings/tokens · @@ -1352,15 +1363,16 @@ async function refresh(){ ?`` +
` :''; + allAccounts=s.accounts||[]; document.getElementById('tbAcc').innerHTML=(s.accounts||[]).map(a=>{ const isCur=a.id===s.active; const ops=isCur ? '当前使用' : ``; const verify=a.has_token?``:''; - const upgrade=a.has_token?'':``; + const edit=``; const del=(a.has_token||(s.accounts||[]).length>1) ? ``:''; const method=a.has_token @@ -1373,33 +1385,78 @@ async function refresh(){ +`${esc(a.token||'(未配置)')}` +`${method}` +`${create}` - +`${ops} ${verify} ${upgrade} ${del}`; + +`${ops} ${edit} ${verify} ${del}`; }).join('')||'暂无账号'; }catch(e){showMsg('msgAcc','刷新失败: '+e,false)} } -function upgradeAcc(id,ghUser){ - // 预填「添加/更新账号」表单并滚动过去,引导给旧账号补 Token - document.getElementById('accLabel').value=''; - document.getElementById('accUser').value=ghUser||''; - document.getElementById('accToken').value=''; - document.getElementById('accToken').focus(); - document.getElementById('accToken').scrollIntoView({behavior:'smooth',block:'center'}); - showMsg('msgAcc','已为你填好用户名 '+ghUser+',把 Token 粘贴进去点「验证并添加」即可(会更新原账号)',true); +// ---- GitHub 账号:弹窗式添加/编辑 ---- +let allAccounts=[]; +function showModalMsg(id,t,ok){ + const m=document.getElementById(id); + m.textContent=t||''; m.className='modal-msg'+(ok===true?' ok':(ok===false?' err':'')); } -async function addAccount(){ - const label=document.getElementById('accLabel').value.trim(); - const gh_user=document.getElementById('accUser').value.trim(); - const token=document.getElementById('accToken').value.trim(); - const priv=document.getElementById('accPrivate').checked; - if(!token){showMsg('msgAcc','请填写 Personal Access Token',false);return;} - const b=document.getElementById('btnAccAdd');b.disabled=true;b.textContent='⏳ 验证中…'; +function closeModal(id){ document.getElementById(id).classList.remove('show'); } +function openAddModal(){ + document.getElementById('addLabel').value=''; + document.getElementById('addUser').value=''; + document.getElementById('addToken').value=''; + document.getElementById('addPrivate').checked=true; + showModalMsg('addMsg','',null); + document.getElementById('modalAdd').classList.add('show'); + setTimeout(()=>document.getElementById('addLabel').focus(),50); +} +function openEditModal(id, focusToken){ + const acc=allAccounts.find(a=>a.id===id); + if(!acc)return; + document.getElementById('modalEdit').dataset.id=id; + document.getElementById('editLabel').value=acc.label||''; + document.getElementById('editUser').value=acc.gh_user||''; + document.getElementById('editToken').value=''; + document.getElementById('editToken').placeholder=acc.token?'当前:'+acc.token:'(未配置)'; + document.getElementById('editPrivate').checked=!!acc.private; + showModalMsg('editMsg','',null); + document.getElementById('modalEdit').classList.add('show'); + setTimeout(()=>{ + const el=focusToken?document.getElementById('editToken'):document.getElementById('editLabel'); + el.focus(); + },50); +} +function upgradeAcc(id){ + // 顶部横幅「去添加 Token」:直接打开当前账号的编辑弹窗,聚焦 Token + openEditModal(id, true); +} +async function saveAdd(){ + const body={ + label: document.getElementById('addLabel').value.trim(), + gh_user: document.getElementById('addUser').value.trim(), + token: document.getElementById('addToken').value.trim(), + private: document.getElementById('addPrivate').checked, + }; + if(!body.token){ showModalMsg('addMsg','请填写 Personal Access Token',false); return; } + const b=document.getElementById('btnAddSave'); b.disabled=true; b.textContent='⏳ 验证中…'; try{ - const r=await (await fetch('api/accounts/add',{method:'POST',headers:{'Content-Type':'application/json'}, - body:JSON.stringify({label,gh_user,token,private:priv})})).json(); - showMsg('msgAcc',r.msg||JSON.stringify(r),!!r.ok); - if(r.ok){document.getElementById('accLabel').value='';document.getElementById('accUser').value='';document.getElementById('accToken').value='';} - }catch(e){showMsg('msgAcc','请求失败: '+e,false)} - finally{b.disabled=false;b.textContent='✓ 验证并添加';refresh();} + const r=await (await fetch('api/accounts/add',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})).json(); + showModalMsg('addMsg', r.msg||'', !!r.ok); + if(r.ok){ setTimeout(()=>{ closeModal('modalAdd'); refresh(); }, 700); } + }catch(e){ showModalMsg('addMsg','请求失败: '+e, false); } + finally{ b.disabled=false; b.textContent='✓ 验证并添加'; } +} +async function saveEdit(){ + const id=document.getElementById('modalEdit').dataset.id; + if(!id){ showModalMsg('editMsg','内部错误:未选中账号',false); return; } + const body={ + id, + label: document.getElementById('editLabel').value.trim(), + token: document.getElementById('editToken').value.trim(), // 空 = 不改 + private: document.getElementById('editPrivate').checked, + }; + const b=document.getElementById('btnEditSave'); b.disabled=true; b.textContent='⏳ 保存中…'; + try{ + const r=await (await fetch('api/accounts/edit',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})).json(); + showModalMsg('editMsg', r.msg||'', !!r.ok); + if(r.ok){ setTimeout(()=>{ closeModal('modalEdit'); refresh(); }, 700); } + }catch(e){ showModalMsg('editMsg','请求失败: '+e, false); } + finally{ b.disabled=false; b.textContent='保存'; } } async function switchAcc(id){ const r=await (await fetch('api/accounts/switch',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id})})).json(); @@ -1437,6 +1494,64 @@ function fmtInterval(s){ } refresh(); + +// ---- 模态弹窗:添加 ---- + + +// ---- 模态弹窗:编辑 ---- + + + """ @@ -1633,6 +1748,7 @@ class Handler(BaseHTTPRequestHandler): notes.append(f"注意:在新地址上找不到用户 {cfg['gitea_user']},请检查") self._json({"ok": True, "msg": "已保存(" + ";".join(notes) + ")"}) elif path == "/api/accounts/add": + # 严格新增:Token 对应的 gh_user 若已存在则拒绝,引导用户用编辑功能 token = (data.get("token") or "").strip() label = (data.get("label") or "").strip() gh_user = (data.get("gh_user") or "").strip() @@ -1647,16 +1763,9 @@ class Handler(BaseHTTPRequestHandler): if gh_user and gh_user != login: self._json({"ok": False, "msg": f"Token 对应的账号是 {login},与填写的 {gh_user} 不一致"}) return - existing = next((a for a in accounts if a["gh_user"] == login), None) - if existing: - existing["token"] = token - existing["private"] = private - if label: - existing["label"] = label - active_id = existing["id"] - save_accounts() - log(f"GitHub 账号 {login} 的 Token 已更新") - self._json({"ok": True, "msg": f"账号 {login} 已存在,Token 已更新并切换为当前账号"}) + if next((a for a in accounts if a["gh_user"] == login), None): + self._json({"ok": False, + "msg": f"GitHub 账号 {login} 已存在,请用编辑功能(每行右边的✏️)更新 Token/备注"}) return acc = {"id": uuid.uuid4().hex[:8], "label": label or login, "gh_user": login, "token": token, "private": private, @@ -1670,6 +1779,34 @@ class Handler(BaseHTTPRequestHandler): log(f"新增 GitHub 账号 {login}({'私有' if private else '公开'}仓库)") self._json({"ok": True, "msg": f"账号 {login} 验证通过,已添加" + ("并切换为当前账号" if active_id == acc["id"] else "")}) + elif path == "/api/accounts/edit": + # 按 id 编辑已有账号:token 留空则保留,可改 label / private + aid = (data.get("id") or "").strip() + acc = find_account(aid) + if not acc: + self._json({"ok": False, "msg": "账号不存在"}) + return + new_token = (data.get("token") or "").strip() + if new_token: + ok, login, err = gh_verify_token(new_token) + if not ok: + self._json({"ok": False, "msg": f"Token 验证失败: {err}"}) + return + if login != acc["gh_user"]: + self._json({"ok": False, + "msg": f"Token 对应的账号是 {login},与当前账号 {acc['gh_user']} 不一致"}) + return + acc["token"] = new_token + log(f"GitHub 账号 {acc['gh_user']} 的 Token 已通过编辑更新") + if "private" in data: + acc["private"] = bool(data.get("private")) + if "label" in data: + label = (data.get("label") or "").strip() + if label: + acc["label"] = label + save_accounts() + self._json({"ok": True, + "msg": f"账号 {acc['gh_user']} 已保存" + ("(Token 已更新)" if new_token else "")}) elif path == "/api/accounts/switch": aid = (data.get("id") or "").strip() if not find_account(aid): diff --git a/GiteaHubSync/manifest b/GiteaHubSync/manifest index b16e4e7..f0716e1 100644 --- a/GiteaHubSync/manifest +++ b/GiteaHubSync/manifest @@ -1,5 +1,5 @@ appname = GiteaHubSync -version = 1.4.1 +version = 1.5.0 display_name = Gitea→GitHub 同步助手 desc = 每次推送到飞牛 Gitea 的代码,10 秒内自动镜像到 GitHub 的 7 个私有仓库。内置控制面板:自动同步开关、立即同步、仓库状态、同步日志,一键打开 Gitea。数据全部保存在 NAS 本地。 platform = all