Files
MC_Report/public/js/pages/servers.js
canglan 257cd14051 feat: external API for QQ bot - client id+secret auth, ticket tracking, bans, per-server data
- db: api_clients table + server_groups.alias + tickets/bans server_name (auto-migrate)
- external.js: client_id+secret auth (multi-client, bcrypt, show-once secret),
  legacy x-external-key compat, JWT pass-through
  - POST /tickets (optional server), GET /tickets/track?token= (full lifecycle)
  - GET/POST /bans, reported-players/stats/all-tickets with server filter
  - GET /servers (alias list), client CRUD (owner/admin, audit logged)
  - resolveServer: alias | 分组/子服 | server_name
- polls.js: server_groups CRUD with alias (add/edit, dup check)
- servers page: alias display + edit; new 外部API page: client mgmt + quick docs
- API.md: full external API section (auth matrix, server param syntax)
- verified: 37 checks incl. resolveServer sandbox (alias/group/fallback)
2026-08-19 19:12:34 +08:00

93 lines
5.0 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 ServersPage = {
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 = Auth.isOwner()
? '<button class="btn btn-p btn-sm" id="btn-add-srv"><i class="fas fa-plus"></i> 添加</button>'
: '';
document.getElementById('btn-add-srv').onclick = () => this.showAdd();
await this.load();
},
async load() {
const ct = document.getElementById('page-content');
try {
const groups = await API.get('/polls/groups');
const grouped = {};
for (const g of groups) {
if (!grouped[g.group_name]) grouped[g.group_name] = [];
grouped[g.group_name].push(g);
}
ct.innerHTML = Object.keys(grouped).length === 0
? '<div class="empty"><i class="fas fa-server"></i><p>暂无服务器分组</p></div>'
: Object.entries(grouped).map(([group, servers]) => `
<div class="card">
<div class="card-h"><i class="fas fa-folder"></i> ${U.esc(group)} <span class="ts tm">${servers.length} 个子服</span></div>
<div class="card-b">
<div style="display:flex;flex-wrap:wrap;gap:8px">
${servers.map(s => `
<div style="display:flex;align-items:center;gap:6px;background:var(--g1);padding:6px 12px;border-radius:var(--r);font-size:13px">
<i class="fas fa-server ts tm"></i> ${U.esc(s.server_name)}
${s.alias ? `<span class="badge bg-admin" title="外部别名">${U.esc(s.alias)}</span>` : ''}
${Auth.isOwner() ? `<button class="btn btn-o btn-sm" style="padding:2px 6px;font-size:10px" onclick="ServersPage.showEdit(${s.id})"><i class="fas fa-edit"></i></button><button class="btn btn-d btn-sm" style="padding:2px 6px;font-size:10px" onclick="ServersPage.del(${s.id})"><i class="fas fa-times"></i></button>` : ''}
</div>
`).join('')}
</div>
</div>
</div>`).join('');
} catch (e) { ct.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
},
showAdd() {
U.modal('添加服务器', `
<form id="add-srv-form">
<div class="grid-2"><div class="form-group"><label>分组名 *</label><input id="as-group" required placeholder="如:网易服务器"></div>
<div class="form-group"><label>子服名 *</label><input id="as-server" required placeholder="如:生存服"></div></div>
<div class="form-group"><label>外部别名(可选)</label><input id="as-alias" placeholder="如survival —— 供外部API按别名拉取该子服数据"><span class="help">外部 API 可用别名或「分组/子服名」定位此服务器</span></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('add-srv-form').onsubmit = async e => {
e.preventDefault();
try {
await API.post('/polls/groups', {
group_name: document.getElementById('as-group').value,
server_name: document.getElementById('as-server').value,
alias: document.getElementById('as-alias').value.trim(),
});
App.closeModal(); this.load();
} catch(ex){alert(ex.message);}
};
},
async showEdit(id) {
try {
const groups = await API.get('/polls/groups');
const s = groups.find(x => x.id === id);
if (!s) return alert('未找到');
U.modal('编辑服务器', `
<form id="edit-srv-form">
<div class="grid-2"><div class="form-group"><label>分组名 *</label><input id="es-group" required value="${U.esc(s.group_name)}"></div>
<div class="form-group"><label>子服名 *</label><input id="es-server" required value="${U.esc(s.server_name)}"></div></div>
<div class="form-group"><label>外部别名(可选)</label><input id="es-alias" value="${U.esc(s.alias||'')}" placeholder="如survival"><span class="help">外部 API 可用别名或「分组/子服名」定位此服务器</span></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('edit-srv-form').onsubmit = async e => {
e.preventDefault();
try {
await API.put(`/polls/groups/${id}`, {
group_name: document.getElementById('es-group').value,
server_name: document.getElementById('es-server').value,
alias: document.getElementById('es-alias').value.trim(),
});
App.closeModal(); this.load();
} catch(ex){alert(ex.message);}
};
} catch (ex) { alert(ex.message); }
},
async del(id) { if (!await U.confirm('确认删除?')) return; try { await API.req('DELETE', '/polls/groups/'+id); this.load(); } catch(ex){alert(ex.message);} }
};