Template
Add multi-server 3x-ui registry with per-panel subscription URLs.
Admins can manage several 3x-ui panels by name and select which one issues invite/user configs via that server's /sub base URL. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+22
-3
@@ -17,7 +17,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% if not xui_sub_url and xui_configured %}
|
||||
{% if not xui_has_sub_url and xui_configured %}
|
||||
<div class="card" style="margin-bottom:var(--space-lg); border-color: rgba(245, 158, 11, 0.35); background: rgba(245, 158, 11, 0.06);">
|
||||
<div style="display:flex; gap:var(--space-md); align-items:flex-start;">
|
||||
<div style="font-size:1.4rem;">⚠️</div>
|
||||
@@ -137,7 +137,14 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="inviteInboundGroup">
|
||||
<label class="form-label">{{ _('xui_inbound_label') }}</label>
|
||||
<label class="form-label">{{ _('xui_server_select_label') }}</label>
|
||||
<select class="form-select" id="inviteXuiPanel" onchange="loadInviteInbounds()">
|
||||
{% for s in xui_servers %}
|
||||
<option value="{{ s.id }}">{{ s.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="form-hint">{{ _('xui_server_select_hint') }}</div>
|
||||
<label class="form-label" style="margin-top:var(--space-sm);">{{ _('xui_inbound_label') }}</label>
|
||||
<select class="form-select" id="inviteInboundId">
|
||||
<option value="">{{ _('invite_inbound_loading') }}</option>
|
||||
</select>
|
||||
@@ -177,6 +184,7 @@
|
||||
const invitesData = {{ invites | tojson }};
|
||||
const xuiConfigured = {{ 'true' if xui_configured else 'false' }};
|
||||
const xuiDefaultInbound = {{ xui_default_inbound | int }};
|
||||
const xuiDefaultPanelId = {{ (xui_default_panel_id or '') | tojson }};
|
||||
|
||||
function inviteUrl(token) {
|
||||
return `${window.location.origin}/invite/${token}`;
|
||||
@@ -201,7 +209,9 @@
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const data = await apiCall('/api/settings/xui/inbounds');
|
||||
const panelId = document.getElementById('inviteXuiPanel')?.value || xuiDefaultPanelId || '';
|
||||
const q = panelId ? `?panel_id=${encodeURIComponent(panelId)}` : '';
|
||||
const data = await apiCall('/api/settings/xui/inbounds' + q);
|
||||
const list = data.inbounds || [];
|
||||
if (!list.length) {
|
||||
select.innerHTML = `<option value="">${_('invite_inbound_empty')}</option>`;
|
||||
@@ -255,6 +265,10 @@
|
||||
document.getElementById('inviteClearPwdWrap').style.display = 'none';
|
||||
document.getElementById('inviteResetUsedWrap').style.display = 'none';
|
||||
setProtocolSelect('xui', 0);
|
||||
if (xuiDefaultPanelId) {
|
||||
const p = document.getElementById('inviteXuiPanel');
|
||||
if (p && [...p.options].some(o => o.value === xuiDefaultPanelId)) p.value = xuiDefaultPanelId;
|
||||
}
|
||||
loadInviteInbounds(xuiDefaultInbound);
|
||||
openModal('inviteModal');
|
||||
}
|
||||
@@ -275,6 +289,10 @@
|
||||
document.getElementById('inviteResetUsedWrap').style.display = 'block';
|
||||
document.getElementById('inviteResetUsed').checked = false;
|
||||
setProtocolSelect(inv.protocol || 'xui', inv.server_id || 0);
|
||||
const panelSel = document.getElementById('inviteXuiPanel');
|
||||
if (panelSel && inv.xui_panel_id && [...panelSel.options].some(o => o.value === inv.xui_panel_id)) {
|
||||
panelSel.value = inv.xui_panel_id;
|
||||
}
|
||||
loadInviteInbounds(inv.xui_inbound_id || xuiDefaultInbound);
|
||||
openModal('inviteModal');
|
||||
}
|
||||
@@ -298,6 +316,7 @@
|
||||
protocol: proto.protocol,
|
||||
server_id: proto.server_id,
|
||||
xui_inbound_id: inbound,
|
||||
xui_panel_id: document.getElementById('inviteXuiPanel')?.value || '',
|
||||
note: document.getElementById('inviteNote').value || '',
|
||||
};
|
||||
const pwd = document.getElementById('invitePassword').value;
|
||||
|
||||
+176
-52
@@ -126,7 +126,13 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="guestInboundGroup" style="{% if settings.guest.create_protocol != 'xui' %}display:none;{% endif %}">
|
||||
<label class="form-label">{{ _('xui_inbound_label') }}</label>
|
||||
<label class="form-label">{{ _('xui_server_select_label') }}</label>
|
||||
<select class="form-select" id="guest_create_xui_panel" onchange="loadXuiInbounds()">
|
||||
{% for s in xui_servers %}
|
||||
<option value="{{ s.id }}" {% if settings.guest.create_xui_panel_id == s.id %}selected{% endif %}>{{ s.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label class="form-label" style="margin-top:var(--space-sm);">{{ _('xui_inbound_label') }}</label>
|
||||
<select class="form-select" id="guest_create_inbound_id">
|
||||
<option value="0">{{ _('xui_inbound_auto') }}</option>
|
||||
</select>
|
||||
@@ -430,35 +436,8 @@
|
||||
|
||||
<div id="xuiFields"
|
||||
style="{% if not settings.sync.xui_sync %}display:none;{% endif %} border-top: 1px solid var(--border-color); padding-top: var(--space-md); margin-top: var(--space-md);">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ _('xui_url_label') }}</label>
|
||||
<input type="url" class="form-input" name="xui_url"
|
||||
value="{{ settings.sync.xui_url }}" placeholder="https://panel.example.com:2053/path">
|
||||
<div class="form-hint">{{ _('xui_url_hint') }}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ _('xui_sub_url_label') }}</label>
|
||||
<input type="url" class="form-input" name="xui_sub_url"
|
||||
value="{{ settings.sync.xui_sub_url or '' }}" placeholder="https://sub.example.com:2096/sub">
|
||||
<div class="form-hint">{{ _('xui_sub_url_hint') }}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ _('xui_api_token_label') }}</label>
|
||||
<input type="password" class="form-input" name="xui_api_token"
|
||||
value="{{ settings.sync.xui_api_token }}" placeholder="{{ _('xui_api_token_placeholder') }}">
|
||||
<div class="form-hint">{{ _('xui_api_token_hint') }}</div>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-sm);">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ _('xui_username_label') }}</label>
|
||||
<input type="text" class="form-input" name="xui_username"
|
||||
value="{{ settings.sync.xui_username }}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ _('xui_password_label') }}</label>
|
||||
<input type="password" class="form-input" name="xui_password"
|
||||
value="{{ settings.sync.xui_password }}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-hint" style="margin-bottom: var(--space-md);">
|
||||
{{ _('xui_servers_manage_hint') }}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -491,14 +470,6 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ _('xui_inbound_label') }}</label>
|
||||
<select class="form-select" name="xui_inbound_id" id="xuiInboundSelect">
|
||||
<option value="0">{{ _('xui_inbound_auto') }}</option>
|
||||
</select>
|
||||
<div class="form-hint">{{ _('xui_inbound_hint') }}</div>
|
||||
</div>
|
||||
|
||||
<div id="xuiAutoConnFields"
|
||||
style="{% if not settings.sync.xui_create_conns %}display:none;{% endif %} background: var(--bg-primary); padding: var(--space-md); border-radius: var(--radius-md); margin-top: var(--space-sm);">
|
||||
<div class="form-group">
|
||||
@@ -519,10 +490,50 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- keep hidden legacy fields so saveSettings still posts them -->
|
||||
<input type="hidden" name="xui_url" value="{{ settings.sync.xui_url or '' }}">
|
||||
<input type="hidden" name="xui_sub_url" value="{{ settings.sync.xui_sub_url or '' }}">
|
||||
<input type="hidden" name="xui_api_token" value="{{ settings.sync.xui_api_token or '' }}">
|
||||
<input type="hidden" name="xui_username" value="{{ settings.sync.xui_username or '' }}">
|
||||
<input type="hidden" name="xui_password" value="{{ settings.sync.xui_password or '' }}">
|
||||
<input type="hidden" name="xui_inbound_id" value="{{ settings.sync.xui_inbound_id or 0 }}">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- BLOCK: 3x-ui servers (multi) -->
|
||||
<div class="card" style="margin-top: var(--space-lg);">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:var(--space-md); margin-bottom: var(--space-lg);">
|
||||
<h3 class="card-title" style="margin:0;">{{ _('xui_servers_title') }}</h3>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="openXuiServerModal()">+ {{ _('xui_server_add') }}</button>
|
||||
</div>
|
||||
<div class="form-hint" style="margin-bottom: var(--space-md);">{{ _('xui_servers_hint') }}</div>
|
||||
<div id="xuiServersList">
|
||||
{% if xui_servers %}
|
||||
{% for s in xui_servers %}
|
||||
<div class="client-item" style="margin-bottom:var(--space-sm);" data-xui-id="{{ s.id }}">
|
||||
<div class="client-info" style="flex:1; min-width:0;">
|
||||
<div class="client-avatar">🌀</div>
|
||||
<div style="min-width:0;">
|
||||
<div class="client-name">{{ s.name }}</div>
|
||||
<div class="client-meta" style="word-break:break-all;">
|
||||
<span>{{ s.url }}</span>
|
||||
{% if s.sub_url %}<span>· sub: {{ s.sub_url }}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="client-actions">
|
||||
<button class="btn btn-secondary btn-sm" type="button" onclick='editXuiServer({{ s | tojson }})'>{{ _('edit') }}</button>
|
||||
<button class="btn btn-danger btn-sm" type="button" onclick="deleteXuiServer('{{ s.id }}')">{{ _('delete') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div style="text-align:center; padding:var(--space-lg); color:var(--text-muted);">{{ _('xui_servers_empty') }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BLOCK: Simple Backup -->
|
||||
<div class="card" style="margin-top: var(--space-lg);">
|
||||
<h3 class="card-title" style="margin-bottom: var(--space-lg);">📤 {{ _('backup_title') }}</h3>
|
||||
@@ -600,6 +611,66 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ===== 3x-ui Server Modal ===== -->
|
||||
<div class="modal-backdrop" id="xuiServerModal">
|
||||
<div class="modal" style="max-width:520px;">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="xuiServerModalTitle">{{ _('xui_server_add') }}</h2>
|
||||
<button class="modal-close" onclick="closeModal('xuiServerModal')">×</button>
|
||||
</div>
|
||||
<input type="hidden" id="xuiServerEditId" value="">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ _('xui_server_name_label') }}</label>
|
||||
<input class="form-input" type="text" id="xuiServerName" placeholder="US / NL / Home">
|
||||
<div class="form-hint">{{ _('xui_server_name_hint') }}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ _('xui_url_label') }}</label>
|
||||
<input class="form-input" type="url" id="xuiServerUrl" placeholder="https://panel.example.com:2053/path">
|
||||
<div class="form-hint">{{ _('xui_url_hint') }}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ _('xui_sub_url_label') }}</label>
|
||||
<input class="form-input" type="url" id="xuiServerSubUrl" placeholder="https://sub.example.com:2096/sub">
|
||||
<div class="form-hint">{{ _('xui_sub_url_hint') }}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ _('xui_api_token_label') }}</label>
|
||||
<input class="form-input" type="password" id="xuiServerToken" placeholder="{{ _('xui_api_token_placeholder') }}" autocomplete="off">
|
||||
<div class="form-hint">{{ _('xui_api_token_hint') }}</div>
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr; gap:var(--space-sm);">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ _('xui_username_label') }}</label>
|
||||
<input class="form-input" type="text" id="xuiServerUser" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ _('xui_password_label') }}</label>
|
||||
<input class="form-input" type="password" id="xuiServerPass" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="display:flex; align-items:center; gap:var(--space-sm); cursor:pointer;">
|
||||
<input type="checkbox" id="xuiServerEnabled" checked> {{ _('enabled') if False else 'Enabled' }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="display:flex; align-items:center; gap:var(--space-sm); cursor:pointer;">
|
||||
<input type="checkbox" id="xuiServerSyncUsers"> {{ _('enable_sync') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" onclick="closeModal('xuiServerModal')">{{ _('cancel') }}</button>
|
||||
<button class="btn btn-primary" onclick="saveXuiServer()" id="xuiServerSaveBtn">
|
||||
<span id="xuiServerSaveText">{{ _('save') }}</span>
|
||||
<div class="spinner hidden" id="xuiServerSaveSpinner" style="width:14px;height:14px;"></div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Create API Token Modal ===== -->
|
||||
<div class="modal-backdrop" id="createTokenModal">
|
||||
<div class="modal" style="max-width: 460px;">
|
||||
@@ -721,18 +792,77 @@
|
||||
|
||||
document.querySelector('[name="xui_sync"]').addEventListener('change', (e) => {
|
||||
document.getElementById('xuiFields').style.display = e.target.checked ? 'block' : 'none';
|
||||
if (e.target.checked) loadXuiInbounds();
|
||||
});
|
||||
|
||||
const xuiDefaultInboundId = {{ settings.sync.xui_inbound_id | default(0) | int }};
|
||||
function openXuiServerModal(server) {
|
||||
document.getElementById('xuiServerEditId').value = server?.id || '';
|
||||
document.getElementById('xuiServerModalTitle').textContent = server?.id ? (_('edit') + ' — 3x-ui') : _('xui_server_add');
|
||||
document.getElementById('xuiServerName').value = server?.name || '';
|
||||
document.getElementById('xuiServerUrl').value = server?.url || '';
|
||||
document.getElementById('xuiServerSubUrl').value = server?.sub_url || '';
|
||||
document.getElementById('xuiServerToken').value = '';
|
||||
document.getElementById('xuiServerUser').value = server?.username || '';
|
||||
document.getElementById('xuiServerPass').value = '';
|
||||
document.getElementById('xuiServerEnabled').checked = server ? !!server.enabled : true;
|
||||
document.getElementById('xuiServerSyncUsers').checked = !!(server && server.sync_users);
|
||||
openModal('xuiServerModal');
|
||||
}
|
||||
|
||||
function editXuiServer(server) { openXuiServerModal(server); }
|
||||
|
||||
async function saveXuiServer() {
|
||||
const id = document.getElementById('xuiServerEditId').value;
|
||||
const body = {
|
||||
name: document.getElementById('xuiServerName').value.trim(),
|
||||
url: document.getElementById('xuiServerUrl').value.trim(),
|
||||
sub_url: document.getElementById('xuiServerSubUrl').value.trim(),
|
||||
api_token: document.getElementById('xuiServerToken').value,
|
||||
username: document.getElementById('xuiServerUser').value.trim(),
|
||||
password: document.getElementById('xuiServerPass').value,
|
||||
enabled: document.getElementById('xuiServerEnabled').checked,
|
||||
sync_users: document.getElementById('xuiServerSyncUsers').checked,
|
||||
default_inbound_id: 0,
|
||||
};
|
||||
if (!body.url) { showToast(_('error') + ': URL', 'error'); return; }
|
||||
const btn = document.getElementById('xuiServerSaveBtn');
|
||||
const text = document.getElementById('xuiServerSaveText');
|
||||
const spinner = document.getElementById('xuiServerSaveSpinner');
|
||||
btn.disabled = true; text.textContent = _('saving') || 'Saving...'; spinner.classList.remove('hidden');
|
||||
try {
|
||||
if (id) await apiCall(`/api/settings/xui/servers/${encodeURIComponent(id)}`, 'PUT', body);
|
||||
else await apiCall('/api/settings/xui/servers', 'POST', body);
|
||||
showToast(_('saved') || 'OK', 'success');
|
||||
closeModal('xuiServerModal');
|
||||
setTimeout(() => location.reload(), 400);
|
||||
} catch (err) {
|
||||
showToast(_('error') + ': ' + err.message, 'error');
|
||||
} finally {
|
||||
btn.disabled = false; text.textContent = _('save'); spinner.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteXuiServer(id) {
|
||||
if (!confirm(_('xui_server_delete_confirm') || 'Delete this 3x-ui server?')) return;
|
||||
try {
|
||||
await apiCall(`/api/settings/xui/servers/${encodeURIComponent(id)}`, 'DELETE');
|
||||
showToast(_('deleted') || 'OK', 'success');
|
||||
setTimeout(() => location.reload(), 400);
|
||||
} catch (err) {
|
||||
showToast(_('error') + ': ' + err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Guest inbound loader still uses primary panel
|
||||
async function loadXuiInbounds() {
|
||||
const select = document.getElementById('xuiInboundSelect');
|
||||
const select = document.getElementById('guest_create_inbound_id') || document.getElementById('xuiInboundSelect');
|
||||
if (!select) return;
|
||||
const prev = select.value || String(xuiDefaultInboundId || '0');
|
||||
const prev = select.value || '0';
|
||||
select.innerHTML = `<option value="0">${_('xui_inbound_auto')}</option>`;
|
||||
try {
|
||||
const res = await fetch('/api/settings/xui/inbounds');
|
||||
const panelSel = document.getElementById('guest_create_xui_panel');
|
||||
const panelId = panelSel ? panelSel.value : '';
|
||||
const q = panelId ? `?panel_id=${encodeURIComponent(panelId)}` : '';
|
||||
const res = await fetch('/api/settings/xui/inbounds' + q);
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || 'Failed to load inbounds');
|
||||
(data.inbounds || []).forEach(ib => {
|
||||
@@ -741,19 +871,12 @@
|
||||
opt.textContent = `${ib.remark || 'VLESS'} (:${ib.port})`;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
if ([...select.options].some(o => o.value === String(prev))) {
|
||||
select.value = String(prev);
|
||||
}
|
||||
if ([...select.options].some(o => o.value === String(prev))) select.value = String(prev);
|
||||
} catch (err) {
|
||||
// Keep auto option; credentials may not be saved yet
|
||||
console.warn('loadXuiInbounds:', err);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.querySelector('[name="xui_sync"]').checked) {
|
||||
loadXuiInbounds();
|
||||
}
|
||||
|
||||
document.getElementById('syncCreateConns').addEventListener('change', (e) => {
|
||||
document.getElementById('autoConnFields').style.display = e.target.checked ? 'block' : 'none';
|
||||
if (e.target.checked) updateProtocolsForSync();
|
||||
@@ -1161,7 +1284,8 @@
|
||||
allow_create: document.getElementById('guest_allow_create').checked,
|
||||
create_protocol: createProtocol,
|
||||
create_server_id: createServerId,
|
||||
create_inbound_id: parseInt(document.getElementById('guest_create_inbound_id').value || '0')
|
||||
create_inbound_id: parseInt(document.getElementById('guest_create_inbound_id').value || '0'),
|
||||
create_xui_panel_id: document.getElementById('guest_create_xui_panel')?.value || '',
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
+12
-2
@@ -292,7 +292,13 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="ucXuiInboundGroup" style="display:none;">
|
||||
<label class="form-label">{{ _('xui_inbound_label') }}</label>
|
||||
<label class="form-label">{{ _('xui_server_select_label') }}</label>
|
||||
<select class="form-select" id="ucXuiPanel" onchange="loadXuiInbounds()">
|
||||
{% for s in xui_servers %}
|
||||
<option value="{{ s.id }}" {% if s.id == xui_default_panel_id %}selected{% endif %}>{{ s.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label class="form-label" style="margin-top:var(--space-sm);">{{ _('xui_inbound_label') }}</label>
|
||||
<select class="form-select" id="ucXuiInbound">
|
||||
<option value="">{{ _('xui_inbound_auto') }}</option>
|
||||
</select>
|
||||
@@ -407,6 +413,7 @@
|
||||
const serversData = {{ servers | tojson }};
|
||||
const xuiConfigured = {{ 'true' if xui_configured else 'false' }};
|
||||
const xuiDefaultInboundId = {{ xui_inbound_id | int }};
|
||||
const xuiServers = {{ xui_servers | default([]) | tojson }};
|
||||
|
||||
function populateTimeSelect(selectId) {
|
||||
const select = document.getElementById(selectId);
|
||||
@@ -640,7 +647,9 @@
|
||||
if (!select) return;
|
||||
select.innerHTML = `<option value="">${_('xui_inbound_auto')}</option>`;
|
||||
try {
|
||||
const data = await apiCall('/api/settings/xui/inbounds');
|
||||
const panelId = document.getElementById('ucXuiPanel')?.value || '';
|
||||
const q = panelId ? `?panel_id=${encodeURIComponent(panelId)}` : '';
|
||||
const data = await apiCall('/api/settings/xui/inbounds' + q);
|
||||
(data.inbounds || []).forEach(ib => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = ib.id;
|
||||
@@ -865,6 +874,7 @@
|
||||
} else if (body.protocol === 'xui') {
|
||||
const inbound = document.getElementById('ucXuiInbound').value;
|
||||
if (inbound) body.xui_inbound_id = parseInt(inbound);
|
||||
body.xui_panel_id = document.getElementById('ucXuiPanel')?.value || '';
|
||||
} else if (body.protocol === 'telemt') {
|
||||
body.telemt_quota = document.getElementById('ucTelemtQuota').value || null;
|
||||
body.telemt_max_ips = parseInt(document.getElementById('ucTelemtMaxIps').value) || null;
|
||||
|
||||
Reference in New Issue
Block a user