Files
MC_Report/public/js/pages/sources.js
canglan 65edbaf157 refactor: session-based external auth, dynamic sources, drop netease UID
Auth (external API):
- ID: 16-digit random (non-sequential); Secret: SeaReport- + 32 hex
- POST /auth/session: ID+Secret -> Bearer SESSION (24h, single-session,
  old session invalidated on re-issue, disabled client invalidates)
- clientAuth now validates Bearer SESSION via api_sessions JOIN api_clients

Sources (dynamic, no default, open-source friendly):
- sources table + CRUD route (/api/sources, owner; delete guarded by usage)
- users/user_identities.source ENUM -> VARCHAR, seeded netease/skin
- register/admin create/identity bind: validate against enabled sources
- UI: 来源管理 page; source dropdowns loaded dynamically everywhere
  (register, dashboard identity, users admin, bans), labels dynamic

UID removal:
- game_uid/reporter_game_uid no longer required (db default '', validations
  dropped, frontend fields optional)

Docs: EXTERNAL-API.md session flow + new credential format; API.md updated
Verified: 37 checks (syntax, session logic, source CRUD, UID removal, docs)
2026-08-19 20:08:06 +08:00

86 lines
4.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const SourcesPage = {
async render() { return '<div class="loading"><i class="fas fa-spinner"></i></div>'; },
async mount() {
document.getElementById('page-title').textContent = '来源管理';
document.getElementById('page-actions').innerHTML = '<button class="btn btn-p btn-sm" onclick="SourcesPage.showAdd()"><i class="fas fa-plus"></i> 添加来源</button>';
await this.load();
},
async load() {
const ct = document.getElementById('page-content');
try {
// 管理视图: 含停用项
const rows = await API.get('/sources');
ct.innerHTML = rows.length === 0
? '<div class="empty"><i class="fas fa-tag"></i><p>暂无来源</p></div>'
: `<div class="card table-wrap"><table><thead><tr><th>code</th><th>名称</th><th>排序</th><th>状态</th><th>操作</th></tr></thead><tbody>${rows.map(s => `
<tr>
<td><code>${U.esc(s.code)}</code></td>
<td>${U.esc(s.label)}</td>
<td>${s.sort_order}</td>
<td>${s.enabled ? '<span class="badge bg-resolved">启用</span>' : '<span class="badge bg-rejected">停用</span>'}</td>
<td>
<button class="btn btn-o btn-sm" onclick="SourcesPage.showEdit('${U.escJs(s.code)}','${U.escJs(s.label)}',${s.enabled},${s.sort_order})"><i class="fas fa-edit"></i> 编辑</button>
<button class="btn btn-d btn-sm" onclick="SourcesPage.del('${U.escJs(s.code)}')"><i class="fas fa-trash"></i></button>
</td>
</tr>`).join('')}</tbody></table></div>
<p class="help" style="margin-top:8px">来源用于标识玩家身份归属(如: 网易端/皮肤站/正版/离线)。已被身份数据使用的来源不能删除, 只能停用。</p>`;
} catch (e) { ct.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
},
showAdd() {
U.modal('添加来源', `
<form id="src-form">
<div class="grid-2">
<div class="form-group"><label>code *</label><input id="src-code" required placeholder="如: official" pattern="[a-z0-9_]{1,20}"></div>
<div class="form-group"><label>名称 *</label><input id="src-label" required placeholder="如: 正版"></div>
</div>
<div class="form-group"><label>排序</label><input id="src-sort" type="number" value="0"></div>
<div class="modal-f"><button type="button" class="btn btn-o" data-action="App:closeModal">取消</button><button type="submit" class="btn btn-p">添加</button></div>
</form>
`);
document.getElementById('src-form').onsubmit = async e => {
e.preventDefault();
try {
await API.post('/sources', {
code: document.getElementById('src-code').value,
label: document.getElementById('src-label').value,
sort_order: document.getElementById('src-sort').value,
});
App.closeModal(); this.load();
} catch(ex){alert(ex.message);}
};
},
showEdit(code, label, enabled, sortOrder) {
U.modal('编辑来源', `
<form id="src-edit-form">
<div class="form-group"><label>code</label><input value="${U.esc(code)}" disabled></div>
<div class="grid-2">
<div class="form-group"><label>名称 *</label><input id="se-label" required value="${U.esc(label)}"></div>
<div class="form-group"><label>排序</label><input id="se-sort" type="number" value="${sortOrder}"></div>
</div>
<div class="form-group"><label>状态</label><select id="se-enabled"><option value="1" ${enabled?'selected':''}>启用</option><option value="0" ${!enabled?'selected':''}>停用</option></select></div>
<div class="modal-f"><button type="button" class="btn btn-o" data-action="App:closeModal">取消</button><button type="submit" class="btn btn-p">保存</button></div>
</form>
`);
document.getElementById('src-edit-form').onsubmit = async e => {
e.preventDefault();
try {
await API.put(`/sources/${code}`, {
label: document.getElementById('se-label').value,
enabled: document.getElementById('se-enabled').value === '1',
sort_order: document.getElementById('se-sort').value,
});
App.closeModal(); this.load();
} catch(ex){alert(ex.message);}
};
},
async del(code) {
if (!await U.confirm(`确认删除来源 ${code}`)) return;
try { await API.delete(`/sources/${code}`); this.load(); } catch(ex){alert(ex.message);}
}
};