const PageSettings = { _el: null, init() { this._el = document.getElementById('page-settings'); }, async render() { const cfg = await API.getConfig(); const games = await API.getGames(); const forwards = await this._getForwards(); this._el.innerHTML = this._template(cfg, games, forwards); this._bindEvents(); }, async _getForwards() { try { const res = await fetch('/api/forward'); return await res.json(); } catch (e) { return []; } }, async _saveForwards(targets) { await fetch('/api/forward', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ targets }) }); }, _bindEvents() { document.getElementById('settings-telemetry-port')?.addEventListener('change', async (e) => { await API.updateConfig({ telemetry_port: parseInt(e.target.value) || 20777 }); Toast.show('遥测端口已更新,重启监听后生效', 'info'); }); document.getElementById('settings-restart-telemetry')?.addEventListener('click', async () => { await API.stopTelemetry(); await API.startTelemetry(); Toast.show('遥测监听已重启', 'success'); }); document.getElementById('btn-add-forward')?.addEventListener('click', () => this._addForward()); document.getElementById('forward-list')?.addEventListener('click', async (e) => { if (e.target.closest('.btn-del-forward')) { e.target.closest('.forward-row').remove(); this._collectAndSave(); } }); document.getElementById('btn-save-forwards')?.addEventListener('click', () => this._collectAndSave()); document.getElementById('btn-import-plugin')?.addEventListener('click', () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.tsp'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; const fd = new FormData(); fd.append('file', file); const res = await fetch('/api/games/install', { method: 'POST', body: fd }); const data = await res.json(); if (data.ok) { Toast.show('插件安装成功', 'success'); this.render(); } else Toast.show('安装失败', 'error'); }; input.click(); }); document.getElementById('btn-reload-plugins')?.addEventListener('click', async () => { await API.reloadGamePlugins(); Toast.show('插件已重新加载', 'success'); this.render(); }); document.getElementById('settings-game-list')?.addEventListener('click', async (e) => { const exportBtn = e.target.closest('.btn-export-plugin'); const removeBtn = e.target.closest('.btn-remove-plugin'); if (exportBtn) { const pid = exportBtn.dataset.pluginId; const a = document.createElement('a'); a.href = '/api/games/' + pid + '/export'; a.download = pid + '.tsp'; a.click(); } if (removeBtn) { if (confirm('移除?')) { await API.removeGamePlugin(removeBtn.dataset.pluginId); Toast.show('已移除', 'info'); this.render(); } } }); document.getElementById('btn-import-dashboard')?.addEventListener('click', () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.tsd'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; const fd = new FormData(); fd.append('file', file); const res = await fetch('/api/dashboards/import', { method: 'POST', body: fd }); const data = await res.json(); Toast.show(data.ok ? '导入成功' : '导入失败', data.ok ? 'success' : 'error'); }; input.click(); }); }, _addForward() { const list = document.getElementById('forward-list'); const row = document.createElement('div'); row.className = 'forward-row glass-card'; row.style.cssText = 'padding:12px;margin-bottom:8px;display:flex;gap:8px;align-items:center;flex-wrap:wrap'; row.innerHTML = ''; list.appendChild(row); }, _collectAndSave() { const rows = document.querySelectorAll('#forward-list .forward-row'); const targets = []; rows.forEach(r => { const host = r.querySelector('.fw-host')?.value?.trim(); const port = parseInt(r.querySelector('.fw-port')?.value) || 0; const name = r.querySelector('.fw-name')?.value?.trim() || ''; const enabled = r.querySelector('.forward-toggle')?.checked !== false; if (host && port > 0) targets.push({ host, port, name, enabled }); }); this._saveForwards(targets).then(() => Toast.show('已保存 ' + targets.length + ' 个转发目标', 'success')); }, _forwardListHtml(forwards) { if (!forwards || forwards.length === 0) return '
暂无转发目标,点击下方添加
'; return forwards.map(f => '').join(''); }, _gamePluginListHtml(games) { if (!games || games.length === 0) return '暂无游戏插件
'; return games.map(g => '将收到的游戏原始 UDP 数据包完整转发到下游物理外设