+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -502,15 +581,53 @@
}
}
+ function updateProtocolsForXuiSync() {
+ const serverIdx = document.querySelector('[name="xui_server_id"]').value;
+ const protoSelect = document.getElementById('xuiSyncProtocolSelect');
+ protoSelect.innerHTML = '';
+
+ if (serverIdx === '' || !serversData[serverIdx]) return;
+
+ const protocols = serversData[serverIdx].protocols || {};
+ let count = 0;
+
+ for (const [key, info] of Object.entries(protocols)) {
+ if (info.installed) {
+ const opt = document.createElement('option');
+ opt.value = key;
+ opt.textContent = key === 'awg' ? 'AmneziaWG' : (key === 'awg2' ? 'AmneziaWG 2.0' : (key === 'awg_legacy' ? 'AWG Legacy' : (key === 'xray' ? 'Xray' : key.toUpperCase())));
+ if (key === "{{ settings.sync.xui_protocol }}") opt.selected = true;
+ protoSelect.appendChild(opt);
+ count++;
+ }
+ }
+
+ if (count === 0) {
+ const opt = document.createElement('option');
+ opt.textContent = _('no_protocols');
+ opt.disabled = true;
+ protoSelect.appendChild(opt);
+ }
+ }
+
document.querySelector('[name="remnawave_sync"]').addEventListener('change', (e) => {
document.getElementById('remnawaveFields').style.display = e.target.checked ? 'block' : 'none';
});
+ document.querySelector('[name="xui_sync"]').addEventListener('change', (e) => {
+ document.getElementById('xuiFields').style.display = e.target.checked ? 'block' : 'none';
+ });
+
document.getElementById('syncCreateConns').addEventListener('change', (e) => {
document.getElementById('autoConnFields').style.display = e.target.checked ? 'block' : 'none';
if (e.target.checked) updateProtocolsForSync();
});
+ document.getElementById('xuiSyncCreateConns').addEventListener('change', (e) => {
+ document.getElementById('xuiAutoConnFields').style.display = e.target.checked ? 'block' : 'none';
+ if (e.target.checked) updateProtocolsForXuiSync();
+ });
+
document.getElementById('ssl_enabled').addEventListener('change', (e) => {
document.getElementById('sslFields').style.display = e.target.checked ? 'block' : 'none';
});
@@ -760,6 +877,64 @@
}
}
+ async function syncXuiNow() {
+ const btn = document.getElementById('xuiSyncNowBtn');
+ const text = document.getElementById('xuiSyncNowBtnText');
+ const spinner = document.getElementById('xuiSyncNowSpinner');
+
+ btn.disabled = true;
+ text.textContent = _('sync_running');
+ spinner.classList.remove('hidden');
+
+ try {
+ const res = await fetch('/api/settings/xui_sync_now', { method: 'POST' });
+ const data = await res.json();
+ if (data.status === 'success') {
+ showToast(_('sync_success').replace('{}', data.count), 'success');
+ if (data.message && data.count === 0) {
+ showToast(data.message, 'error');
+ } else {
+ setTimeout(() => window.location.reload(), 1500);
+ }
+ } else {
+ throw new Error(data.message || 'Error occurred');
+ }
+ } catch (err) {
+ showToast(`${_('error')}: ` + err.message, 'error');
+ } finally {
+ btn.disabled = false;
+ text.textContent = `🔄 ${_('sync_now_btn')}`;
+ spinner.classList.add('hidden');
+ }
+ }
+
+ async function deleteSyncXui() {
+ if (!confirm(_('delete_sync_confirm'))) return;
+
+ const btn = document.getElementById('xuiSyncDelBtn');
+ const text = document.getElementById('xuiSyncDelBtnText');
+ const spinner = document.getElementById('xuiSyncDelSpinner');
+
+ btn.disabled = true;
+ text.textContent = _('deleting');
+ spinner.classList.remove('hidden');
+
+ try {
+ const res = await fetch('/api/settings/xui_sync_delete', { method: 'POST' });
+ const data = await res.json();
+ if (data.status === 'success') {
+ showToast(_('sync_deleted').replace('{}', data.count), 'success');
+ setTimeout(() => window.location.reload(), 1500);
+ }
+ } catch (err) {
+ showToast(`${_('error')}: ` + err.message, 'error');
+ } finally {
+ btn.disabled = false;
+ text.textContent = `🗑 ${_('delete_sync_btn')}`;
+ spinner.classList.add('hidden');
+ }
+ }
+
async function saveSettings() {
const btn = document.getElementById('saveBtn');
const spinner = document.getElementById('saveSpinner');
@@ -782,7 +957,16 @@
remnawave_sync_users: syncForm.remnawave_sync_users.checked,
remnawave_create_conns: syncForm.remnawave_create_conns.checked,
remnawave_server_id: parseInt(syncForm.remnawave_server_id.value || '0'),
- remnawave_protocol: syncForm.remnawave_protocol.value
+ remnawave_protocol: syncForm.remnawave_protocol.value,
+ xui_sync: syncForm.xui_sync.checked,
+ xui_url: syncForm.xui_url.value,
+ xui_username: syncForm.xui_username.value,
+ xui_password: syncForm.xui_password.value,
+ xui_api_token: syncForm.xui_api_token.value,
+ xui_sync_users: syncForm.xui_sync_users.checked,
+ xui_create_conns: syncForm.xui_create_conns.checked,
+ xui_server_id: parseInt(syncForm.xui_server_id.value || '0'),
+ xui_protocol: syncForm.xui_protocol.value
};
diff --git a/translations/en.json b/translations/en.json
index cf10d58..70643a7 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -212,6 +212,14 @@
"api_key_label": "API Key",
"enable_sync": "Enable synchronization",
"sync_hint": "Users will be created, disabled, and deleted automatically based on Remnawave data",
+ "xui_url_label": "3x-ui URL",
+ "xui_url_hint": "Include scheme, port and webBasePath if configured (example: https://host:2053/secret)",
+ "xui_api_token_label": "API Token (preferred)",
+ "xui_api_token_placeholder": "Settings → Security → API Token",
+ "xui_api_token_hint": "If set, username/password login is skipped",
+ "xui_username_label": "3x-ui Username",
+ "xui_password_label": "3x-ui Password",
+ "xui_sync_hint": "Clients from 3x-ui inbounds will be created, disabled, and deleted as panel users (matched by email)",
"sync_now_btn": "🔄 Sync now",
"delete_sync_btn": "🗑 Delete synchronization",
"auto_create_conns": "Create connections automatically",
diff --git a/translations/fa.json b/translations/fa.json
index fd7096c..4fcfef6 100644
--- a/translations/fa.json
+++ b/translations/fa.json
@@ -210,6 +210,14 @@
"api_key_label": "کلید API",
"enable_sync": "فعالسازی همگامسازی",
"sync_hint": "کاربران طبق دادههای Remnawave بهطور خودکار مدیریت میشوند",
+ "xui_url_label": "آدرس 3x-ui",
+ "xui_url_hint": "شامل پروتکل، پورت و webBasePath در صورت وجود",
+ "xui_api_token_label": "توکن API (ترجیحی)",
+ "xui_api_token_placeholder": "Settings → Security → API Token",
+ "xui_api_token_hint": "در صورت تنظیم، ورود با نام کاربری/رمز رد میشود",
+ "xui_username_label": "نام کاربری 3x-ui",
+ "xui_password_label": "رمز عبور 3x-ui",
+ "xui_sync_hint": "کلاینتهای 3x-ui بر اساس email به کاربران پنل همگام میشوند",
"sync_now_btn": "🔄 همگامسازی اکنون",
"delete_sync_btn": "🗑 حذف همگامسازی",
"auto_create_conns": "ایجاد خودکار اتصالها",
diff --git a/translations/fr.json b/translations/fr.json
index 4f4bdc3..0a74ec4 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -210,6 +210,14 @@
"api_key_label": "Clé API",
"enable_sync": "Activer synchronisation",
"sync_hint": "Synchro automatique avec Remnawave",
+ "xui_url_label": "URL 3x-ui",
+ "xui_url_hint": "Inclure le schéma, le port et webBasePath si configuré",
+ "xui_api_token_label": "Jeton API (préféré)",
+ "xui_api_token_placeholder": "Settings → Security → API Token",
+ "xui_api_token_hint": "Si défini, le login/mot de passe est ignoré",
+ "xui_username_label": "Identifiant 3x-ui",
+ "xui_password_label": "Mot de passe 3x-ui",
+ "xui_sync_hint": "Les clients 3x-ui deviennent des utilisateurs du panneau (liés par email)",
"sync_now_btn": "🔄 Sync maintenant",
"delete_sync_btn": "🗑 Supprimer synchro",
"auto_create_conns": "Créer connexions auto",
diff --git a/translations/ru.json b/translations/ru.json
index e55b9b5..fc239f1 100644
--- a/translations/ru.json
+++ b/translations/ru.json
@@ -212,6 +212,14 @@
"api_key_label": "API Key",
"enable_sync": "Включить синхронизацию",
"sync_hint": "Пользователи будут создаваться, отключаться и удаляться автоматически на основе данных из Remnawave",
+ "xui_url_label": "URL 3x-ui",
+ "xui_url_hint": "Укажите схему, порт и webBasePath при наличии (пример: https://host:2053/secret)",
+ "xui_api_token_label": "API Token (предпочтительно)",
+ "xui_api_token_placeholder": "Settings → Security → API Token",
+ "xui_api_token_hint": "Если задан, вход по логину/паролю не используется",
+ "xui_username_label": "Логин 3x-ui",
+ "xui_password_label": "Пароль 3x-ui",
+ "xui_sync_hint": "Клиенты из inbounds 3x-ui будут создаваться, отключаться и удаляться как пользователи панели (связь по email)",
"sync_now_btn": "🔄 Синхронизировать сейчас",
"delete_sync_btn": "🗑 Удалить синхронизацию",
"auto_create_conns": "Создавать подключения автоматически",
diff --git a/translations/zh.json b/translations/zh.json
index a611051..ef69fd2 100644
--- a/translations/zh.json
+++ b/translations/zh.json
@@ -210,6 +210,14 @@
"api_key_label": "API 密钥",
"enable_sync": "开启同步",
"sync_hint": "将根据 Remnawave 数据自动创建、禁用和删除用户",
+ "xui_url_label": "3x-ui 地址",
+ "xui_url_hint": "包含协议、端口和 webBasePath(如有)",
+ "xui_api_token_label": "API Token(推荐)",
+ "xui_api_token_placeholder": "Settings → Security → API Token",
+ "xui_api_token_hint": "若已设置,将跳过用户名/密码登录",
+ "xui_username_label": "3x-ui 用户名",
+ "xui_password_label": "3x-ui 密码",
+ "xui_sync_hint": "3x-ui 客户端将按 email 同步为面板用户",
"sync_now_btn": "🔄 立即同步",
"delete_sync_btn": "🗑 删除同步数据",
"auto_create_conns": "自动创建连接",