v1.7.0: pick server first, then protocol in create flows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
orohi
2026-07-26 09:00:09 +03:00
co-authored by Cursor
parent 64aa1850a0
commit a9a0eea61d
7 changed files with 266 additions and 68 deletions
+87 -18
View File
@@ -110,21 +110,26 @@
<div id="guestCreateFields"
style="{% if not settings.guest.allow_create %}display:none;{% endif %} background: var(--bg-primary); padding: var(--space-md); border-radius: var(--radius-md);">
<div class="form-group">
<label class="form-label">{{ _('protocol_label') }}</label>
<select class="form-select" id="guest_create_protocol" onchange="updateGuestCreateFields()">
<option value="xui" {% if settings.guest.create_protocol == 'xui' %}selected{% endif %}>3x-ui VLESS</option>
{% for s in servers %}
{% set server_idx = loop.index0 %}
{% for key, info in (s.protocols or {}).items() %}
{% if info.installed and key.split('__')[0] in ['awg','awg2','awg_legacy','xray','wireguard','telemt'] %}
<option value="{{ key }}|{{ server_idx }}"
{% if settings.guest.create_protocol == key and settings.guest.create_server_id == server_idx %}selected{% endif %}>
{{ s.name or s.host }} — {{ key }}
</option>
<label class="form-label">{{ _('server_label') }}</label>
<select class="form-select" id="guest_create_server" onchange="fillGuestProtocols()">
{% if xui_servers %}
<option value="xui" {% if settings.guest.create_protocol == 'xui' %}selected{% endif %}>3x-ui</option>
{% endif %}
{% endfor %}
{% for s in servers %}
<option value="{{ loop.index0 }}"
{% if settings.guest.create_protocol != 'xui' and settings.guest.create_server_id == loop.index0 %}selected{% endif %}>
{{ s.name or s.host }} ({{ s.host }})
</option>
{% endfor %}
</select>
<div class="form-hint">{{ _('server_then_protocol_hint') }}</div>
</div>
<div class="form-group">
<label class="form-label">{{ _('protocol_label') }}</label>
<select class="form-select" id="guest_create_protocol" onchange="updateGuestCreateFields()">
<!-- filled by JS -->
</select>
<input type="hidden" id="guest_create_protocol_pref" value="{{ settings.guest.create_protocol or '' }}">
</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>
@@ -1275,13 +1280,14 @@
let createProtocol = 'xui';
let createServerId = 0;
const guestProtoRaw = document.getElementById('guest_create_protocol').value;
if (guestProtoRaw === 'xui') {
const guestServerVal = document.getElementById('guest_create_server')?.value || 'xui';
const guestProtoVal = document.getElementById('guest_create_protocol')?.value || '';
if (guestServerVal === 'xui' || guestProtoVal === 'xui') {
createProtocol = 'xui';
} else if (guestProtoRaw.includes('|')) {
const parts = guestProtoRaw.split('|');
createProtocol = parts[0];
createServerId = parseInt(parts[1] || '0');
createServerId = 0;
} else {
createProtocol = guestProtoVal;
createServerId = parseInt(guestServerVal || '0', 10) || 0;
}
const guest = {
@@ -1314,6 +1320,68 @@
}
}
const guestServersData = {{ servers | default([]) | tojson }};
const VPN_PROTO_ORDER = ['awg2', 'awg', 'awg_legacy', 'wireguard', 'xray', 'telemt'];
const PROTO_TITLES = {
awg2: 'AmneziaWG 2.0',
awg: 'AmneziaWG',
awg_legacy: 'AWG Legacy',
wireguard: 'WireGuard',
xray: 'Xray (VLESS-Reality)',
telemt: 'Telemt',
xui: '3x-ui VLESS',
};
function protoTitle(key) {
const raw = String(key || '');
const base = raw.split('__')[0];
const title = PROTO_TITLES[base] || base.toUpperCase();
const m = raw.match(/__(\d+)$/);
return m ? `${title} #${m[1]}` : title;
}
function fillGuestProtocols() {
const serverSel = document.getElementById('guest_create_server');
const protoSel = document.getElementById('guest_create_protocol');
if (!serverSel || !protoSel) return;
const prefer = document.getElementById('guest_create_protocol_pref')?.value || protoSel.value || '';
const serverVal = serverSel.value;
protoSel.innerHTML = '';
if (serverVal === 'xui') {
const opt = document.createElement('option');
opt.value = 'xui';
opt.textContent = PROTO_TITLES.xui;
protoSel.appendChild(opt);
updateGuestCreateFields();
return;
}
const server = guestServersData[parseInt(serverVal, 10)];
const protocols = (server && server.protocols) || {};
const installed = Object.keys(protocols).filter(k => {
const info = protocols[k] || {};
return !!info.installed && VPN_PROTO_ORDER.includes(k.split('__')[0]);
}).sort((a, b) => {
const ia = VPN_PROTO_ORDER.indexOf(a.split('__')[0]);
const ib = VPN_PROTO_ORDER.indexOf(b.split('__')[0]);
return (ia < 0 ? 99 : ia) - (ib < 0 ? 99 : ib) || a.localeCompare(b);
});
if (!installed.length) {
const opt = document.createElement('option');
opt.value = '';
opt.disabled = true;
opt.textContent = _('no_protocols');
protoSel.appendChild(opt);
} else {
installed.forEach(key => {
const opt = document.createElement('option');
opt.value = key;
opt.textContent = protoTitle(key);
if (prefer && prefer === key) opt.selected = true;
protoSel.appendChild(opt);
});
}
updateGuestCreateFields();
}
function updateGuestCreateFields() {
const v = document.getElementById('guest_create_protocol')?.value;
const group = document.getElementById('guestInboundGroup');
@@ -1321,6 +1389,7 @@
if (v === 'xui') loadGuestInbounds();
}
fillGuestProtocols();
if (document.getElementById('guest_create_protocol')?.value === 'xui') {
loadGuestInbounds();
}