feat: v1.5.0 — GitHub 账号添加/编辑改为弹窗式(带✏️编辑按钮)
- 「添加 GitHub 账号」改为卡片里的按钮,点击弹出填写弹窗
(模态:备注/用户名/Token/私有,含取消/验证并添加/ESC/点背景关闭)
- 表格每行新增 ✏️ 编辑按钮:弹出编辑弹窗,预填当前值
(Token 字段空着+占位符显示当前掩码,留空则保留原 Token,
只改备注/私有时无需重新输入 Token)
- 后端新增 /api/accounts/edit 端点(按 id 改备注/私有/Token),
add 改为严格新增(gh_user 重复则拒绝并引导用编辑功能)
- 顶部横幅「去添加 Token」直接打开当前账号的编辑弹窗并聚焦 Token
- 移除了原来表内的「添加 Token」按钮(编辑弹窗已覆盖该流程)
- manifest 1.4.1 → 1.5.0
This commit is contained in:
@@ -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 = """
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>添加 / 更新 GitHub 账号<span class="h2sub">Token 验证通过后自动识别用户名</span></h2>
|
||||
<label class="flabel">备注名<span class="sub">方便自己识别,可留空</span></label>
|
||||
<div class="addrow"><input id="accLabel" placeholder="如:我的主账号"></div>
|
||||
<label class="flabel">GitHub 用户名<span class="sub">留空则按 Token 自动识别</span></label>
|
||||
<div class="addrow"><input id="accUser" placeholder="如 cbb2625274797"></div>
|
||||
<label class="flabel">Personal Access Token</label>
|
||||
<div class="addrow"><input id="accToken" type="password" placeholder="ghp_ 开头"></div>
|
||||
<label class="flabel">自动创建的仓库设为</label>
|
||||
<div class="addrow" style="align-items:center">
|
||||
<label class="chk"><input type="checkbox" id="accPrivate" checked> 私有(推荐)</label>
|
||||
</div>
|
||||
<div class="btns" style="margin-top:14px">
|
||||
<button class="b-add" id="btnAccAdd" onclick="addAccount()">✓ 验证并添加</button>
|
||||
</div>
|
||||
<div class="msg" id="msgAcc"></div>
|
||||
<div class="tip">
|
||||
<p style="color:#646a73;margin-bottom:14px">添加多个账号后可随时切换推送目标,每个账号独立记录同步进度。已存在的账号可点表格行右边的 ✏️ 编辑。</p>
|
||||
<button class="b-add" onclick="openAddModal()" style="font-size:15px;padding:12px 24px">➕ 添加 GitHub 账号</button>
|
||||
<div class="tip" style="margin-top:16px">
|
||||
Token 获取:GitHub → Settings → Developer settings → Personal access tokens →
|
||||
<b>Tokens (classic)</b> → Generate new token,勾选 <b>repo</b> 权限(即可自动创建仓库并推送)。<br>
|
||||
链接:<a href="https://github.com/settings/tokens" target="_blank">github.com/settings/tokens</a> ·
|
||||
@@ -1352,15 +1363,16 @@ async function refresh(){
|
||||
?`<div class="banner warn"><span class="ic">⚠️</span><div><b>当前账号「${esc(cur.label)}」通过 SSH 私钥推送,暂时无法自动创建仓库。</b>
|
||||
<br>GitHub 的创建仓库接口只认 Token 认证,SSH 私钥只能推送<b>已存在</b>的仓库——这是 GitHub 的限制,不是软件没做。
|
||||
<br>给这个账号配上 Token 即可解锁(推送也会改走 HTTPS,更稳更快):
|
||||
<button class="b-mini" style="margin-left:6px" onclick="upgradeAcc('${esc(cur.id)}','${esc(cur.gh_user)}')">去添加 Token</button></div></div>`
|
||||
<button class="b-mini" style="margin-left:6px" onclick="upgradeAcc('${esc(cur.id)}')">去添加 Token</button></div></div>`
|
||||
:'';
|
||||
allAccounts=s.accounts||[];
|
||||
document.getElementById('tbAcc').innerHTML=(s.accounts||[]).map(a=>{
|
||||
const isCur=a.id===s.active;
|
||||
const ops=isCur
|
||||
? '<span class="pill on">当前使用</span>'
|
||||
: `<button class="b-mini" onclick="switchAcc('${esc(a.id)}')">切换到此账号</button>`;
|
||||
const verify=a.has_token?`<button class="b-mini" onclick="verifyAcc('${esc(a.id)}')">验证</button>`:'';
|
||||
const upgrade=a.has_token?'':`<button class="b-mini" onclick="upgradeAcc('${esc(a.id)}','${esc(a.gh_user)}')">添加 Token</button>`;
|
||||
const edit=`<button class="b-mini b-icon" title="编辑" onclick="openEditModal('${esc(a.id)}')">✏️</button>`;
|
||||
const del=(a.has_token||(s.accounts||[]).length>1)
|
||||
? `<button class="b-del" onclick="delAcc('${esc(a.id)}')">删除</button>`:'';
|
||||
const method=a.has_token
|
||||
@@ -1373,33 +1385,78 @@ async function refresh(){
|
||||
+`<td data-label="Token" class="mono">${esc(a.token||'(未配置)')}</td>`
|
||||
+`<td data-label="推送方式">${method}</td>`
|
||||
+`<td data-label="自动建库">${create}</td>`
|
||||
+`<td data-label="操作" class="ops">${ops} ${verify} ${upgrade} ${del}</td></tr>`;
|
||||
+`<td data-label="操作" class="ops">${ops} ${edit} ${verify} ${del}</td></tr>`;
|
||||
}).join('')||'<tr><td colspan="6" style="color:#646a73">暂无账号</td></tr>';
|
||||
}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();
|
||||
</script>
|
||||
|
||||
// ---- 模态弹窗:添加 ----
|
||||
<div class="modal" id="modalAdd">
|
||||
<div class="modal-box">
|
||||
<div class="modal-head">
|
||||
<h3>添加 GitHub 账号</h3>
|
||||
<button class="modal-close" onclick="closeModal('modalAdd')" aria-label="关闭">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label class="flabel" style="margin-top:0">备注名<span class="sub">方便自己识别,可留空</span></label>
|
||||
<input id="addLabel" placeholder="如:我的主账号" autocomplete="off">
|
||||
<label class="flabel">GitHub 用户名<span class="sub">留空则按 Token 自动识别</span></label>
|
||||
<input id="addUser" placeholder="如 cbb2625274797" autocomplete="off">
|
||||
<label class="flabel">Personal Access Token</label>
|
||||
<input id="addToken" type="password" placeholder="ghp_ 开头" autocomplete="off">
|
||||
<label class="chk" style="margin-top:8px"><input type="checkbox" id="addPrivate" checked> 自动创建的仓库设为私有(推荐)</label>
|
||||
</div>
|
||||
<div class="modal-foot">
|
||||
<div class="modal-msg" id="addMsg"></div>
|
||||
<button class="b-mini" onclick="closeModal('modalAdd')">取消</button>
|
||||
<button class="b-add" id="btnAddSave" onclick="saveAdd()">✓ 验证并添加</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// ---- 模态弹窗:编辑 ----
|
||||
<div class="modal" id="modalEdit">
|
||||
<div class="modal-box">
|
||||
<div class="modal-head">
|
||||
<h3>编辑 GitHub 账号</h3>
|
||||
<button class="modal-close" onclick="closeModal('modalEdit')" aria-label="关闭">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label class="flabel" style="margin-top:0">备注名</label>
|
||||
<input id="editLabel" autocomplete="off">
|
||||
<label class="flabel">GitHub 用户名<span class="sub">账号标识,不可修改</span></label>
|
||||
<input id="editUser" readonly tabindex="-1">
|
||||
<label class="flabel">Personal Access Token<span class="sub">留空则保留当前 Token</span></label>
|
||||
<input id="editToken" type="password" placeholder="ghp_ 开头(留空不修改)" autocomplete="off">
|
||||
<label class="chk" style="margin-top:8px"><input type="checkbox" id="editPrivate"> 自动创建的仓库设为私有(推荐)</label>
|
||||
</div>
|
||||
<div class="modal-foot">
|
||||
<div class="modal-msg" id="editMsg"></div>
|
||||
<button class="b-mini" onclick="closeModal('modalEdit')">取消</button>
|
||||
<button class="b-add" id="btnEditSave" onclick="saveEdit()">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 弹窗:点遮罩关闭 + ESC 关闭
|
||||
document.querySelectorAll('.modal').forEach(m=>{
|
||||
m.addEventListener('click',e=>{ if(e.target===m) m.classList.remove('show'); });
|
||||
});
|
||||
document.addEventListener('keydown',e=>{
|
||||
if(e.key==='Escape') document.querySelectorAll('.modal.show').forEach(m=>m.classList.remove('show'));
|
||||
});
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user