- Added GitHub spec-kit for development workflow - Standardized file signatures to Descomplicar® format - Updated development configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
862 lines
27 KiB
JavaScript
862 lines
27 KiB
JavaScript
/**
|
|
* Descomplicar® Crescimento Digital
|
|
* https://descomplicar.pt
|
|
*/
|
|
|
|
/**
|
|
* Desk-Moloni Admin JavaScript v3.0
|
|
*
|
|
* Modern ES6+ JavaScript for admin interface
|
|
* Features: Real-time updates, AJAX, Animations, Responsive behavior
|
|
*
|
|
* @package DeskMoloni\Assets
|
|
* @version 3.0
|
|
*/
|
|
|
|
class DeskMoloniAdmin {
|
|
constructor() {
|
|
this.apiUrl = window.location.origin + '/admin/desk_moloni/api/';
|
|
this.refreshInterval = 30000; // 30 seconds
|
|
this.refreshIntervalId = null;
|
|
this.isOnline = navigator.onLine;
|
|
|
|
this.init();
|
|
}
|
|
|
|
/**
|
|
* Initialize the admin interface
|
|
*/
|
|
init() {
|
|
this.bindEvents();
|
|
this.initTooltips();
|
|
this.setupAutoRefresh();
|
|
this.initProgressBars();
|
|
this.setupOfflineDetection();
|
|
|
|
// Load initial data
|
|
if (this.isDashboard()) {
|
|
this.loadDashboardData();
|
|
}
|
|
|
|
console.log('🚀 Desk-Moloni Admin v3.0 initialized');
|
|
}
|
|
|
|
/**
|
|
* Bind event listeners
|
|
*/
|
|
bindEvents() {
|
|
// Dashboard refresh button
|
|
const refreshBtn = document.getElementById('refresh-dashboard');
|
|
if (refreshBtn) {
|
|
refreshBtn.addEventListener('click', () => this.loadDashboardData(true));
|
|
}
|
|
|
|
// Sync buttons
|
|
document.querySelectorAll('[data-sync-action]').forEach(btn => {
|
|
btn.addEventListener('click', (e) => this.handleSyncAction(e));
|
|
});
|
|
|
|
// Filter dropdowns
|
|
document.querySelectorAll('[data-filter]').forEach(filter => {
|
|
filter.addEventListener('click', (e) => this.handleFilter(e));
|
|
});
|
|
|
|
// Form submissions
|
|
document.querySelectorAll('.desk-moloni-form').forEach(form => {
|
|
form.addEventListener('submit', (e) => this.handleFormSubmit(e));
|
|
});
|
|
|
|
// Real-time search
|
|
const searchInput = document.querySelector('[data-search]');
|
|
if (searchInput) {
|
|
let searchTimeout;
|
|
searchInput.addEventListener('input', (e) => {
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(() => this.performSearch(e.target.value), 300);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize tooltips for status indicators
|
|
*/
|
|
initTooltips() {
|
|
document.querySelectorAll('[data-tooltip]').forEach(element => {
|
|
element.addEventListener('mouseenter', (e) => this.showTooltip(e));
|
|
element.addEventListener('mouseleave', (e) => this.hideTooltip(e));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Setup auto-refresh for dashboard
|
|
*/
|
|
setupAutoRefresh() {
|
|
if (!this.isDashboard()) return;
|
|
|
|
this.refreshIntervalId = setInterval(() => {
|
|
if (this.isOnline && !document.hidden) {
|
|
this.loadDashboardData();
|
|
}
|
|
}, this.refreshInterval);
|
|
|
|
// Pause refresh when page is hidden
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.hidden) {
|
|
clearInterval(this.refreshIntervalId);
|
|
} else if (this.isDashboard()) {
|
|
this.loadDashboardData();
|
|
this.setupAutoRefresh();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize animated progress bars
|
|
*/
|
|
initProgressBars() {
|
|
document.querySelectorAll('.desk-moloni-progress__bar').forEach(bar => {
|
|
const width = bar.dataset.width || '0';
|
|
|
|
// Animate to target width
|
|
setTimeout(() => {
|
|
bar.style.width = width + '%';
|
|
}, 100);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Setup offline detection
|
|
*/
|
|
setupOfflineDetection() {
|
|
window.addEventListener('online', () => {
|
|
this.isOnline = true;
|
|
this.showNotification('🌐 Connection restored', 'success');
|
|
if (this.isDashboard()) {
|
|
this.loadDashboardData();
|
|
}
|
|
});
|
|
|
|
window.addEventListener('offline', () => {
|
|
this.isOnline = false;
|
|
this.showNotification('📡 You are offline', 'warning');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Load dashboard data via AJAX
|
|
*/
|
|
async loadDashboardData(showLoader = false) {
|
|
if (!this.isOnline) return;
|
|
|
|
if (showLoader) {
|
|
this.showLoader();
|
|
}
|
|
|
|
try {
|
|
const response = await this.apiCall('dashboard_data');
|
|
|
|
if (response.success) {
|
|
this.updateDashboard(response.data);
|
|
this.updateLastRefresh();
|
|
} else {
|
|
this.showNotification('Failed to load dashboard data', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Dashboard data load error:', error);
|
|
this.showNotification('Failed to connect to server', 'error');
|
|
} finally {
|
|
if (showLoader) {
|
|
this.hideLoader();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update dashboard with new data
|
|
*/
|
|
updateDashboard(data) {
|
|
// Update metrics
|
|
this.updateElement('[data-metric="sync-count"]', data.sync_count || 0);
|
|
this.updateElement('[data-metric="error-count"]', data.error_count || 0);
|
|
this.updateElement('[data-metric="success-rate"]', (data.success_rate || 0) + '%');
|
|
this.updateElement('[data-metric="avg-time"]', (data.avg_execution_time || 0).toFixed(2) + 's');
|
|
|
|
// Update progress bars
|
|
this.updateProgressBar('[data-progress="sync"]', data.sync_progress || 0);
|
|
this.updateProgressBar('[data-progress="queue"]', data.queue_progress || 0);
|
|
|
|
// Update recent activity
|
|
if (data.recent_activity) {
|
|
this.updateRecentActivity(data.recent_activity);
|
|
}
|
|
|
|
// Update status indicators
|
|
this.updateSyncStatus(data.sync_status || 'idle');
|
|
}
|
|
|
|
/**
|
|
* Handle sync actions
|
|
*/
|
|
async handleSyncAction(event) {
|
|
event.preventDefault();
|
|
|
|
const button = event.target.closest('[data-sync-action]');
|
|
const action = button.dataset.syncAction;
|
|
const entityType = button.dataset.entityType || 'all';
|
|
|
|
button.disabled = true;
|
|
button.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Syncing...';
|
|
|
|
try {
|
|
const response = await this.apiCall('sync', {
|
|
action: action,
|
|
entity_type: entityType
|
|
});
|
|
|
|
if (response.success) {
|
|
this.showNotification(`✅ ${action} sync completed successfully`, 'success');
|
|
this.loadDashboardData();
|
|
} else {
|
|
this.showNotification(`❌ Sync failed: ${response.message}`, 'error');
|
|
}
|
|
} catch (error) {
|
|
this.showNotification('❌ Sync request failed', 'error');
|
|
} finally {
|
|
button.disabled = false;
|
|
button.innerHTML = button.dataset.originalText || 'Sync';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle form submissions with AJAX
|
|
*/
|
|
async handleFormSubmit(event) {
|
|
event.preventDefault();
|
|
|
|
const form = event.target;
|
|
const formData = new FormData(form);
|
|
const submitBtn = form.querySelector('[type="submit"]');
|
|
|
|
// Add CSRF token
|
|
if (window.deskMoloniCSRF) {
|
|
formData.append(window.deskMoloniCSRF.token_name, window.deskMoloniCSRF.token_value);
|
|
}
|
|
|
|
// Show loading state
|
|
const originalText = submitBtn.textContent;
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Saving...';
|
|
|
|
try {
|
|
const response = await fetch(form.action, {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
this.showNotification('✅ Settings saved successfully', 'success');
|
|
|
|
// Update CSRF token if provided
|
|
if (result.csrf_token) {
|
|
window.deskMoloniCSRF.updateToken(result.csrf_token);
|
|
}
|
|
} else {
|
|
this.showNotification(`❌ ${result.message || 'Save failed'}`, 'error');
|
|
}
|
|
} catch (error) {
|
|
this.showNotification('❌ Failed to save settings', 'error');
|
|
} finally {
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = originalText;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility: Make API calls
|
|
*/
|
|
async apiCall(endpoint, data = null) {
|
|
const url = this.apiUrl + endpoint;
|
|
const options = {
|
|
method: data ? 'POST' : 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
};
|
|
|
|
if (data) {
|
|
// Add CSRF token to data
|
|
if (window.deskMoloniCSRF) {
|
|
data[window.deskMoloniCSRF.token_name] = window.deskMoloniCSRF.token_value;
|
|
}
|
|
options.body = JSON.stringify(data);
|
|
}
|
|
|
|
const response = await fetch(url, options);
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Update element content with animation
|
|
*/
|
|
updateElement(selector, value) {
|
|
const element = document.querySelector(selector);
|
|
if (!element) return;
|
|
|
|
const currentValue = element.textContent;
|
|
if (currentValue !== value.toString()) {
|
|
element.style.transform = 'scale(1.1)';
|
|
element.style.color = 'var(--dm-primary)';
|
|
|
|
setTimeout(() => {
|
|
element.textContent = value;
|
|
element.style.transform = 'scale(1)';
|
|
element.style.color = '';
|
|
}, 150);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update progress bar
|
|
*/
|
|
updateProgressBar(selector, percentage) {
|
|
const progressBar = document.querySelector(selector + ' .desk-moloni-progress__bar');
|
|
if (progressBar) {
|
|
progressBar.style.width = percentage + '%';
|
|
progressBar.dataset.width = percentage;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show notification
|
|
*/
|
|
showNotification(message, type = 'info') {
|
|
// Create notification element if it doesn't exist
|
|
let container = document.getElementById('desk-moloni-notifications');
|
|
if (!container) {
|
|
container = document.createElement('div');
|
|
container.id = 'desk-moloni-notifications';
|
|
container.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
z-index: 10000;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
`;
|
|
document.body.appendChild(container);
|
|
}
|
|
|
|
const notification = document.createElement('div');
|
|
notification.className = `desk-moloni-notification desk-moloni-notification--${type}`;
|
|
notification.style.cssText = `
|
|
padding: 12px 20px;
|
|
border-radius: 6px;
|
|
color: white;
|
|
font-weight: 500;
|
|
box-shadow: var(--dm-shadow-lg);
|
|
transform: translateX(100%);
|
|
transition: transform 0.3s ease;
|
|
max-width: 300px;
|
|
word-wrap: break-word;
|
|
`;
|
|
|
|
// Set background color based on type
|
|
const colors = {
|
|
success: 'var(--dm-success)',
|
|
error: 'var(--dm-error)',
|
|
warning: 'var(--dm-warning)',
|
|
info: 'var(--dm-info)'
|
|
};
|
|
notification.style.background = colors[type] || colors.info;
|
|
|
|
notification.textContent = message;
|
|
container.appendChild(notification);
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
notification.style.transform = 'translateX(0)';
|
|
}, 10);
|
|
|
|
// Auto remove after 5 seconds
|
|
setTimeout(() => {
|
|
notification.style.transform = 'translateX(100%)';
|
|
setTimeout(() => {
|
|
if (notification.parentNode) {
|
|
notification.parentNode.removeChild(notification);
|
|
}
|
|
}, 300);
|
|
}, 5000);
|
|
}
|
|
|
|
/**
|
|
* Show/hide loader
|
|
*/
|
|
showLoader() {
|
|
document.body.classList.add('desk-moloni-loading');
|
|
}
|
|
|
|
hideLoader() {
|
|
document.body.classList.remove('desk-moloni-loading');
|
|
}
|
|
|
|
/**
|
|
* Check if current page is dashboard
|
|
*/
|
|
isDashboard() {
|
|
return window.location.href.includes('desk_moloni') &&
|
|
(window.location.href.includes('dashboard') || window.location.href.endsWith('desk_moloni'));
|
|
}
|
|
|
|
/**
|
|
* Update last refresh time
|
|
*/
|
|
updateLastRefresh() {
|
|
const element = document.querySelector('[data-last-refresh]');
|
|
if (element) {
|
|
element.textContent = 'Last updated: ' + new Date().toLocaleTimeString();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
window.deskMoloniAdmin = new DeskMoloniAdmin();
|
|
});
|
|
function initDeskMoloni() {
|
|
// Initialize components
|
|
initSyncControls();
|
|
initConfigValidation();
|
|
initQueueMonitoring();
|
|
initLogViewer();
|
|
}
|
|
|
|
/**
|
|
* Initialize sync control buttons
|
|
*/
|
|
function initSyncControls() {
|
|
const syncButtons = document.querySelectorAll('.desk-moloni-sync-btn');
|
|
|
|
syncButtons.forEach(function(button) {
|
|
button.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
const action = this.dataset.action;
|
|
const entityType = this.dataset.entityType;
|
|
const entityId = this.dataset.entityId;
|
|
|
|
performSync(action, entityType, entityId, this);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Perform synchronization action
|
|
*/
|
|
function performSync(action, entityType, entityId, button) {
|
|
// Show loading state
|
|
const originalText = button.textContent;
|
|
button.disabled = true;
|
|
button.innerHTML = '<span class="desk-moloni-loading"></span> Syncing...';
|
|
|
|
// Prepare data
|
|
const data = {
|
|
action: action,
|
|
entity_type: entityType,
|
|
entity_id: entityId,
|
|
csrf_token: getCSRFToken()
|
|
};
|
|
|
|
// Make AJAX request
|
|
fetch(admin_url + 'desk_moloni/admin/sync_action', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: JSON.stringify(data)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showNotification('Sync completed successfully', 'success');
|
|
refreshSyncStatus();
|
|
} else {
|
|
showNotification('Sync failed: ' + (data.message || 'Unknown error'), 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Sync error:', error);
|
|
showNotification('Sync failed: Network error', 'error');
|
|
})
|
|
.finally(() => {
|
|
// Restore button state
|
|
button.disabled = false;
|
|
button.textContent = originalText;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize configuration form validation
|
|
*/
|
|
function initConfigValidation() {
|
|
const configForm = document.getElementById('desk-moloni-config-form');
|
|
|
|
if (configForm) {
|
|
configForm.addEventListener('submit', function(e) {
|
|
if (!validateConfigForm(this)) {
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
});
|
|
|
|
// Real-time validation for OAuth credentials
|
|
const clientIdField = document.getElementById('oauth_client_id');
|
|
const clientSecretField = document.getElementById('oauth_client_secret');
|
|
|
|
if (clientIdField && clientSecretField) {
|
|
clientIdField.addEventListener('blur', validateOAuthCredentials);
|
|
clientSecretField.addEventListener('blur', validateOAuthCredentials);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate configuration form
|
|
*/
|
|
function validateConfigForm(form) {
|
|
let isValid = true;
|
|
const errors = [];
|
|
|
|
// Validate required fields
|
|
const requiredFields = form.querySelectorAll('[required]');
|
|
requiredFields.forEach(function(field) {
|
|
if (!field.value.trim()) {
|
|
isValid = false;
|
|
errors.push(field.getAttribute('data-label') + ' is required');
|
|
field.classList.add('error');
|
|
} else {
|
|
field.classList.remove('error');
|
|
}
|
|
});
|
|
|
|
// Validate OAuth Client ID format
|
|
const clientId = form.querySelector('#oauth_client_id');
|
|
if (clientId && clientId.value && !isValidUUID(clientId.value)) {
|
|
isValid = false;
|
|
errors.push('OAuth Client ID must be a valid UUID');
|
|
clientId.classList.add('error');
|
|
}
|
|
|
|
// Show errors
|
|
if (!isValid) {
|
|
showNotification('Please fix the following errors:\n' + errors.join('\n'), 'error');
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
/**
|
|
* Validate OAuth credentials
|
|
*/
|
|
function validateOAuthCredentials() {
|
|
const clientId = document.getElementById('oauth_client_id').value;
|
|
const clientSecret = document.getElementById('oauth_client_secret').value;
|
|
|
|
if (clientId && clientSecret) {
|
|
// Test OAuth credentials
|
|
fetch(admin_url + 'desk_moloni/admin/test_oauth', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: JSON.stringify({
|
|
client_id: clientId,
|
|
client_secret: clientSecret,
|
|
csrf_token: getCSRFToken()
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const statusElement = document.getElementById('oauth-status');
|
|
if (statusElement) {
|
|
if (data.valid) {
|
|
statusElement.innerHTML = '<span class="desk-moloni-status success">Valid</span>';
|
|
} else {
|
|
statusElement.innerHTML = '<span class="desk-moloni-status error">Invalid</span>';
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('OAuth validation error:', error);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize queue monitoring
|
|
*/
|
|
function initQueueMonitoring() {
|
|
const queueTable = document.getElementById('desk-moloni-queue-table');
|
|
|
|
if (queueTable) {
|
|
// Auto-refresh every 30 seconds
|
|
setInterval(refreshQueueStatus, 30000);
|
|
|
|
// Add action buttons functionality
|
|
const actionButtons = queueTable.querySelectorAll('.queue-action-btn');
|
|
actionButtons.forEach(function(button) {
|
|
button.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
const action = this.dataset.action;
|
|
const queueId = this.dataset.queueId;
|
|
|
|
performQueueAction(action, queueId);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh queue status
|
|
*/
|
|
function refreshQueueStatus() {
|
|
fetch(admin_url + 'desk_moloni/admin/get_queue_status', {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
updateQueueTable(data.queue_items);
|
|
updateQueueStats(data.stats);
|
|
})
|
|
.catch(error => {
|
|
console.error('Queue refresh error:', error);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update queue table
|
|
*/
|
|
function updateQueueTable(queueItems) {
|
|
const tableBody = document.querySelector('#desk-moloni-queue-table tbody');
|
|
|
|
if (tableBody && queueItems) {
|
|
tableBody.innerHTML = '';
|
|
|
|
queueItems.forEach(function(item) {
|
|
const row = createQueueTableRow(item);
|
|
tableBody.appendChild(row);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create queue table row
|
|
*/
|
|
function createQueueTableRow(item) {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>${item.id}</td>
|
|
<td>${item.entity_type}</td>
|
|
<td>${item.entity_id}</td>
|
|
<td><span class="desk-moloni-status ${item.status}">${item.status}</span></td>
|
|
<td>${item.priority}</td>
|
|
<td>${item.attempts}/${item.max_attempts}</td>
|
|
<td>${item.created_at}</td>
|
|
<td>
|
|
${item.status === 'failed' ? `<button class="desk-moloni-btn desk-moloni-btn-small queue-action-btn" data-action="retry" data-queue-id="${item.id}">Retry</button>` : ''}
|
|
<button class="desk-moloni-btn desk-moloni-btn-danger desk-moloni-btn-small queue-action-btn" data-action="cancel" data-queue-id="${item.id}">Cancel</button>
|
|
</td>
|
|
`;
|
|
|
|
return row;
|
|
}
|
|
|
|
/**
|
|
* Perform queue action
|
|
*/
|
|
function performQueueAction(action, queueId) {
|
|
fetch(admin_url + 'desk_moloni/admin/queue_action', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: JSON.stringify({
|
|
action: action,
|
|
queue_id: queueId,
|
|
csrf_token: getCSRFToken()
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showNotification('Action completed successfully', 'success');
|
|
refreshQueueStatus();
|
|
} else {
|
|
showNotification('Action failed: ' + (data.message || 'Unknown error'), 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Queue action error:', error);
|
|
showNotification('Action failed: Network error', 'error');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize log viewer
|
|
*/
|
|
function initLogViewer() {
|
|
const logViewer = document.getElementById('desk-moloni-log-viewer');
|
|
|
|
if (logViewer) {
|
|
// Auto-refresh logs every 60 seconds
|
|
setInterval(refreshLogs, 60000);
|
|
|
|
// Add filter controls
|
|
const filterForm = document.getElementById('log-filter-form');
|
|
if (filterForm) {
|
|
filterForm.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
refreshLogs();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh logs
|
|
*/
|
|
function refreshLogs() {
|
|
const filterForm = document.getElementById('log-filter-form');
|
|
const formData = new FormData(filterForm);
|
|
const params = new URLSearchParams(formData);
|
|
|
|
fetch(admin_url + 'desk_moloni/admin/get_logs?' + params.toString(), {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const logViewer = document.getElementById('desk-moloni-log-viewer');
|
|
if (logViewer && data.logs) {
|
|
logViewer.innerHTML = data.logs.map(log =>
|
|
`<div class="desk-moloni-log-line ${log.level}">[${log.timestamp}] ${log.level.toUpperCase()}: ${log.message}</div>`
|
|
).join('');
|
|
|
|
// Scroll to bottom
|
|
logViewer.scrollTop = logViewer.scrollHeight;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Log refresh error:', error);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Show notification
|
|
*/
|
|
function showNotification(message, type) {
|
|
// Create notification element
|
|
const notification = document.createElement('div');
|
|
notification.className = 'desk-moloni-notification ' + type;
|
|
notification.textContent = message;
|
|
|
|
// Insert at top of content area
|
|
const contentArea = document.querySelector('.content-area') || document.body;
|
|
contentArea.insertBefore(notification, contentArea.firstChild);
|
|
|
|
// Auto-remove after 5 seconds
|
|
setTimeout(function() {
|
|
if (notification.parentNode) {
|
|
notification.parentNode.removeChild(notification);
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
/**
|
|
* Refresh sync status indicators
|
|
*/
|
|
function refreshSyncStatus() {
|
|
fetch(admin_url + 'desk_moloni/admin/get_sync_status', {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
updateSyncStatusElements(data);
|
|
})
|
|
.catch(error => {
|
|
console.error('Sync status refresh error:', error);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update sync status elements
|
|
*/
|
|
function updateSyncStatusElements(data) {
|
|
// Update status indicators
|
|
const statusElements = document.querySelectorAll('[data-sync-status]');
|
|
statusElements.forEach(function(element) {
|
|
const entityType = element.dataset.syncStatus;
|
|
if (data[entityType]) {
|
|
element.className = 'desk-moloni-status ' + data[entityType].status;
|
|
element.textContent = data[entityType].status;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Utility functions
|
|
*/
|
|
|
|
/**
|
|
* Get CSRF token
|
|
*/
|
|
function getCSRFToken() {
|
|
const tokenElement = document.querySelector('meta[name="csrf-token"]');
|
|
return tokenElement ? tokenElement.getAttribute('content') : '';
|
|
}
|
|
|
|
/**
|
|
* Validate UUID format
|
|
*/
|
|
function isValidUUID(uuid) {
|
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
return uuidRegex.test(uuid);
|
|
}
|
|
|
|
/**
|
|
* Update queue statistics
|
|
*/
|
|
function updateQueueStats(stats) {
|
|
if (stats) {
|
|
const statElements = {
|
|
'pending': document.getElementById('queue-stat-pending'),
|
|
'processing': document.getElementById('queue-stat-processing'),
|
|
'completed': document.getElementById('queue-stat-completed'),
|
|
'failed': document.getElementById('queue-stat-failed')
|
|
};
|
|
|
|
Object.keys(statElements).forEach(function(key) {
|
|
const element = statElements[key];
|
|
if (element && stats[key] !== undefined) {
|
|
element.textContent = stats[key];
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
})(); |