|
|
|
@@ -319,6 +319,30 @@
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- NaiveProxy Card -->
|
|
|
|
|
<div class="card card-hover protocol-card protocol-naiveproxy" id="proto-naiveproxy">
|
|
|
|
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:var(--space-sm);">
|
|
|
|
|
<div class="protocol-icon">{{ icon('link') }}</div>
|
|
|
|
|
<div class="flex gap-sm" id="naiveproxy-ctrl" style="display:none!important;"></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="protocol-name">NaiveProxy</div>
|
|
|
|
|
<div class="protocol-desc">
|
|
|
|
|
{{ _('naiveproxy_desc') }}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="protocol-status" id="naiveproxy-status">
|
|
|
|
|
<span class="badge badge-warn"><span class="badge-dot"></span> {{ _('not_checked') }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="naiveproxy-info" class="hidden">
|
|
|
|
|
<div class="protocol-info" id="naiveproxy-info-grid"></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex gap-sm" id="naiveproxy-actions">
|
|
|
|
|
<button class="btn btn-primary btn-sm" onclick="openInstallModal('naiveproxy')" id="naiveproxy-install-btn"
|
|
|
|
|
style="flex:1">
|
|
|
|
|
{{ _('install') }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- WireGuard Card -->
|
|
|
|
|
<div class="card card-hover protocol-card protocol-wireguard" id="proto-wireguard">
|
|
|
|
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:var(--space-sm);">
|
|
|
|
@@ -703,6 +727,24 @@
|
|
|
|
|
<div class="form-hint">{{ _('hysteria_install_hint') }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="naiveproxyOptions"
|
|
|
|
|
style="display:none; padding: var(--space-md); background: rgba(0,0,0,0.03); border-radius: var(--radius-md); margin-bottom: var(--space-md);">
|
|
|
|
|
<div class="form-hint" style="margin-bottom: var(--space-md); padding: var(--space-sm) var(--space-md); border-radius: var(--radius-sm); background: rgba(234,179,8,0.12); border: 1px solid rgba(234,179,8,0.35); color: var(--text);">
|
|
|
|
|
{{ _('naiveproxy_ports_warning') }}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">{{ _('naiveproxy_domain') }}</label>
|
|
|
|
|
<input class="form-input" type="text" id="installNaiveproxyDomain" placeholder="vpn.example.com" oninput="updateNaiveproxyDnsHint()">
|
|
|
|
|
<div class="form-hint" id="naiveproxyDnsHint"></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">{{ _('naiveproxy_email') }}</label>
|
|
|
|
|
<input class="form-input" type="email" id="installNaiveproxyEmail" placeholder="admin@example.com">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-hint">{{ _('naiveproxy_install_hint') }}</div>
|
|
|
|
|
<div class="form-hint" style="margin-top: var(--space-sm);">{{ _('naiveproxy_port_hint') }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="modal-footer">
|
|
|
|
|
<button class="btn btn-secondary" onclick="closeModal('installModal')">{{ _('cancel') }}</button>
|
|
|
|
|
<button class="btn btn-primary" onclick="installProtocol()" id="installBtn">
|
|
|
|
@@ -1055,6 +1097,7 @@
|
|
|
|
|
{ proto: 'xray', category: 'protocols', icon: 'zap', title: 'Xray (VLESS-Reality)', descKey: 'xray_desc' },
|
|
|
|
|
{ proto: 'telemt', category: 'protocols', icon: 'plane', title: 'Telemt (Telegram Proxy)', descKey: 'telemt_desc' },
|
|
|
|
|
{ proto: 'hysteria', category: 'protocols', icon: 'refresh', title: 'Hysteria 2', descKey: 'hysteria_desc' },
|
|
|
|
|
{ proto: 'naiveproxy', category: 'protocols', icon: 'link', title: 'NaiveProxy', descKey: 'naiveproxy_desc' },
|
|
|
|
|
{ proto: 'wireguard', category: 'protocols', icon: 'lock', title: 'WireGuard', descKey: 'wireguard_desc' },
|
|
|
|
|
{ proto: 'dns', category: 'services', icon: 'search', title: 'AmneziaDNS', descKey: 'dns_desc' },
|
|
|
|
|
{ proto: 'adguard', category: 'services', icon: 'shield-check', title: 'AdGuard Home', descKey: 'adguard_desc' },
|
|
|
|
@@ -1296,6 +1339,7 @@
|
|
|
|
|
case 'xray': title = 'Xray'; break;
|
|
|
|
|
case 'telemt': title = 'Telemt'; break;
|
|
|
|
|
case 'hysteria': title = 'Hysteria 2'; break;
|
|
|
|
|
case 'naiveproxy': title = 'NaiveProxy'; break;
|
|
|
|
|
case 'wireguard': title = 'WireGuard'; break;
|
|
|
|
|
case 'dns': title = 'AmneziaDNS'; break;
|
|
|
|
|
case 'socks5': title = 'SOCKS5 Proxy'; break;
|
|
|
|
@@ -1355,7 +1399,7 @@
|
|
|
|
|
|
|
|
|
|
for (const [proto, info] of orderedProtocolEntries(data.protocols)) {
|
|
|
|
|
updateProtocolCard(proto, info);
|
|
|
|
|
const isVPN = ['awg', 'awg2', 'awg_legacy', 'xray', 'telemt', 'hysteria', 'wireguard'].includes(protoBase(proto));
|
|
|
|
|
const isVPN = ['awg', 'awg2', 'awg_legacy', 'xray', 'telemt', 'hysteria', 'naiveproxy', 'wireguard'].includes(protoBase(proto));
|
|
|
|
|
if (info.container_running && isVPN) {
|
|
|
|
|
const opt = document.createElement('option');
|
|
|
|
|
opt.value = proto;
|
|
|
|
@@ -1583,10 +1627,13 @@
|
|
|
|
|
if (isService) {
|
|
|
|
|
grid = buildServiceInfoGrid(proto, info);
|
|
|
|
|
} else if (info.port) {
|
|
|
|
|
grid += `<div class="protocol-info-item"><span class="protocol-info-label">${_('port')}</span><span class="protocol-info-value">${info.port}/${(['xray', 'telemt'].includes(protoBase(proto))) ? 'TCP' : 'UDP'}</span></div>`;
|
|
|
|
|
grid += `<div class="protocol-info-item"><span class="protocol-info-label">${_('port')}</span><span class="protocol-info-value">${info.port}/${(['xray', 'telemt', 'naiveproxy'].includes(protoBase(proto))) ? 'TCP' : 'UDP'}</span></div>`;
|
|
|
|
|
if (protoBase(proto) === 'hysteria' && info.domain) {
|
|
|
|
|
grid += `<div class="protocol-info-item"><span class="protocol-info-label">${_('hysteria_domain')}</span><span class="protocol-info-value">${info.domain}</span></div>`;
|
|
|
|
|
}
|
|
|
|
|
if (protoBase(proto) === 'naiveproxy' && info.domain) {
|
|
|
|
|
grid += `<div class="protocol-info-item"><span class="protocol-info-label">${_('naiveproxy_domain')}</span><span class="protocol-info-value">${info.domain}</span></div>`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!isService && info.clients_count !== undefined) {
|
|
|
|
|
grid += `<div class="protocol-info-item"><span class="protocol-info-label">${_('connections')}</span><span class="protocol-info-value">${info.clients_count}</span></div>`;
|
|
|
|
@@ -1629,6 +1676,13 @@
|
|
|
|
|
<button class="btn btn-danger btn-sm" onclick="uninstallProtocol('${proto}')">${_('uninstall')}</button>
|
|
|
|
|
`;
|
|
|
|
|
showConnectionsSection();
|
|
|
|
|
} else if (protoBase(proto) === 'naiveproxy') {
|
|
|
|
|
actionsEl.innerHTML = `
|
|
|
|
|
<button class="btn btn-secondary btn-sm" onclick="selectProtocolForConns('${proto}')" style="flex:1">${_('connections')}</button>
|
|
|
|
|
<button class="btn btn-secondary btn-sm" onclick="showProtocolBackups('${proto}')">${_('backup')}</button>
|
|
|
|
|
<button class="btn btn-danger btn-sm" onclick="uninstallProtocol('${proto}')">${_('uninstall')}</button>
|
|
|
|
|
`;
|
|
|
|
|
showConnectionsSection();
|
|
|
|
|
} else {
|
|
|
|
|
actionsEl.innerHTML = `
|
|
|
|
|
<button class="btn btn-secondary btn-sm" onclick="selectProtocolForConns('${proto}')" style="flex:1">${_('connections')}</button>
|
|
|
|
@@ -2007,6 +2061,14 @@
|
|
|
|
|
hint.innerHTML = `${_('hysteria_dns_hint')} <code>A ${domain} ${SERVER_HOST}</code>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateNaiveproxyDnsHint() {
|
|
|
|
|
const input = document.getElementById('installNaiveproxyDomain');
|
|
|
|
|
const hint = document.getElementById('naiveproxyDnsHint');
|
|
|
|
|
if (!input || !hint) return;
|
|
|
|
|
const domain = (input.value || '').trim() || 'vpn.example.com';
|
|
|
|
|
hint.innerHTML = `${_('naiveproxy_dns_hint')} <code>A ${domain} ${SERVER_HOST}</code>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openInstallModal(proto, installAnother = false) {
|
|
|
|
|
const base = protoBase(proto);
|
|
|
|
|
currentInstallProto = installAnother ? base : proto;
|
|
|
|
@@ -2023,12 +2085,14 @@
|
|
|
|
|
const adguardOpts = document.getElementById('adguardOptions');
|
|
|
|
|
const nginxOpts = document.getElementById('nginxOptions');
|
|
|
|
|
const hysteriaOpts = document.getElementById('hysteriaOptions');
|
|
|
|
|
const naiveproxyOpts = document.getElementById('naiveproxyOptions');
|
|
|
|
|
|
|
|
|
|
telemtOpts.style.display = 'none';
|
|
|
|
|
socks5Opts.style.display = 'none';
|
|
|
|
|
adguardOpts.style.display = 'none';
|
|
|
|
|
nginxOpts.style.display = 'none';
|
|
|
|
|
hysteriaOpts.style.display = 'none';
|
|
|
|
|
if (naiveproxyOpts) naiveproxyOpts.style.display = 'none';
|
|
|
|
|
if (portGroup) portGroup.style.display = '';
|
|
|
|
|
|
|
|
|
|
if (base === 'dns') {
|
|
|
|
@@ -2086,6 +2150,16 @@
|
|
|
|
|
if (hyDomain && !hyDomain.value && SERVER_SSL_DOMAIN) hyDomain.value = SERVER_SSL_DOMAIN;
|
|
|
|
|
if (hyEmail && !hyEmail.value && SERVER_SSL_EMAIL) hyEmail.value = SERVER_SSL_EMAIL;
|
|
|
|
|
updateHysteriaDnsHint();
|
|
|
|
|
} else if (base === 'naiveproxy') {
|
|
|
|
|
if (portGroup) portGroup.style.display = 'none';
|
|
|
|
|
portInput.value = '443';
|
|
|
|
|
portInput.disabled = false;
|
|
|
|
|
if (naiveproxyOpts) naiveproxyOpts.style.display = 'block';
|
|
|
|
|
const npDomain = document.getElementById('installNaiveproxyDomain');
|
|
|
|
|
const npEmail = document.getElementById('installNaiveproxyEmail');
|
|
|
|
|
if (npDomain && !npDomain.value && SERVER_SSL_DOMAIN) npDomain.value = SERVER_SSL_DOMAIN;
|
|
|
|
|
if (npEmail && !npEmail.value && SERVER_SSL_EMAIL) npEmail.value = SERVER_SSL_EMAIL;
|
|
|
|
|
updateNaiveproxyDnsHint();
|
|
|
|
|
} else {
|
|
|
|
|
portLabel.textContent = _('port') + ' (UDP)';
|
|
|
|
|
portInput.value = currentInstallAnother ? nextSuggestedPort(currentInstallProto, 55424) : '55424';
|
|
|
|
@@ -2159,6 +2233,10 @@
|
|
|
|
|
}
|
|
|
|
|
params.hysteria_domain = document.getElementById('installHysteriaDomain').value.trim();
|
|
|
|
|
params.hysteria_email = document.getElementById('installHysteriaEmail').value.trim();
|
|
|
|
|
} else if (protoBase(currentInstallProto) === 'naiveproxy') {
|
|
|
|
|
params.port = '443';
|
|
|
|
|
params.naiveproxy_domain = document.getElementById('installNaiveproxyDomain').value.trim();
|
|
|
|
|
params.naiveproxy_email = document.getElementById('installNaiveproxyEmail').value.trim();
|
|
|
|
|
}
|
|
|
|
|
const result = await apiCall(`/api/servers/${SERVER_ID}/install`, 'POST', params);
|
|
|
|
|
clearInterval(progressInterval);
|
|
|
|
@@ -2559,7 +2637,7 @@
|
|
|
|
|
const sent = userData.dataSent || '';
|
|
|
|
|
const initial = name.charAt(0).toUpperCase();
|
|
|
|
|
const enabled = (client.enabled !== undefined) ? client.enabled : (userData.enabled !== false);
|
|
|
|
|
const hasPrivKey = !!userData.clientPrivateKey || proto === 'xray' || proto === 'telemt' || protoBase(proto) === 'hysteria';
|
|
|
|
|
const hasPrivKey = !!userData.clientPrivateKey || proto === 'xray' || proto === 'telemt' || protoBase(proto) === 'hysteria' || protoBase(proto) === 'naiveproxy';
|
|
|
|
|
const assignedUser = client.assigned_user || '';
|
|
|
|
|
|
|
|
|
|
let metaHtml = '';
|
|
|
|
@@ -2597,7 +2675,7 @@
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="client-actions">
|
|
|
|
|
<button class="btn btn-secondary btn-sm btn-icon" onclick="showConnectionConfig('${escapeJs(client.clientId)}', '${escapeJs(name)}', ${!!userData.clientPrivateKey || proto === 'xray' || proto === 'telemt' || protoBase(proto) === 'hysteria'})" title="${_('config')}">${uiIcon('file')}</button>
|
|
|
|
|
<button class="btn btn-secondary btn-sm btn-icon" onclick="showConnectionConfig('${escapeJs(client.clientId)}', '${escapeJs(name)}', ${!!userData.clientPrivateKey || proto === 'xray' || proto === 'telemt' || protoBase(proto) === 'hysteria' || protoBase(proto) === 'naiveproxy'})" title="${_('config')}">${uiIcon('file')}</button>
|
|
|
|
|
${proto === 'telemt' ? `<button class="btn btn-secondary btn-sm btn-icon" onclick="editConnection('${escapeJs(client.clientId)}')" title="${_('edit')}">${uiIcon('pencil')}</button>` : ''}
|
|
|
|
|
<button class="btn btn-secondary btn-sm btn-icon" onclick="toggleConnection('${escapeJs(client.clientId)}', ${!enabled})" title="${toggleTitle}">${toggleIcon}</button>
|
|
|
|
|
<button class="btn btn-danger btn-sm btn-icon" onclick="removeConnection('${escapeJs(client.clientId)}')" title="${_('delete')}">${uiIcon('trash')}</button>
|
|
|
|
@@ -2649,8 +2727,8 @@
|
|
|
|
|
document.getElementById('configModalTitle').textContent = `${_('config')} — ${connName}`;
|
|
|
|
|
document.getElementById('configText').textContent = result.config;
|
|
|
|
|
document.getElementById('vpnLinkText').textContent = currentVpnLink;
|
|
|
|
|
document.getElementById('panel-vpn').style.display = (proto === 'telemt' || protoBase(proto) === 'hysteria' ? 'none' : '');
|
|
|
|
|
document.querySelectorAll('.config-tab')[1].style.display = (proto === 'telemt' || protoBase(proto) === 'hysteria' ? 'none' : '');
|
|
|
|
|
document.getElementById('panel-vpn').style.display = (proto === 'telemt' || protoBase(proto) === 'hysteria' || protoBase(proto) === 'naiveproxy' ? 'none' : '');
|
|
|
|
|
document.querySelectorAll('.config-tab')[1].style.display = (proto === 'telemt' || protoBase(proto) === 'hysteria' || protoBase(proto) === 'naiveproxy' ? 'none' : '');
|
|
|
|
|
switchConfigTab('conf');
|
|
|
|
|
openModal('configModal');
|
|
|
|
|
generateQR(result.config);
|
|
|
|
@@ -2728,9 +2806,9 @@
|
|
|
|
|
const proto = document.getElementById('connProtoSelect').value;
|
|
|
|
|
|
|
|
|
|
// Restore tabs visibility first
|
|
|
|
|
document.getElementById('panel-vpn').style.display = (proto === 'telemt' || protoBase(proto) === 'hysteria' ? 'none' : '');
|
|
|
|
|
document.getElementById('panel-vpn').style.display = (proto === 'telemt' || protoBase(proto) === 'hysteria' || protoBase(proto) === 'naiveproxy' ? 'none' : '');
|
|
|
|
|
document.getElementById('panel-qr').style.display = '';
|
|
|
|
|
document.querySelectorAll('.config-tab')[1].style.display = (proto === 'telemt' || protoBase(proto) === 'hysteria' ? 'none' : '');
|
|
|
|
|
document.querySelectorAll('.config-tab')[1].style.display = (proto === 'telemt' || protoBase(proto) === 'hysteria' || protoBase(proto) === 'naiveproxy' ? 'none' : '');
|
|
|
|
|
document.querySelectorAll('.config-tab')[2].style.display = '';
|
|
|
|
|
|
|
|
|
|
// Client was created via native Amnezia app — private key is not stored server-side
|
|
|
|
|