Files
crm.clientright.ru/crm_extensions/file_storage/js/file_sync.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

277 lines
9.2 KiB
JavaScript

/**
* Long Polling синхронизация файлов для CRM
*
* Автоматически обновляет списки файлов при изменениях в Nextcloud
*/
(function() {
'use strict';
// Конфигурация
const CONFIG = {
apiUrl: '/crm_extensions/file_storage/api/long_poll_events.php',
retryDelay: 5000, // 5 сек при ошибке
reconnectDelay: 100, // 0.1 сек между запросами
debug: true
};
// Статистика
let stats = {
requests: 0,
events: 0,
errors: 0,
lastUpdate: null
};
// Флаг активности
let isActive = false;
/**
* Логирование
*/
function log(message, level = 'info') {
if (!CONFIG.debug && level === 'debug') return;
const prefix = '[FileSync]';
const timestamp = new Date().toLocaleTimeString('ru-RU');
switch(level) {
case 'error':
console.error(`${prefix} [${timestamp}] ${message}`);
break;
case 'warn':
console.warn(`${prefix} [${timestamp}] ${message}`);
break;
case 'debug':
console.log(`${prefix} [${timestamp}] ${message}`);
break;
default:
console.log(`${prefix} [${timestamp}] ${message}`);
}
}
/**
* Показать уведомление пользователю
*/
function showNotification(message, type = 'info') {
// Проверяем наличие Vtiger notification system
if (typeof Vtiger_Helper_Js !== 'undefined' && Vtiger_Helper_Js.showPnotify) {
Vtiger_Helper_Js.showPnotify({
text: message,
type: type,
delay: 3000
});
} else {
log(message, type);
}
}
/**
* Обновить список файлов на странице
*/
function refreshFilesList() {
log('Обновление списка файлов...', 'debug');
// Проверяем наличие app (только в CRM)
if (typeof app === 'undefined') {
log('app не определен (не в CRM контексте)', 'debug');
return;
}
// Проверяем, на какой странице мы находимся
const currentModule = app.getModuleName();
const currentView = app.getViewName();
if (currentView === 'Detail') {
// Обновляем виджет документов на странице детального просмотра
if (typeof jQuery !== 'undefined') {
const documentsWidget = jQuery('.documentsWidget');
if (documentsWidget.length > 0) {
log('Обновление виджета документов...', 'debug');
// Триггерим перезагрузку виджета
documentsWidget.trigger('refresh');
}
}
} else if (currentView === 'List' && currentModule === 'Documents') {
// Обновляем список документов
log('Обновление списка документов...', 'debug');
if (typeof Vtiger_List_Js !== 'undefined') {
const listViewInstance = Vtiger_List_Js.getInstance();
if (listViewInstance) {
listViewInstance.getListViewRecords();
}
}
}
}
/**
* Обработка события файла
*/
function handleFileEvent(event) {
const type = event.type;
const data = event.data || {};
stats.events++;
stats.lastUpdate = new Date();
log(`Событие: ${type}`, 'debug');
switch(type) {
case 'file_created':
showNotification(
`📝 Добавлен файл: ${data.fileName || 'неизвестно'}`,
'info'
);
refreshFilesList();
break;
case 'file_updated':
showNotification(
`✏️ Обновлен файл: ${data.fileName || 'неизвестно'}`,
'info'
);
refreshFilesList();
break;
case 'file_deleted':
showNotification(
`🗑️ Удален файл (ID: ${data.documentId || 'неизвестно'})`,
'warning'
);
refreshFilesList();
break;
case 'file_renamed':
showNotification(
`🔄 Переименован файл: ${data.newFileName || 'неизвестно'}`,
'info'
);
refreshFilesList();
break;
case 'folder_renamed':
log(`Папка переименована: ${data.oldPath}${data.newPath}`, 'info');
// TODO: обновить пути в CRM
break;
case 'folder_deleted':
log(`Папка удалена: ${data.folderPath}`, 'warn');
// TODO: пометить файлы как удаленные
break;
default:
log(`Неизвестное событие: ${type}`, 'warn');
}
}
/**
* Long Polling цикл
*/
function longPoll() {
if (!isActive) {
log('Long Polling остановлен', 'debug');
return;
}
stats.requests++;
fetch(CONFIG.apiUrl)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
if (data.events && Array.isArray(data.events) && data.events.length > 0) {
log(`Получено ${data.events.length} событий (ожидание: ${data.waited}s)`, 'info');
// Обрабатываем каждое событие
data.events.forEach(event => {
handleFileEvent(event);
});
} else {
log(`Нет новых событий (ожидание: ${data.waited}s)`, 'debug');
}
// Сразу отправляем следующий запрос
setTimeout(longPoll, CONFIG.reconnectDelay);
})
.catch(error => {
stats.errors++;
log(`Ошибка Long Polling: ${error.message}`, 'error');
// Повторяем через CONFIG.retryDelay при ошибке
setTimeout(longPoll, CONFIG.retryDelay);
});
}
/**
* Запуск синхронизации
*/
function start() {
if (isActive) {
log('Long Polling уже запущен', 'warn');
return;
}
isActive = true;
log('🚀 Запуск Long Polling синхронизации файлов...', 'info');
longPoll();
}
/**
* Остановка синхронизации
*/
function stop() {
if (!isActive) {
log('Long Polling уже остановлен', 'warn');
return;
}
isActive = false;
log('🛑 Остановка Long Polling...', 'info');
}
/**
* Получить статистику
*/
function getStats() {
return {
...stats,
isActive: isActive,
uptime: stats.lastUpdate
? Math.floor((new Date() - stats.lastUpdate) / 1000)
: null
};
}
// Экспортируем API
window.CRM_FileSync = {
start: start,
stop: stop,
getStats: getStats,
config: CONFIG
};
// Автоматический запуск при загрузке страницы
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
log('Документ загружен, запускаем синхронизацию...', 'debug');
start();
});
} else {
// Документ уже загружен
log('Документ уже загружен, запускаем синхронизацию...', 'debug');
start();
}
// Останавливаем при выгрузке страницы
window.addEventListener('beforeunload', function() {
stop();
});
log('Модуль синхронизации файлов загружен', 'info');
})();