Files
Amnezia-Web-Panel-main/templates/my_connections.html
T
orohiandCursor 183071f588 Wire Hysteria 2 UI with per-server SSL defaults and renew.
Expose domain/email on servers, warn that TCP 80/443 must be free on install, and allow Let's Encrypt re-issue from Hysteria settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 09:46:20 +03:00

163 lines
6.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% from "macros/icons.html" import icon %}
{% block title_extra %} — {{ _('my_connections_title') }}{% endblock %}
{% block content %}
<section>
<h1 class="section-title">
<span class="icon">{{ icon('layers') }}</span>
{{ _('my_connections_title') }}
</h1>
{% if connections %}
<div class="clients-list" id="myConnectionsList">
{% for c in connections %}
<div class="client-item">
<div class="client-info">
<div class="client-avatar">{{ icon('link') }}</div>
<div>
<div class="client-name">{{ c.name or 'VPN Connection' }}</div>
<div class="client-meta">
<span style="display:inline-flex;align-items:center;gap:4px;">{{ icon('server') }} {{ c.server_name }}</span>
<span class="badge badge-info" style="font-size:0.65rem;">
{{ 'AmneziaWG' if c.protocol == 'awg' else ('AmneziaWG 2.0' if c.protocol == 'awg2' else ('AWG Legacy' if c.protocol == 'awg_legacy' else
('Xray' if c.protocol == 'xray' else c.protocol | upper))) }}
</span>
{% if c.created_at %}
<span>📅 {{ c.created_at[:10] }}</span>
{% endif %}
</div>
</div>
</div>
<div class="client-actions">
<button class="btn btn-primary btn-sm"
onclick="showMyConfig('{{ c.id }}', '{{ c.name or 'Connection' }}', '{{ c.protocol }}')">
{{ _('show_config') }}
</button>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<div class="empty-icon">{{ icon('link') }}</div>
<div class="empty-title">{{ _('no_connections') }}</div>
<div class="empty-desc">{{ _('no_connections_user_desc') }}</div>
</div>
{% endif %}
</section>
<!-- ===== Config Modal ===== -->
<div class="modal-backdrop" id="configModal">
<div class="modal" style="max-width: 600px;">
<div class="modal-header">
<h2 class="modal-title" id="configModalTitle">{{ _('config') }}</h2>
<button class="modal-close" onclick="closeModal('configModal')">×</button>
</div>
<div class="config-tabs">
<button class="config-tab active" onclick="switchConfigTab('conf')">{{ _('config_tab') }}</button>
<button class="config-tab" onclick="switchConfigTab('vpn')">{{ _('vpn_key_tab') }}</button>
<button class="config-tab" onclick="switchConfigTab('qr')">{{ _('qr_code_tab') }}</button>
</div>
<div class="config-panel active" id="panel-conf">
<div class="config-display">
<div class="config-text" id="configText"></div>
<div class="config-actions">
<button class="btn btn-secondary btn-sm" onclick="copyToClipboard(currentConfig)" style="flex:1">
{{ _('copy_config') }}
</button>
<button class="btn btn-primary btn-sm"
onclick="downloadFile(currentConfig, currentConfigName + '.conf')" style="flex:1">
{{ _('download_conf') }}
</button>
</div>
</div>
</div>
<div class="config-panel" id="panel-vpn">
<div class="vpn-link-box" id="vpnLinkText"></div>
<div class="config-actions" style="margin-top: var(--space-sm);">
<button class="btn btn-secondary btn-sm" onclick="copyToClipboard(currentVpnLink)" style="flex:1">
{{ _('copy_key') }}
</button>
</div>
<div class="form-hint" style="margin-top: var(--space-sm);">
{{ _('vpn_link_hint') }}
</div>
</div>
<div class="config-panel" id="panel-qr">
<div class="qr-container">
<div id="qrCode"></div>
<div class="qr-hint">{{ _('qr_hint') }}</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let currentConfig = '';
let currentVpnLink = '';
let currentConfigName = 'connection';
async function showMyConfig(connectionId, name, proto) {
try {
showToast(_('loading_connections'), 'info');
const result = await apiCall(`/api/my/connections/${connectionId}/config`, 'POST');
if (result.config) {
currentConfig = result.config;
currentVpnLink = result.vpn_link || '';
currentConfigName = name.replace(/\s+/g, '_');
document.getElementById('configModalTitle').textContent = `${_('config')}${name}`;
document.getElementById('configText').textContent = result.config;
document.getElementById('vpnLinkText').textContent = currentVpnLink;
// Telemt / Hysteria use share links, not vpn:// Amnezia format
const vpnTab = document.querySelectorAll('.config-tab')[1];
const vpnPanel = document.getElementById('panel-vpn');
if (proto === 'telemt' || String(proto || '').split('__')[0] === 'hysteria') {
vpnTab.style.display = 'none';
} else {
vpnTab.style.display = '';
}
switchConfigTab('conf');
openModal('configModal');
generateQR(result.config);
}
} catch (err) {
showToast(`${_('error')}: ` + err.message, 'error');
}
}
function switchConfigTab(tab) {
document.querySelectorAll('.config-tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.config-panel').forEach(p => p.classList.remove('active'));
const tabs = document.querySelectorAll('.config-tab');
const panels = { conf: 'panel-conf', vpn: 'panel-vpn', qr: 'panel-qr' };
const tabIdx = { conf: 0, vpn: 1, qr: 2 };
tabs[tabIdx[tab]].classList.add('active');
document.getElementById(panels[tab]).classList.add('active');
}
function generateQR(text) {
const container = document.getElementById('qrCode');
container.innerHTML = '';
try {
new QRCode(container, {
text: text, width: 280, height: 280,
colorDark: '#000000', colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.L
});
} catch (e) {
container.innerHTML = `<div style="color:var(--text-muted);font-size:0.85rem;">${_('qr_error')}</div>`;
}
}
</script>
{% endblock %}