freealberta/influence/app/public/js/listmonk-admin.js
admin 4d8b9effd0 feat(blog): add detailed update on Influence and Map app developments since August
A bunch of udpates to the listmonk sync to add influence to it
2025-10-25 12:45:35 -06:00

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;