forked from test2/Amnezia-Web-Panel-main
Allow manual 3x-ui sync without prior save; persist form credentials.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1422,10 +1422,14 @@ async def _xui_fetch_clients(client: httpx.AsyncClient, base_url: str, headers:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
async def sync_users_with_xui(data: dict):
|
async def sync_users_with_xui(data: dict, *, force: bool = False):
|
||||||
"""Import / update panel users from an existing 3x-ui instance."""
|
"""Import / update panel users from an existing 3x-ui instance.
|
||||||
|
|
||||||
|
force=True: used by the manual "Sync now" button (ignores xui_sync_users flag).
|
||||||
|
Background jobs keep force=False and only run when xui_sync_users is enabled.
|
||||||
|
"""
|
||||||
settings = data.get('settings', {}).get('sync', {})
|
settings = data.get('settings', {}).get('sync', {})
|
||||||
if not settings.get('xui_sync_users'):
|
if not force and not settings.get('xui_sync_users'):
|
||||||
return 0, "3x-ui synchronization is disabled in settings"
|
return 0, "3x-ui synchronization is disabled in settings"
|
||||||
|
|
||||||
url = (settings.get('xui_url') or '').strip()
|
url = (settings.get('xui_url') or '').strip()
|
||||||
@@ -4042,8 +4046,34 @@ async def api_xui_sync_now(request: Request):
|
|||||||
if not _check_admin(request):
|
if not _check_admin(request):
|
||||||
return JSONResponse({'error': 'Forbidden'}, status_code=403)
|
return JSONResponse({'error': 'Forbidden'}, status_code=403)
|
||||||
data = load_data()
|
data = load_data()
|
||||||
count, msg = await sync_users_with_xui(data)
|
|
||||||
return {'status': 'success', 'count': count, 'message': msg}
|
# Allow syncing with credentials from the request body (form may not be saved yet)
|
||||||
|
try:
|
||||||
|
body = await request.json()
|
||||||
|
except Exception:
|
||||||
|
body = {}
|
||||||
|
if isinstance(body, dict) and body:
|
||||||
|
sync_cfg = data.setdefault('settings', {}).setdefault('sync', {})
|
||||||
|
for key in (
|
||||||
|
'xui_url', 'xui_username', 'xui_password', 'xui_api_token',
|
||||||
|
'xui_create_conns', 'xui_server_id', 'xui_protocol', 'xui_sync_users', 'xui_sync',
|
||||||
|
):
|
||||||
|
if key in body and body[key] is not None:
|
||||||
|
sync_cfg[key] = body[key]
|
||||||
|
# Persist so background sync / next page load keep the values
|
||||||
|
async with DATA_LOCK:
|
||||||
|
current = load_data()
|
||||||
|
current.setdefault('settings', {}).setdefault('sync', {}).update(sync_cfg)
|
||||||
|
save_data(current)
|
||||||
|
data = current
|
||||||
|
|
||||||
|
count, msg = await sync_users_with_xui(data, force=True)
|
||||||
|
ok = count > 0 or (isinstance(msg, str) and msg.startswith('Successfully'))
|
||||||
|
return {
|
||||||
|
'status': 'success' if ok else 'error',
|
||||||
|
'count': count,
|
||||||
|
'message': msg,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.post('/api/settings/xui_sync_delete', tags=["Settings"])
|
@app.post('/api/settings/xui_sync_delete', tags=["Settings"])
|
||||||
|
|||||||
+19
-7
@@ -881,23 +881,35 @@
|
|||||||
const btn = document.getElementById('xuiSyncNowBtn');
|
const btn = document.getElementById('xuiSyncNowBtn');
|
||||||
const text = document.getElementById('xuiSyncNowBtnText');
|
const text = document.getElementById('xuiSyncNowBtnText');
|
||||||
const spinner = document.getElementById('xuiSyncNowSpinner');
|
const spinner = document.getElementById('xuiSyncNowSpinner');
|
||||||
|
const syncForm = document.getElementById('syncForm');
|
||||||
|
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
text.textContent = _('sync_running');
|
text.textContent = _('sync_running');
|
||||||
spinner.classList.remove('hidden');
|
spinner.classList.remove('hidden');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/settings/xui_sync_now', { method: 'POST' });
|
const payload = {
|
||||||
|
xui_sync: true,
|
||||||
|
xui_sync_users: true,
|
||||||
|
xui_url: syncForm.xui_url.value.trim(),
|
||||||
|
xui_username: syncForm.xui_username.value.trim(),
|
||||||
|
xui_password: syncForm.xui_password.value,
|
||||||
|
xui_api_token: syncForm.xui_api_token.value.trim(),
|
||||||
|
xui_create_conns: syncForm.xui_create_conns.checked,
|
||||||
|
xui_server_id: parseInt(syncForm.xui_server_id.value || '0'),
|
||||||
|
xui_protocol: syncForm.xui_protocol.value
|
||||||
|
};
|
||||||
|
const res = await fetch('/api/settings/xui_sync_now', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
showToast(_('sync_success').replace('{}', data.count), 'success');
|
showToast(_('sync_success').replace('{}', data.count), 'success');
|
||||||
if (data.message && data.count === 0) {
|
setTimeout(() => window.location.reload(), 1500);
|
||||||
showToast(data.message, 'error');
|
|
||||||
} else {
|
|
||||||
setTimeout(() => window.location.reload(), 1500);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(data.message || 'Error occurred');
|
throw new Error(data.message || data.error || 'Error occurred');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast(`${_('error')}: ` + err.message, 'error');
|
showToast(`${_('error')}: ` + err.message, 'error');
|
||||||
|
|||||||
Reference in New Issue
Block a user