✨ 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!
277 lines
9.2 KiB
JavaScript
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');
|
|
|
|
})();
|