571 lines
18 KiB
JavaScript
571 lines
18 KiB
JavaScript
/**
|
|
* Admin Listmonk Management Functions
|
|
* Handles admin interface for email list synchronization
|
|
*/
|
|
|
|
// Global variables for admin Listmonk functionality
|
|
let syncInProgress = false;
|
|
let syncProgressInterval = null;
|
|
|
|
/**
|
|
* Initialize Listmonk admin section
|
|
*/
|
|
async function initListmonkAdmin() {
|
|
await refreshListmonkStatus();
|
|
await loadListmonkStats();
|
|
}
|
|
|
|
/**
|
|
* Refresh the Listmonk sync status display
|
|
*/
|
|
async function refreshListmonkStatus() {
|
|
console.log('🔄 Refreshing Listmonk status...');
|
|
try {
|
|
const response = await fetch('/api/listmonk/status', {
|
|
credentials: 'include'
|
|
});
|
|
|
|
console.log('📡 Status response:', response.status, response.statusText);
|
|
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
|
|
const status = await response.json();
|
|
console.log('📊 Status data:', status);
|
|
|
|
updateStatusDisplay(status);
|
|
|
|
// Update global status if available
|
|
if (window.listmonkStatus) {
|
|
window.listmonkStatus.currentStatus = status;
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Failed to refresh Listmonk status:', error);
|
|
updateStatusDisplay({
|
|
enabled: false,
|
|
connected: false,
|
|
lastError: `Status check failed: ${error.message}`
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the status display in the admin panel
|
|
*/
|
|
function updateStatusDisplay(status) {
|
|
console.log('🎨 Updating status display with:', status);
|
|
|
|
const connectionStatus = document.getElementById('connection-status');
|
|
const autosyncStatus = document.getElementById('autosync-status');
|
|
const lastError = document.getElementById('last-error');
|
|
|
|
console.log('🔍 Status elements found:', {
|
|
connectionStatus: !!connectionStatus,
|
|
autosyncStatus: !!autosyncStatus,
|
|
lastError: !!lastError
|
|
});
|
|
|
|
if (connectionStatus) {
|
|
if (status.enabled && status.connected) {
|
|
connectionStatus.innerHTML = '<span class="status-connected">✅ Connected</span>';
|
|
} else if (status.enabled && !status.connected) {
|
|
connectionStatus.innerHTML = '<span class="status-error">❌ Failed</span>';
|
|
} else {
|
|
connectionStatus.innerHTML = '<span class="status-disabled">⭕ Disabled</span>';
|
|
}
|
|
console.log('✅ Connection status updated:', connectionStatus.innerHTML);
|
|
}
|
|
|
|
if (autosyncStatus) {
|
|
if (status.enabled) {
|
|
autosyncStatus.innerHTML = '<span class="status-connected">✅ Enabled</span>';
|
|
} else {
|
|
autosyncStatus.innerHTML = '<span class="status-disabled">⭕ Disabled</span>';
|
|
}
|
|
console.log('✅ Auto-sync status updated:', autosyncStatus.innerHTML);
|
|
}
|
|
|
|
if (lastError) {
|
|
if (status.lastError) {
|
|
lastError.innerHTML = `<span class="status-error">${escapeHtml(status.lastError)}</span>`;
|
|
} else {
|
|
lastError.innerHTML = '<span class="status-connected">None</span>';
|
|
}
|
|
console.log('✅ Last error updated:', lastError.innerHTML);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load and display Listmonk list statistics
|
|
*/
|
|
async function loadListmonkStats() {
|
|
try {
|
|
const response = await fetch('/api/listmonk/stats', {
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
|
|
const data = await response.json();
|
|
console.log('📊 Stats API response:', data); // Debug log
|
|
|
|
if (data.success && data.stats) {
|
|
// Ensure stats is an array
|
|
const statsArray = Array.isArray(data.stats) ? data.stats : [];
|
|
console.log('📊 Stats array:', statsArray); // Debug log
|
|
displayListStats(statsArray);
|
|
} else {
|
|
console.warn('No Listmonk stats available:', data.error || 'Unknown error');
|
|
displayListStats([]);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Failed to load Listmonk stats:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display list statistics in the admin panel
|
|
*/
|
|
function displayListStats(stats) {
|
|
const statusContent = document.getElementById('sync-status-display');
|
|
if (!statusContent) return;
|
|
|
|
console.log('📊 displayListStats called with:', stats, 'Type:', typeof stats); // Debug log
|
|
|
|
// Ensure stats is an array
|
|
const statsArray = Array.isArray(stats) ? stats : [];
|
|
console.log('📊 Stats array after conversion:', statsArray, 'Length:', statsArray.length); // Debug log
|
|
|
|
// Add stats section if it doesn't exist
|
|
let statsSection = document.getElementById('listmonk-stats-section');
|
|
if (!statsSection) {
|
|
statsSection = document.createElement('div');
|
|
statsSection.id = 'listmonk-stats-section';
|
|
statsSection.innerHTML = '<h4>Email Lists</h4>';
|
|
statusContent.appendChild(statsSection);
|
|
}
|
|
|
|
// Clear existing stats
|
|
const existingStats = statsSection.querySelector('.stats-list');
|
|
if (existingStats) {
|
|
existingStats.remove();
|
|
}
|
|
|
|
// Create stats display
|
|
const statsList = document.createElement('div');
|
|
statsList.className = 'stats-list';
|
|
|
|
if (statsArray.length === 0) {
|
|
statsList.innerHTML = '<p class="status-disabled">No email lists found</p>';
|
|
} else {
|
|
statsArray.forEach(list => {
|
|
const statRow = document.createElement('div');
|
|
statRow.className = 'status-row';
|
|
statRow.innerHTML = `
|
|
<span title="${escapeHtml(list.description || '')}">${escapeHtml(list.name)}</span>
|
|
<span class="status-connected">${list.subscriberCount} subscribers</span>
|
|
`;
|
|
statsList.appendChild(statRow);
|
|
});
|
|
}
|
|
|
|
statsSection.appendChild(statsList);
|
|
}
|
|
|
|
/**
|
|
* Sync data to Listmonk
|
|
* @param {string} type - 'locations', 'users', or 'all'
|
|
*/
|
|
async function syncToListmonk(type) {
|
|
if (syncInProgress) {
|
|
showNotification('Sync already in progress', 'warning');
|
|
return;
|
|
}
|
|
|
|
syncInProgress = true;
|
|
|
|
const progressSection = document.getElementById('sync-progress');
|
|
const resultsDiv = document.getElementById('sync-results');
|
|
const progressBar = document.getElementById('sync-progress-bar');
|
|
|
|
// Show progress section
|
|
if (progressSection) {
|
|
progressSection.style.display = 'block';
|
|
}
|
|
|
|
// Reset progress
|
|
if (progressBar) {
|
|
progressBar.style.width = '0%';
|
|
}
|
|
|
|
if (resultsDiv) {
|
|
resultsDiv.innerHTML = '<div class="sync-result info">Starting sync...</div>';
|
|
}
|
|
|
|
// Disable sync buttons
|
|
const buttons = document.querySelectorAll('.sync-buttons .btn');
|
|
buttons.forEach(btn => {
|
|
btn.disabled = true;
|
|
btn.style.opacity = '0.6';
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(`/api/listmonk/sync/${type}`, {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
// Animate progress to 100%
|
|
if (progressBar) {
|
|
progressBar.style.width = '100%';
|
|
}
|
|
|
|
// Display results
|
|
displaySyncResults(result, resultsDiv);
|
|
|
|
// Refresh stats after successful sync
|
|
setTimeout(() => {
|
|
loadListmonkStats();
|
|
}, 1000);
|
|
|
|
} catch (error) {
|
|
console.error('Sync failed:', error);
|
|
|
|
if (resultsDiv) {
|
|
resultsDiv.innerHTML = `
|
|
<div class="sync-result error">
|
|
<strong>Sync Failed:</strong> ${escapeHtml(error.message)}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
showNotification('Sync failed: ' + error.message, 'error');
|
|
} finally {
|
|
syncInProgress = false;
|
|
|
|
// Re-enable sync buttons
|
|
buttons.forEach(btn => {
|
|
btn.disabled = false;
|
|
btn.style.opacity = '1';
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display sync results in the admin panel
|
|
*/
|
|
function displaySyncResults(result, resultsDiv) {
|
|
if (!resultsDiv) return;
|
|
|
|
let html = '';
|
|
|
|
if (result.success) {
|
|
html += `<div class="sync-result success">
|
|
<strong>✅ ${escapeHtml(result.message)}</strong>
|
|
</div>`;
|
|
|
|
// Show detailed results for different sync types
|
|
if (result.results) {
|
|
if (result.results.locations) {
|
|
html += formatSyncResults('Locations', result.results.locations);
|
|
}
|
|
if (result.results.users) {
|
|
html += formatSyncResults('Users', result.results.users);
|
|
}
|
|
|
|
// For single type syncs
|
|
if (result.results.total !== undefined) {
|
|
html += formatSyncResults('Items', result.results);
|
|
}
|
|
}
|
|
|
|
showNotification('Sync completed successfully!', 'success');
|
|
} else {
|
|
html += `<div class="sync-result error">
|
|
<strong>❌ Sync Failed:</strong> ${escapeHtml(result.error)}
|
|
</div>`;
|
|
showNotification('Sync failed', 'error');
|
|
}
|
|
|
|
resultsDiv.innerHTML = html;
|
|
}
|
|
|
|
/**
|
|
* Format sync results for display
|
|
*/
|
|
function formatSyncResults(type, results) {
|
|
let html = `<div class="sync-result info">
|
|
<strong>${type}:</strong>
|
|
${results.success} succeeded, ${results.failed} failed
|
|
(${results.total} total)
|
|
</div>`;
|
|
|
|
// Show errors if any
|
|
if (results.errors && results.errors.length > 0) {
|
|
html += `<div class="sync-result error">
|
|
<strong>Errors:</strong>
|
|
<ul style="margin: 5px 0 0 20px; font-size: 12px;">`;
|
|
|
|
// Show only first 5 errors to avoid overwhelming the UI
|
|
const errorsToShow = results.errors.slice(0, 5);
|
|
errorsToShow.forEach(error => {
|
|
html += `<li>${escapeHtml(error.email || error.id)}: ${escapeHtml(error.error)}</li>`;
|
|
});
|
|
|
|
if (results.errors.length > 5) {
|
|
html += `<li><em>... and ${results.errors.length - 5} more errors</em></li>`;
|
|
}
|
|
|
|
html += `</ul></div>`;
|
|
}
|
|
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* Test Listmonk connection
|
|
*/
|
|
async function testListmonkConnection() {
|
|
try {
|
|
const response = await fetch('/api/listmonk/test-connection', {
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success && result.connected) {
|
|
showNotification('✅ Listmonk connection successful!', 'success');
|
|
} else {
|
|
showNotification('❌ Connection failed: ' + (result.message || 'Unknown error'), 'error');
|
|
}
|
|
|
|
// Refresh status after test
|
|
setTimeout(refreshListmonkStatus, 1000);
|
|
|
|
} catch (error) {
|
|
console.error('Connection test failed:', error);
|
|
showNotification('❌ Connection test failed: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reinitialize Listmonk lists
|
|
*/
|
|
async function reinitializeListmonk() {
|
|
if (!confirm('This will recreate all email lists. Are you sure?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/listmonk/reinitialize', {
|
|
method: 'POST',
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showNotification('✅ Lists reinitialized successfully!', 'success');
|
|
setTimeout(() => {
|
|
refreshListmonkStatus();
|
|
loadListmonkStats();
|
|
}, 1000);
|
|
} else {
|
|
showNotification('❌ Failed to reinitialize: ' + result.message, 'error');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Reinitialize failed:', error);
|
|
showNotification('❌ Reinitialize failed: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility function to escape HTML
|
|
*/
|
|
function escapeHtml(text) {
|
|
if (typeof text !== 'string') return text;
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
/**
|
|
* Fallback notification function if showNotification is not available
|
|
*/
|
|
function showNotification(message, type = 'info') {
|
|
// Check if a global showNotification function exists
|
|
if (typeof window.showNotification === 'function' && window.showNotification !== showNotification) {
|
|
window.showNotification(message, type);
|
|
} else {
|
|
// Simple fallback - log to console and show alert for errors
|
|
console.log(`[${type.toUpperCase()}] ${message}`);
|
|
if (type === 'error') {
|
|
console.error(message);
|
|
alert(`ERROR: ${message}`);
|
|
} else if (type === 'warning') {
|
|
console.warn(message);
|
|
alert(`WARNING: ${message}`);
|
|
} else if (type === 'success') {
|
|
console.info(`SUCCESS: ${message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Auto-initialize when admin page loads
|
|
function initializeListmonkEventListeners() {
|
|
console.log('🔧 Initializing Listmonk event listeners...');
|
|
|
|
// Sync Locations button
|
|
const syncLocationsBtn = document.getElementById('sync-locations-btn');
|
|
if (syncLocationsBtn) {
|
|
syncLocationsBtn.addEventListener('click', () => {
|
|
console.log('📍 Sync Locations clicked');
|
|
syncToListmonk('locations');
|
|
});
|
|
console.log('✅ Sync Locations button listener added');
|
|
} else {
|
|
console.warn('❌ Sync Locations button not found');
|
|
}
|
|
|
|
// Sync Users button
|
|
const syncUsersBtn = document.getElementById('sync-users-btn');
|
|
if (syncUsersBtn) {
|
|
syncUsersBtn.addEventListener('click', () => {
|
|
console.log('👤 Sync Users clicked');
|
|
syncToListmonk('users');
|
|
});
|
|
console.log('✅ Sync Users button listener added');
|
|
} else {
|
|
console.warn('❌ Sync Users button not found');
|
|
}
|
|
|
|
// Sync All button
|
|
const syncAllBtn = document.getElementById('sync-all-btn');
|
|
if (syncAllBtn) {
|
|
syncAllBtn.addEventListener('click', () => {
|
|
console.log('🔄 Sync All clicked');
|
|
syncToListmonk('all');
|
|
});
|
|
console.log('✅ Sync All button listener added');
|
|
} else {
|
|
console.warn('❌ Sync All button not found');
|
|
}
|
|
|
|
// Refresh Status button
|
|
const refreshStatusBtn = document.getElementById('refresh-status-btn');
|
|
if (refreshStatusBtn) {
|
|
refreshStatusBtn.addEventListener('click', () => {
|
|
console.log('🔍 Refresh Status clicked');
|
|
refreshListmonkStatus();
|
|
});
|
|
console.log('✅ Refresh Status button listener added');
|
|
} else {
|
|
console.warn('❌ Refresh Status button not found');
|
|
}
|
|
|
|
// Test Connection button
|
|
const testConnectionBtn = document.getElementById('test-connection-btn');
|
|
if (testConnectionBtn) {
|
|
testConnectionBtn.addEventListener('click', () => {
|
|
console.log('🔗 Test Connection clicked');
|
|
testListmonkConnection();
|
|
});
|
|
console.log('✅ Test Connection button listener added');
|
|
} else {
|
|
console.warn('❌ Test Connection button not found');
|
|
}
|
|
|
|
// Reinitialize Lists button
|
|
const reinitializeListsBtn = document.getElementById('reinitialize-lists-btn');
|
|
if (reinitializeListsBtn) {
|
|
reinitializeListsBtn.addEventListener('click', () => {
|
|
console.log('🛠️ Reinitialize Lists clicked');
|
|
reinitializeListmonk();
|
|
});
|
|
console.log('✅ Reinitialize Lists button listener added');
|
|
} else {
|
|
console.warn('❌ Reinitialize Lists button not found');
|
|
}
|
|
}
|
|
|
|
function attemptInitialization(attempt = 1) {
|
|
console.log(`🔄 Attempting Listmonk initialization (attempt ${attempt})`);
|
|
|
|
// Try to initialize admin and event listeners directly
|
|
try {
|
|
initListmonkAdmin();
|
|
initializeListmonkEventListeners();
|
|
console.log('✅ Listmonk initialization completed successfully');
|
|
return true;
|
|
} catch (error) {
|
|
console.warn(`⚠️ Attempt ${attempt} failed:`, error.message);
|
|
if (attempt < 5) {
|
|
console.log(`⏱️ Retrying in ${attempt * 500}ms...`);
|
|
setTimeout(() => attemptInitialization(attempt + 1), attempt * 500);
|
|
return false;
|
|
} else {
|
|
console.error('❌ All initialization attempts failed');
|
|
// Still try to initialize event listeners as a fallback
|
|
try {
|
|
initializeListmonkEventListeners();
|
|
console.log('✅ Event listeners initialized as fallback');
|
|
} catch (fallbackError) {
|
|
console.error('❌ Even fallback initialization failed:', fallbackError);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Also try a more direct approach - just initialize when the page is ready
|
|
function directInitialization() {
|
|
console.log('🚀 Direct Listmonk initialization...');
|
|
try {
|
|
initListmonkAdmin();
|
|
initializeListmonkEventListeners();
|
|
console.log('✅ Direct initialization successful');
|
|
} catch (error) {
|
|
console.error('Direct initialization failed:', error);
|
|
// Try just the event listeners
|
|
try {
|
|
initializeListmonkEventListeners();
|
|
console.log('✅ Event listeners initialized directly');
|
|
} catch (listenerError) {
|
|
console.error('❌ Even direct event listener initialization failed:', listenerError);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
attemptInitialization();
|
|
// Also try direct initialization after a delay
|
|
setTimeout(directInitialization, 2000);
|
|
});
|
|
} else {
|
|
attemptInitialization();
|
|
// Also try direct initialization after a delay
|
|
setTimeout(directInitialization, 1000);
|
|
}
|
|
|
|
// Export functions for global use
|
|
window.syncToListmonk = syncToListmonk;
|
|
window.refreshListmonkStatus = refreshListmonkStatus;
|
|
window.testListmonkConnection = testListmonkConnection;
|
|
window.reinitializeListmonk = reinitializeListmonk;
|