466 lines
16 KiB
JavaScript
466 lines
16 KiB
JavaScript
/**
|
|
* Admin Listmonk Management Functions for Influence System
|
|
* 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}: ${response.statusText}`);
|
|
}
|
|
|
|
const status = await response.json();
|
|
console.log('📊 Status data:', status);
|
|
|
|
updateStatusDisplay(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 style="color: #27ae60;">Connected</span>';
|
|
} else if (status.enabled) {
|
|
connectionStatus.innerHTML = '❌ <span style="color: #e74c3c;">Connection Failed</span>';
|
|
} else {
|
|
connectionStatus.innerHTML = '⭕ <span style="color: #95a5a6;">Disabled</span>';
|
|
}
|
|
console.log('✅ Connection status updated:', connectionStatus.innerHTML);
|
|
}
|
|
|
|
if (autosyncStatus) {
|
|
if (status.enabled) {
|
|
autosyncStatus.innerHTML = '✅ <span style="color: #27ae60;">Enabled</span>';
|
|
} else {
|
|
autosyncStatus.innerHTML = '⭕ <span style="color: #95a5a6;">Disabled</span>';
|
|
}
|
|
console.log('✅ Auto-sync status updated:', autosyncStatus.innerHTML);
|
|
}
|
|
|
|
if (lastError) {
|
|
if (status.lastError) {
|
|
lastError.style.display = 'block';
|
|
lastError.innerHTML = `<strong>⚠️ Last Error:</strong> ${escapeHtml(status.lastError)}`;
|
|
} else {
|
|
lastError.style.display = 'none';
|
|
}
|
|
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}: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('📊 Stats API response:', data);
|
|
|
|
if (data.success && data.stats) {
|
|
displayListStats(data.stats);
|
|
} else {
|
|
console.error('Stats API returned unsuccessful response:', data);
|
|
displayListStats([]);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Failed to load Listmonk stats:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display list statistics in the admin panel
|
|
*/
|
|
function displayListStats(stats) {
|
|
const statsSection = document.getElementById('listmonk-stats-section');
|
|
if (!statsSection) return;
|
|
|
|
console.log('📊 displayListStats called with:', stats, 'Type:', typeof stats);
|
|
|
|
// Ensure stats is an array
|
|
const statsArray = Array.isArray(stats) ? stats : [];
|
|
console.log('📊 Stats array after conversion:', statsArray, 'Length:', statsArray.length);
|
|
|
|
// 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';
|
|
statsList.style.cssText = 'display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem;';
|
|
|
|
if (statsArray.length === 0) {
|
|
statsList.innerHTML = '<p style="color: #666; text-align: center; grid-column: 1/-1;">No email lists found or sync is disabled</p>';
|
|
} else {
|
|
statsArray.forEach(list => {
|
|
const statCard = document.createElement('div');
|
|
statCard.style.cssText = 'background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);';
|
|
statCard.innerHTML = `
|
|
<h4 style="margin: 0 0 0.5rem 0; font-size: 0.9rem; opacity: 0.9;">${escapeHtml(list.name)}</h4>
|
|
<p style="margin: 0; font-size: 2rem; font-weight: bold;">${list.subscriberCount || 0}</p>
|
|
<p style="margin: 0.25rem 0 0 0; font-size: 0.85rem; opacity: 0.9;">subscribers</p>
|
|
`;
|
|
statsList.appendChild(statCard);
|
|
});
|
|
}
|
|
|
|
statsSection.appendChild(statsList);
|
|
}
|
|
|
|
/**
|
|
* Sync data to Listmonk
|
|
* @param {string} type - 'participants', 'recipients', 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%';
|
|
progressBar.textContent = '0%';
|
|
}
|
|
|
|
if (resultsDiv) {
|
|
resultsDiv.innerHTML = '<div class="sync-result info"><strong>⏳ Starting sync...</strong></div>';
|
|
}
|
|
|
|
// Disable sync buttons
|
|
const buttons = document.querySelectorAll('.sync-buttons .btn');
|
|
buttons.forEach(btn => {
|
|
btn.disabled = true;
|
|
btn.style.opacity = '0.6';
|
|
});
|
|
|
|
// Simulate progress
|
|
let progress = 0;
|
|
const progressInterval = setInterval(() => {
|
|
if (progress < 90) {
|
|
progress += Math.random() * 10;
|
|
if (progressBar) {
|
|
progressBar.style.width = `${Math.min(progress, 90)}%`;
|
|
progressBar.textContent = `${Math.floor(Math.min(progress, 90))}%`;
|
|
}
|
|
}
|
|
}, 200);
|
|
|
|
try {
|
|
let endpoint = '/api/listmonk/sync/';
|
|
let syncName = '';
|
|
|
|
switch(type) {
|
|
case 'participants':
|
|
endpoint += 'participants';
|
|
syncName = 'Campaign Participants';
|
|
break;
|
|
case 'recipients':
|
|
endpoint += 'recipients';
|
|
syncName = 'Custom Recipients';
|
|
break;
|
|
case 'all':
|
|
endpoint += 'all';
|
|
syncName = 'All Data';
|
|
break;
|
|
default:
|
|
throw new Error('Invalid sync type');
|
|
}
|
|
|
|
const response = await fetch(endpoint, {
|
|
method: 'POST',
|
|
credentials: 'include'
|
|
});
|
|
|
|
clearInterval(progressInterval);
|
|
|
|
if (progressBar) {
|
|
progressBar.style.width = '100%';
|
|
progressBar.textContent = '100%';
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
displaySyncResults(result, resultsDiv);
|
|
|
|
// Refresh stats after successful sync
|
|
await loadListmonkStats();
|
|
|
|
} catch (error) {
|
|
clearInterval(progressInterval);
|
|
console.error('Sync failed:', error);
|
|
if (resultsDiv) {
|
|
resultsDiv.innerHTML = `
|
|
<div class="sync-result error" style="background: #f8d7da; color: #721c24; padding: 1rem; border-radius: 8px; border-left: 4px solid #e74c3c;">
|
|
<strong>❌ Sync Failed</strong>
|
|
<p style="margin: 0.5rem 0 0 0;">${escapeHtml(error.message)}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
} 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" style="background: #d4edda; color: #155724; padding: 1rem; border-radius: 8px; border-left: 4px solid #27ae60; margin-bottom: 1rem;">
|
|
<strong>✅ ${escapeHtml(result.message || 'Sync completed successfully')}</strong>
|
|
</div>`;
|
|
|
|
if (result.results) {
|
|
// Handle different result structures
|
|
if (result.results.participants || result.results.customRecipients) {
|
|
// Multi-type sync (all)
|
|
if (result.results.participants) {
|
|
html += formatSyncResults('Campaign Participants', result.results.participants);
|
|
}
|
|
if (result.results.customRecipients) {
|
|
html += formatSyncResults('Custom Recipients', result.results.customRecipients);
|
|
}
|
|
} else {
|
|
// Single type sync
|
|
html += formatSyncResults('Results', result.results);
|
|
}
|
|
}
|
|
} else {
|
|
html += `<div class="sync-result error" style="background: #f8d7da; color: #721c24; padding: 1rem; border-radius: 8px; border-left: 4px solid #e74c3c;">
|
|
<strong>❌ Sync Failed</strong>
|
|
<p style="margin: 0.5rem 0 0 0;">${escapeHtml(result.error || result.message || 'Unknown error')}</p>
|
|
</div>`;
|
|
}
|
|
|
|
resultsDiv.innerHTML = html;
|
|
}
|
|
|
|
/**
|
|
* Format sync results for display
|
|
*/
|
|
function formatSyncResults(type, results) {
|
|
let html = `<div class="sync-result info" style="background: #e3f2fd; padding: 1rem; border-radius: 8px; border-left: 4px solid #2196f3; margin-bottom: 0.5rem;">
|
|
<strong>📊 ${escapeHtml(type)}:</strong>
|
|
<span style="color: #27ae60; font-weight: bold;">${results.success} succeeded</span>,
|
|
<span style="color: #e74c3c; font-weight: bold;">${results.failed} failed</span>
|
|
(${results.total} total)
|
|
</div>`;
|
|
|
|
// Show errors if any
|
|
if (results.errors && results.errors.length > 0) {
|
|
const maxErrors = 5; // Show max 5 errors
|
|
const errorCount = results.errors.length;
|
|
|
|
html += `<div class="sync-result warning" style="background: #fff3cd; color: #856404; padding: 1rem; border-radius: 8px; border-left: 4px solid #f39c12; margin-bottom: 0.5rem;">
|
|
<strong>⚠️ Errors (showing ${Math.min(errorCount, maxErrors)} of ${errorCount}):</strong>
|
|
<ul style="margin: 0.5rem 0 0 0; padding-left: 1.5rem;">`;
|
|
|
|
results.errors.slice(0, maxErrors).forEach(error => {
|
|
html += `<li style="margin: 0.25rem 0;">${escapeHtml(error.email || 'Unknown')}: ${escapeHtml(error.error || 'Unknown error')}</li>`;
|
|
});
|
|
|
|
html += '</ul></div>';
|
|
}
|
|
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* Test Listmonk connection
|
|
*/
|
|
async function testListmonkConnection() {
|
|
try {
|
|
const response = await fetch('/api/listmonk/test-connection', {
|
|
method: 'POST',
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showNotification('✅ Listmonk connection successful!', 'success');
|
|
await refreshListmonkStatus();
|
|
} else {
|
|
showNotification(`❌ Connection failed: ${result.message}`, 'error');
|
|
}
|
|
} 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}: ${response.statusText}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showNotification('✅ Listmonk lists reinitialized successfully!', 'success');
|
|
await refreshListmonkStatus();
|
|
await loadListmonkStats();
|
|
} else {
|
|
showNotification(`❌ Reinitialization failed: ${result.message}`, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Reinitialization failed:', error);
|
|
showNotification(`❌ Reinitialization failed: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility function to escape HTML
|
|
*/
|
|
function escapeHtml(text) {
|
|
if (typeof text !== 'string') return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
/**
|
|
* Show notification message
|
|
*/
|
|
function showNotification(message, type = 'info') {
|
|
const messageContainer = document.getElementById('message-container');
|
|
if (!messageContainer) {
|
|
// Fallback to alert if container not found
|
|
alert(message);
|
|
return;
|
|
}
|
|
|
|
const alertClass = type === 'success' ? 'message-success' :
|
|
type === 'error' ? 'message-error' :
|
|
'message-info';
|
|
|
|
messageContainer.className = alertClass;
|
|
messageContainer.textContent = message;
|
|
messageContainer.classList.remove('hidden');
|
|
|
|
// Auto-hide after 5 seconds
|
|
setTimeout(() => {
|
|
messageContainer.classList.add('hidden');
|
|
}, 5000);
|
|
}
|
|
|
|
// Initialize Listmonk admin when tab is activated
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Watch for tab changes
|
|
const listmonkTab = document.querySelector('[data-tab="listmonk"]');
|
|
if (listmonkTab) {
|
|
listmonkTab.addEventListener('click', () => {
|
|
// Initialize on first load
|
|
if (!listmonkTab.dataset.initialized) {
|
|
initListmonkAdmin();
|
|
listmonkTab.dataset.initialized = 'true';
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Export functions for global use
|
|
window.syncToListmonk = syncToListmonk;
|
|
window.refreshListmonkStatus = refreshListmonkStatus;
|
|
window.testListmonkConnection = testListmonkConnection;
|
|
window.reinitializeListmonk = reinitializeListmonk;
|
|
window.initListmonkAdmin = initListmonkAdmin;
|