listmonk sync
This commit is contained in:
@@ -56,6 +56,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(() => {
|
||||
loadWalkSheetConfig();
|
||||
initializeNocodbLinks();
|
||||
initializeListmonkLinks();
|
||||
}, 100);
|
||||
|
||||
// Check if URL has a hash to show specific section
|
||||
@@ -1998,6 +1999,88 @@ function setAdminNocodbLink(elementId, url) {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Listmonk links in admin panel
|
||||
async function initializeListmonkLinks() {
|
||||
console.log('Starting Listmonk links initialization...');
|
||||
|
||||
try {
|
||||
// Since we're in the admin panel, the user is already verified as admin
|
||||
// by the requireAdmin middleware. Let's get the URLs from the server directly.
|
||||
console.log('Fetching Listmonk URLs for admin panel...');
|
||||
const configResponse = await fetch('/api/admin/listmonk-urls');
|
||||
|
||||
if (!configResponse.ok) {
|
||||
throw new Error(`Listmonk URLs fetch failed: ${configResponse.status} ${configResponse.statusText}`);
|
||||
}
|
||||
|
||||
const config = await configResponse.json();
|
||||
console.log('Listmonk URLs received:', config);
|
||||
|
||||
if (config.success && config.listmonkUrls) {
|
||||
console.log('Setting up Listmonk links with URLs:', config.listmonkUrls);
|
||||
|
||||
// Set up admin dashboard Listmonk links
|
||||
setAdminListmonkLink('admin-listmonk-admin-link', config.listmonkUrls.adminUrl);
|
||||
setAdminListmonkLink('admin-listmonk-lists-link', config.listmonkUrls.listsUrl);
|
||||
setAdminListmonkLink('admin-listmonk-campaigns-link', config.listmonkUrls.campaignsUrl);
|
||||
setAdminListmonkLink('admin-listmonk-subscribers-link', config.listmonkUrls.subscribersUrl);
|
||||
setAdminListmonkLink('admin-listmonk-settings-link', config.listmonkUrls.settingsUrl);
|
||||
|
||||
console.log('Listmonk links initialized in admin panel');
|
||||
} else {
|
||||
console.warn('No Listmonk URLs found in admin config response');
|
||||
// Hide the Listmonk section if no URLs are available
|
||||
const listmonkSection = document.getElementById('listmonk-links');
|
||||
const listmonkNav = document.querySelector('.admin-nav a[href="#listmonk-links"]');
|
||||
if (listmonkSection) {
|
||||
listmonkSection.style.display = 'none';
|
||||
console.log('Hidden Listmonk section');
|
||||
}
|
||||
if (listmonkNav) {
|
||||
listmonkNav.style.display = 'none';
|
||||
console.log('Hidden Listmonk nav link');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error initializing Listmonk links in admin panel:', error);
|
||||
// Hide the Listmonk section on error
|
||||
const listmonkSection = document.getElementById('listmonk-links');
|
||||
const listmonkNav = document.querySelector('.admin-nav a[href="#listmonk-links"]');
|
||||
if (listmonkSection) {
|
||||
listmonkSection.style.display = 'none';
|
||||
console.log('Hidden Listmonk section due to error');
|
||||
}
|
||||
if (listmonkNav) {
|
||||
listmonkNav.style.display = 'none';
|
||||
console.log('Hidden Listmonk nav link due to error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to set admin Listmonk link href
|
||||
function setAdminListmonkLink(elementId, url) {
|
||||
console.log(`Setting up Listmonk link: ${elementId} = ${url}`);
|
||||
const element = document.getElementById(elementId);
|
||||
|
||||
if (element && url) {
|
||||
element.href = url;
|
||||
element.style.display = 'inline-flex';
|
||||
// Remove any disabled state
|
||||
element.classList.remove('btn-disabled');
|
||||
element.removeAttribute('disabled');
|
||||
console.log(`✓ Successfully set up ${elementId}`);
|
||||
} else if (element) {
|
||||
element.style.display = 'none';
|
||||
// Add disabled state if no URL
|
||||
element.classList.add('btn-disabled');
|
||||
element.setAttribute('disabled', 'disabled');
|
||||
element.href = '#';
|
||||
console.log(`⚠ Disabled ${elementId} - no URL provided`);
|
||||
} else {
|
||||
console.error(`✗ Element not found: ${elementId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Shift User Management Functions
|
||||
let currentShiftData = null;
|
||||
let allUsers = [];
|
||||
|
||||
570
map/app/public/js/listmonk-admin.js
Normal file
570
map/app/public/js/listmonk-admin.js
Normal file
@@ -0,0 +1,570 @@
|
||||
/**
|
||||
* 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;
|
||||
225
map/app/public/js/listmonk-status.js
Normal file
225
map/app/public/js/listmonk-status.js
Normal file
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* Listmonk Status Manager
|
||||
* Handles real-time status monitoring and user notifications for email list sync
|
||||
*/
|
||||
|
||||
class ListmonkStatus {
|
||||
constructor() {
|
||||
this.statusElement = null;
|
||||
this.checkInterval = null;
|
||||
this.lastErrorShown = null;
|
||||
this.currentStatus = {
|
||||
enabled: false,
|
||||
connected: false,
|
||||
lastError: null
|
||||
};
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Only initialize if user is authenticated
|
||||
if (window.currentUser) {
|
||||
this.createStatusIndicator();
|
||||
this.startStatusCheck();
|
||||
}
|
||||
}
|
||||
|
||||
createStatusIndicator() {
|
||||
// Create status indicator element
|
||||
const indicator = document.createElement('div');
|
||||
indicator.id = 'listmonk-status';
|
||||
indicator.className = 'listmonk-status-indicator checking';
|
||||
indicator.innerHTML = `
|
||||
<span class="status-icon">⏳</span>
|
||||
<span class="status-text">Email Sync</span>
|
||||
`;
|
||||
|
||||
// Find a good place to put it - try header actions first
|
||||
let container = document.querySelector('.header-actions');
|
||||
if (!container) {
|
||||
// Fallback to header nav
|
||||
container = document.querySelector('.header-nav');
|
||||
}
|
||||
if (!container) {
|
||||
// Fallback to header itself
|
||||
container = document.querySelector('header');
|
||||
}
|
||||
|
||||
if (container) {
|
||||
container.appendChild(indicator);
|
||||
this.statusElement = indicator;
|
||||
}
|
||||
}
|
||||
|
||||
async checkStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/listmonk/status', {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const status = await response.json();
|
||||
this.updateIndicator(status);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to check Listmonk status:', error);
|
||||
this.updateIndicator({
|
||||
enabled: false,
|
||||
connected: false,
|
||||
lastError: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateIndicator(status) {
|
||||
if (!this.statusElement) return;
|
||||
|
||||
const icon = this.statusElement.querySelector('.status-icon');
|
||||
const text = this.statusElement.querySelector('.status-text');
|
||||
|
||||
// Update current status
|
||||
const wasConnected = this.currentStatus.connected;
|
||||
this.currentStatus = status;
|
||||
|
||||
if (!status.enabled) {
|
||||
this.statusElement.className = 'listmonk-status-indicator disabled';
|
||||
icon.innerHTML = '⭕';
|
||||
text.textContent = 'Sync Off';
|
||||
this.statusElement.title = 'Email list synchronization is disabled';
|
||||
} else if (status.connected) {
|
||||
this.statusElement.className = 'listmonk-status-indicator connected';
|
||||
icon.innerHTML = '✅';
|
||||
text.textContent = 'Sync On';
|
||||
this.statusElement.title = 'Email lists are synchronizing automatically';
|
||||
|
||||
// If we just reconnected, show a brief success message
|
||||
if (!wasConnected && this.lastErrorShown) {
|
||||
this.showReconnectedNotification();
|
||||
}
|
||||
} else {
|
||||
this.statusElement.className = 'listmonk-status-indicator error';
|
||||
icon.innerHTML = '❌';
|
||||
text.textContent = 'Sync Error';
|
||||
this.statusElement.title = status.lastError || 'Email list sync failed - check configuration';
|
||||
|
||||
// Show popup warning if this is a new error or first time seeing this error
|
||||
const errorKey = status.lastError + (status.lastErrorTime || '');
|
||||
if ((!this.lastErrorShown || this.lastErrorShown !== errorKey) && status.lastError) {
|
||||
this.showSyncError(status.lastError);
|
||||
this.lastErrorShown = errorKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showSyncError(error) {
|
||||
// Don't spam notifications
|
||||
if (document.querySelector('.sync-error-notification')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create and show error notification
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'sync-error-notification';
|
||||
notification.innerHTML = `
|
||||
<div class="notification-header">
|
||||
<strong>⚠️ Email Sync Error</strong>
|
||||
<button class="close-btn" onclick="this.parentElement.parentElement.remove()">×</button>
|
||||
</div>
|
||||
<div class="notification-body">
|
||||
<p>The email list synchronization is not working:</p>
|
||||
<code>${this.escapeHtml(error || 'Connection failed')}</code>
|
||||
<p>New contacts will be saved locally but won't sync to email lists until this is resolved.</p>
|
||||
<p>Please contact your administrator if this persists.</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Auto-remove after 15 seconds
|
||||
setTimeout(() => {
|
||||
if (notification && notification.parentNode) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 15000);
|
||||
}
|
||||
|
||||
showReconnectedNotification() {
|
||||
// Don't show if there's already a notification
|
||||
if (document.querySelector('.sync-success-notification')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'sync-success-notification';
|
||||
notification.innerHTML = `
|
||||
<div class="notification-header">
|
||||
<strong>✅ Email Sync Restored</strong>
|
||||
<button class="close-btn" onclick="this.parentElement.parentElement.remove()">×</button>
|
||||
</div>
|
||||
<div class="notification-body">
|
||||
<p>Email list synchronization is working again. New contacts will now sync automatically.</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (notification && notification.parentNode) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
startStatusCheck() {
|
||||
// Check immediately
|
||||
this.checkStatus();
|
||||
|
||||
// Then check every 30 seconds
|
||||
this.checkInterval = setInterval(() => {
|
||||
this.checkStatus();
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
stopStatusCheck() {
|
||||
if (this.checkInterval) {
|
||||
clearInterval(this.checkInterval);
|
||||
this.checkInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Utility method for HTML escaping
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Manual refresh method for admin panel
|
||||
async refresh() {
|
||||
if (this.statusElement) {
|
||||
this.statusElement.className = 'listmonk-status-indicator checking';
|
||||
this.statusElement.querySelector('.status-icon').innerHTML = '⏳';
|
||||
}
|
||||
await this.checkStatus();
|
||||
}
|
||||
|
||||
// Get current status (for admin panel)
|
||||
getCurrentStatus() {
|
||||
return { ...this.currentStatus };
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Only create if we don't already have one
|
||||
if (!window.listmonkStatus) {
|
||||
window.listmonkStatus = new ListmonkStatus();
|
||||
}
|
||||
});
|
||||
|
||||
// Export for use in other modules
|
||||
window.ListmonkStatus = ListmonkStatus;
|
||||
Reference in New Issue
Block a user