Files
crm.clientright.ru/crm_extensions/file_storage/js/file_sync_sse.js
Fedor 9245768987 🚀 CRM Files Migration & Real-time Features
 Features:
- Migrated ALL files to new S3 structure (Projects, Contacts, Accounts, HelpDesk, Invoice, etc.)
- Added Nextcloud folder buttons to ALL modules
- Fixed Nextcloud editor integration
- WebSocket server for real-time updates
- Redis Pub/Sub integration
- File path manager for organized storage
- Redis caching for performance (Functions.php)

📁 New Structure:
Documents/Project/ProjectName_ID/file_docID.ext
Documents/Contacts/FirstName_LastName_ID/file_docID.ext
Documents/Accounts/AccountName_ID/file_docID.ext

🔧 Technical:
- FilePathManager for standardized paths
- S3StorageService integration
- WebSocket server (Node.js + Docker)
- Redis cache for getBasicModuleInfo()
- Predis library for Redis connectivity

📝 Scripts:
- Migration scripts for all modules
- Test pages for WebSocket/SSE/Polling
- Documentation (MIGRATION_*.md, REDIS_*.md)

🎯 Result: 15,000+ files migrated successfully!
2025-10-24 19:59:28 +03:00

295 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* SSE (Server-Sent Events) клиент для синхронизации файлов в реальном времени
*
* Автоматически подключается к SSE endpoint и обновляет UI при изменениях файлов
*/
class FileSyncSSE {
constructor() {
this.eventSource = null;
this.reconnectInterval = 5000; // 5 секунд
this.maxReconnectAttempts = 10;
this.reconnectAttempts = 0;
this.isConnected = false;
this.init();
}
init() {
console.log('🔄 Инициализация SSE для синхронизации файлов...');
this.connect();
}
connect() {
try {
// Закрываем предыдущее соединение
if (this.eventSource) {
this.eventSource.close();
}
// Создаем новое SSE соединение
this.eventSource = new EventSource('/crm_extensions/file_storage/api/sse_events.php');
// Обработчик успешного подключения
this.eventSource.onopen = (event) => {
console.log('✅ SSE подключение установлено');
this.isConnected = true;
this.reconnectAttempts = 0;
this.showConnectionStatus('connected');
};
// Обработчик сообщений
this.eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.handleEvent(data);
} catch (error) {
console.error('❌ Ошибка парсинга SSE данных:', error);
}
};
// Обработчик ошибок
this.eventSource.onerror = (event) => {
console.error('❌ SSE ошибка:', event);
this.isConnected = false;
this.showConnectionStatus('disconnected');
// Попытка переподключения
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`🔄 Попытка переподключения ${this.reconnectAttempts}/${this.maxReconnectAttempts}...`);
setTimeout(() => {
this.connect();
}, this.reconnectInterval);
} else {
console.error('❌ Максимальное количество попыток переподключения достигнуто');
this.showConnectionStatus('failed');
}
};
} catch (error) {
console.error('❌ Ошибка создания SSE соединения:', error);
this.showConnectionStatus('error');
}
}
handleEvent(data) {
console.log('📨 SSE событие:', data);
switch (data.type) {
case 'connected':
console.log('✅ SSE подключен:', data.data.message);
break;
case 'disconnected':
console.log('❌ SSE отключен:', data.data.message);
break;
case 'heartbeat':
// Heartbeat - просто обновляем статус
break;
case 'file_created':
this.handleFileCreated(data.data);
break;
case 'file_updated':
this.handleFileUpdated(data.data);
break;
case 'file_deleted':
this.handleFileDeleted(data.data);
break;
case 'folder_renamed':
this.handleFolderRenamed(data.data);
break;
case 'folder_deleted':
this.handleFolderDeleted(data.data);
break;
default:
console.log('❓ Неизвестное SSE событие:', data.type);
}
}
handleFileCreated(data) {
console.log('📄 Файл создан:', data);
// Показываем уведомление
this.showNotification('Файл добавлен', `Файл "${data.fileName}" добавлен в ${data.module}`, 'success');
// Обновляем список файлов если мы на странице детального просмотра
this.refreshFileList(data.module, data.recordId);
}
handleFileUpdated(data) {
console.log('📝 Файл обновлен:', data);
// Показываем уведомление
this.showNotification('Файл обновлен', `Файл "${data.fileName}" обновлен в ${data.module}`, 'info');
// Обновляем список файлов
this.refreshFileList(data.module, data.recordId);
}
handleFileDeleted(data) {
console.log('🗑️ Файл удален:', data);
// Показываем уведомление
this.showNotification('Файл удален', `Файл "${data.fileName}" удален из ${data.module}`, 'warning');
// Обновляем список файлов
this.refreshFileList(data.module, data.recordId);
}
handleFolderRenamed(data) {
console.log('📁 Папка переименована:', data);
// Показываем уведомление
this.showNotification('Папка переименована', `Папка переименована в ${data.module}`, 'info');
// Обновляем список файлов
this.refreshFileList(data.module, data.recordId);
}
handleFolderDeleted(data) {
console.log('🗂️ Папка удалена:', data);
// Показываем уведомление
this.showNotification('Папка удалена', `Папка удалена из ${data.module}`, 'error');
// Обновляем список файлов
this.refreshFileList(data.module, data.recordId);
}
refreshFileList(module, recordId) {
// Проверяем, находимся ли мы на странице детального просмотра нужного модуля
const currentModule = window.location.search.match(/module=([^&]+)/);
const currentRecord = window.location.search.match(/record=([^&]+)/);
if (currentModule && currentModule[1] === module &&
currentRecord && currentRecord[1] === recordId) {
console.log('🔄 Обновляем список файлов...');
// Обновляем страницу или конкретный блок с файлами
if (typeof refreshFileList === 'function') {
refreshFileList();
} else {
// Fallback - обновляем всю страницу
setTimeout(() => {
window.location.reload();
}, 1000);
}
}
}
showNotification(title, message, type = 'info') {
// Используем существующую систему уведомлений CRM
if (typeof Vtiger_Helper_Js !== 'undefined' && Vtiger_Helper_Js.showPnotify) {
Vtiger_Helper_Js.showPnotify({
title: title,
text: message,
type: type,
delay: 5000
});
} else {
// Fallback - обычный alert
alert(`${title}: ${message}`);
}
}
showConnectionStatus(status) {
// Создаем или обновляем индикатор статуса подключения
let statusElement = document.getElementById('sse-connection-status');
if (!statusElement) {
statusElement = document.createElement('div');
statusElement.id = 'sse-connection-status';
statusElement.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
padding: 8px 12px;
border-radius: 4px;
font-size: 12px;
z-index: 9999;
transition: all 0.3s ease;
`;
document.body.appendChild(statusElement);
}
switch (status) {
case 'connected':
statusElement.textContent = '🟢 Файлы синхронизируются';
statusElement.style.backgroundColor = '#d4edda';
statusElement.style.color = '#155724';
statusElement.style.border = '1px solid #c3e6cb';
break;
case 'disconnected':
statusElement.textContent = '🟡 Переподключение...';
statusElement.style.backgroundColor = '#fff3cd';
statusElement.style.color = '#856404';
statusElement.style.border = '1px solid #ffeaa7';
break;
case 'failed':
statusElement.textContent = '🔴 Синхронизация недоступна';
statusElement.style.backgroundColor = '#f8d7da';
statusElement.style.color = '#721c24';
statusElement.style.border = '1px solid #f5c6cb';
break;
case 'error':
statusElement.textContent = '❌ Ошибка подключения';
statusElement.style.backgroundColor = '#f8d7da';
statusElement.style.color = '#721c24';
statusElement.style.border = '1px solid #f5c6cb';
break;
}
// Автоматически скрываем через 5 секунд для успешного подключения
if (status === 'connected') {
setTimeout(() => {
if (statusElement) {
statusElement.style.opacity = '0.7';
}
}, 5000);
}
}
disconnect() {
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
this.isConnected = false;
console.log('🔌 SSE соединение закрыто');
}
}
// Автоматически инициализируем SSE при загрузке страницы
document.addEventListener('DOMContentLoaded', function() {
// Проверяем, что мы в CRM (не в админке или других разделах)
if (window.location.pathname.includes('/index.php') &&
!window.location.pathname.includes('/admin') &&
!window.location.pathname.includes('/install')) {
console.log('🚀 Запуск SSE синхронизации файлов...');
window.fileSyncSSE = new FileSyncSSE();
}
});
// Экспортируем для использования в других модулях
if (typeof module !== 'undefined' && module.exports) {
module.exports = FileSyncSSE;
}