- 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)
93 lines
5.0 KiB
JavaScript
93 lines
5.0 KiB
JavaScript
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);} }
|
||
};
|