Let 3x-ui assign inbound and share link on create.

Stop requiring inbound selection in the site UI; send the create request to the chosen panel and use the link it returns.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
orohi
2026-07-26 03:40:24 +03:00
co-authored by Cursor
parent c28235b09a
commit 8dc090bd67
7 changed files with 169 additions and 174 deletions
+10 -46
View File
@@ -42,8 +42,11 @@
<div>
<div style="font-weight:700; font-size:1.05rem;">{{ inv.name }}</div>
<div style="font-size:0.8rem; color:var(--text-muted); margin-top:2px;">
{% if inv.protocol == 'xui' %}3x-ui VLESS{% else %}{{ inv.protocol }}{% endif %}
· inbound #{{ inv.xui_inbound_id or '—' }}
{% if inv.protocol == 'xui' %}
3x-ui · {% for s in xui_servers if s.id == inv.xui_panel_id %}{{ s.name }}{% else %}{{ _('xui_server_select_label') }}{% endfor %}
{% else %}
{{ inv.protocol }}
{% endif %}
</div>
</div>
{% if inv.available %}
@@ -138,17 +141,12 @@
<div class="form-group" id="inviteInboundGroup">
<label class="form-label">{{ _('xui_server_select_label') }}</label>
<select class="form-select" id="inviteXuiPanel" onchange="loadInviteInbounds()">
<select class="form-select" id="inviteXuiPanel">
{% 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>
<div class="form-hint">{{ _('invite_inbound_hint') }}</div>
</div>
<div class="form-group">
@@ -183,7 +181,6 @@
<script>
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) {
@@ -198,38 +195,6 @@
function updateInviteProtoFields() {
const v = document.getElementById('inviteProtocol').value;
document.getElementById('inviteInboundGroup').style.display = (v === 'xui') ? '' : 'none';
if (v === 'xui') loadInviteInbounds(document.getElementById('inviteInboundId').value || xuiDefaultInbound);
}
async function loadInviteInbounds(selected) {
const select = document.getElementById('inviteInboundId');
select.innerHTML = `<option value="">${_('invite_inbound_loading')}</option>`;
if (!xuiConfigured) {
select.innerHTML = `<option value="">${_('invite_inbound_need_xui')}</option>`;
return;
}
try {
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>`;
return;
}
select.innerHTML = '';
list.forEach((ib, idx) => {
const opt = document.createElement('option');
opt.value = ib.id;
opt.textContent = `${ib.remark || 'VLESS'} (:${ib.port})`;
const prefer = selected || xuiDefaultInbound;
if (prefer && String(ib.id) === String(prefer)) opt.selected = true;
else if (!prefer && idx === 0) opt.selected = true;
select.appendChild(opt);
});
} catch (e) {
select.innerHTML = `<option value="">${_('error')}: ${e.message}</option>`;
}
}
function parseProtocolValue() {
@@ -269,7 +234,6 @@
const p = document.getElementById('inviteXuiPanel');
if (p && [...p.options].some(o => o.value === xuiDefaultPanelId)) p.value = xuiDefaultPanelId;
}
loadInviteInbounds(xuiDefaultInbound);
openModal('inviteModal');
}
@@ -293,7 +257,6 @@
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');
}
@@ -305,8 +268,9 @@
try {
const editId = document.getElementById('inviteEditId').value;
const proto = parseProtocolValue();
const inbound = parseInt(document.getElementById('inviteInboundId').value || '0');
if (proto.protocol === 'xui' && !inbound) throw new Error(_('invite_inbound_required'));
if (proto.protocol === 'xui' && !document.getElementById('inviteXuiPanel')?.value) {
throw new Error(_('invite_inbound_need_xui'));
}
const body = {
name: document.getElementById('inviteName').value || 'Invite',
@@ -315,7 +279,7 @@
user_id: document.getElementById('inviteUserId').value || '',
protocol: proto.protocol,
server_id: proto.server_id,
xui_inbound_id: inbound,
xui_inbound_id: 0,
xui_panel_id: document.getElementById('inviteXuiPanel')?.value || '',
note: document.getElementById('inviteNote').value || '',
};
+6 -55
View File
@@ -127,15 +127,13 @@
</div>
<div class="form-group" id="guestInboundGroup" style="{% if settings.guest.create_protocol != 'xui' %}display:none;{% endif %}">
<label class="form-label">{{ _('xui_server_select_label') }}</label>
<select class="form-select" id="guest_create_xui_panel" onchange="loadXuiInbounds()">
<select class="form-select" id="guest_create_xui_panel">
{% 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>
<div class="form-hint">{{ _('xui_server_select_hint') }}</div>
<input type="hidden" id="guest_create_inbound_id" value="0">
</div>
</div>
</div>
@@ -852,30 +850,9 @@
}
}
// Guest inbound loader still uses primary panel
async function loadXuiInbounds() {
const select = document.getElementById('guest_create_inbound_id') || document.getElementById('xuiInboundSelect');
if (!select) return;
const prev = select.value || '0';
select.innerHTML = `<option value="0">${_('xui_inbound_auto')}</option>`;
try {
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 => {
const opt = document.createElement('option');
opt.value = ib.id;
opt.textContent = `${ib.remark || 'VLESS'} (:${ib.port})`;
select.appendChild(opt);
});
if ([...select.options].some(o => o.value === String(prev))) select.value = String(prev);
} catch (err) {
console.warn('loadXuiInbounds:', err);
}
}
// Inbound + share link are assigned by the selected 3x-ui panel on create
async function loadXuiInbounds() {}
async function loadGuestInbounds() {}
document.getElementById('syncCreateConns').addEventListener('change', (e) => {
document.getElementById('autoConnFields').style.display = e.target.checked ? 'block' : 'none';
@@ -1331,32 +1308,6 @@
}
}
async function loadGuestInbounds() {
const select = document.getElementById('guest_create_inbound_id');
if (!select) return;
const preferred = {{ settings.guest.create_inbound_id | default(0) | int }};
select.innerHTML = `<option value="0">${_('xui_inbound_auto')}</option>`;
try {
const res = await fetch('/api/settings/xui/inbounds');
const data = await res.json();
if (!res.ok) return;
(data.inbounds || []).forEach(ib => {
const opt = document.createElement('option');
opt.value = ib.id;
opt.textContent = `${ib.remark || 'VLESS'} (:${ib.port})`;
if (String(ib.id) === String(preferred)) opt.selected = true;
select.appendChild(opt);
});
} catch (e) { /* ignore */ }
}
if (document.getElementById('guest_allow_create')?.checked) {
loadGuestInbounds();
}
document.getElementById('guest_allow_create')?.addEventListener('change', (e) => {
if (e.target.checked) loadGuestInbounds();
});
async function toggleBot() {
const btn = document.getElementById('toggleBotBtn');
const text = document.getElementById('toggleBotBtnText');
+4 -31
View File
@@ -293,16 +293,12 @@
<div class="form-group" id="ucXuiInboundGroup" style="display:none;">
<label class="form-label">{{ _('xui_server_select_label') }}</label>
<select class="form-select" id="ucXuiPanel" onchange="loadXuiInbounds()">
<select class="form-select" id="ucXuiPanel">
{% 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>
<div class="form-hint">{{ _('xui_inbound_hint') }}</div>
<div class="form-hint">{{ _('xui_server_select_hint') }}</div>
</div>
<div class="form-group" id="ucNameGroup">
@@ -643,35 +639,14 @@
}
async function loadXuiInbounds() {
const select = document.getElementById('ucXuiInbound');
if (!select) return;
select.innerHTML = `<option value="">${_('xui_inbound_auto')}</option>`;
try {
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;
opt.textContent = `${ib.remark || 'VLESS'} (:${ib.port})`;
if (String(ib.id) === String(xuiDefaultInboundId)) opt.selected = true;
select.appendChild(opt);
});
} catch (err) {
showToast(`${_('error')}: ${err.message}`, 'error');
}
// Inbound is assigned by the selected 3x-ui panel — nothing to load here.
}
function updateXuiInboundVisibility() {
const proto = document.getElementById('ucProtocol')?.value;
const group = document.getElementById('ucXuiInboundGroup');
if (!group) return;
if (proto === 'xui') {
group.style.display = 'block';
loadXuiInbounds();
} else {
group.style.display = 'none';
}
group.style.display = (proto === 'xui') ? 'block' : 'none';
}
// Show/hide connection fields
@@ -872,8 +847,6 @@
if (!clientId) throw new Error(_('select_connection'));
body.client_id = clientId;
} 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;