✨ 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!
213 lines
8.9 KiB
HTML
213 lines
8.9 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>🚀 Redis Pub/Sub Test</title>
|
||
<style>
|
||
body { font-family: Arial; max-width: 1200px; margin: 40px auto; padding: 20px; background: #f5f5f5; }
|
||
.panel { background: white; padding: 30px; margin-bottom: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||
h1 { color: #333; border-bottom: 3px solid #667eea; padding-bottom: 10px; }
|
||
.status { padding: 15px; margin: 20px 0; font-size: 16px; border-radius: 8px; }
|
||
.status.success { background: #d4edda; border-left: 4px solid #28a745; }
|
||
.status.error { background: #f8d7da; border-left: 4px solid #dc3545; }
|
||
.status.info { background: #d1ecf1; border-left: 4px solid #17a2b8; }
|
||
button { padding: 12px 24px; font-size: 16px; border: none; border-radius: 6px; cursor: pointer; margin: 5px; background: #667eea; color: white; font-weight: 600; }
|
||
button:hover { background: #5568d3; }
|
||
.log-container { background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 6px; height: 400px; overflow-y: auto; font-family: 'Courier New', monospace; font-size: 14px; }
|
||
.log-entry { margin-bottom: 5px; line-height: 1.6; }
|
||
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin: 20px 0; }
|
||
.stat-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px; text-align: center; }
|
||
.stat-value { font-size: 2em; font-weight: bold; }
|
||
.stat-label { font-size: 0.9em; opacity: 0.9; margin-top: 5px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="panel">
|
||
<h1>🚀 Redis Pub/Sub + SSE Test</h1>
|
||
|
||
<div id="sseStatus" class="status info">
|
||
<strong>Подключение...</strong>
|
||
</div>
|
||
|
||
<div class="stats">
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="eventCount">0</div>
|
||
<div class="stat-label">Событий</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="latency">-</div>
|
||
<div class="stat-label">Задержка</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="status">🔴</div>
|
||
<div class="stat-label">Статус</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<button onclick="testWebhook('file_created')">📝 Тест: Файл создан</button>
|
||
<button onclick="testWebhook('file_updated')">✏️ Тест: Файл обновлен</button>
|
||
<button onclick="testWebhook('file_deleted')">🗑️ Тест: Файл удален</button>
|
||
<button onclick="clearLog()">🧹 Очистить</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<h3>📝 Лог событий (мгновенная доставка через Redis!)</h3>
|
||
<div class="log-container" id="log">
|
||
Ожидание подключения...
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<h3>⚡ Преимущества Redis Pub/Sub:</h3>
|
||
<ul>
|
||
<li><strong>Мгновенная доставка:</strong> <100 мс (vs 5-9 сек Long Polling)</li>
|
||
<li><strong>Нет лишних запросов:</strong> постоянное SSE соединение</li>
|
||
<li><strong>Масштабируемость:</strong> тысячи клиентов одновременно</li>
|
||
<li><strong>Низкая нагрузка:</strong> события push, а не pull</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<script>
|
||
let eventSource;
|
||
let eventCount = 0;
|
||
let webhookTime = null;
|
||
|
||
function log(message) {
|
||
const logContainer = document.getElementById('log');
|
||
const time = new Date().toLocaleTimeString('ru-RU');
|
||
const entry = document.createElement('div');
|
||
entry.className = 'log-entry';
|
||
entry.textContent = `[${time}] ${message}`;
|
||
logContainer.appendChild(entry);
|
||
logContainer.scrollTop = logContainer.scrollHeight;
|
||
}
|
||
|
||
function updateStatus(status, message) {
|
||
const statusEl = document.getElementById('sseStatus');
|
||
const statusIcon = document.getElementById('status');
|
||
|
||
switch(status) {
|
||
case 'connected':
|
||
statusEl.className = 'status success';
|
||
statusEl.innerHTML = '<strong>✅ ' + message + '</strong>';
|
||
statusIcon.textContent = '🟢';
|
||
break;
|
||
case 'disconnected':
|
||
statusEl.className = 'status error';
|
||
statusEl.innerHTML = '<strong>❌ ' + message + '</strong>';
|
||
statusIcon.textContent = '🔴';
|
||
break;
|
||
default:
|
||
statusEl.className = 'status info';
|
||
statusEl.innerHTML = '<strong>🟡 ' + message + '</strong>';
|
||
statusIcon.textContent = '🟡';
|
||
}
|
||
}
|
||
|
||
function connectSSE() {
|
||
log('🔄 Подключение к Redis SSE...');
|
||
updateStatus('connecting', 'Подключение к Redis SSE...');
|
||
|
||
eventSource = new EventSource('/crm_extensions/file_storage/api/redis_sse.php');
|
||
|
||
eventSource.onopen = function() {
|
||
log('✅ SSE подключение установлено');
|
||
updateStatus('connected', 'Подключено к Redis через SSE');
|
||
};
|
||
|
||
eventSource.onmessage = function(event) {
|
||
try {
|
||
const data = JSON.parse(event.data);
|
||
handleEvent(data);
|
||
} catch (e) {
|
||
log('❌ Ошибка парсинга: ' + e.message);
|
||
}
|
||
};
|
||
|
||
eventSource.onerror = function(error) {
|
||
log('❌ Ошибка SSE: ' + error);
|
||
updateStatus('disconnected', 'Отключено от Redis');
|
||
|
||
// Переподключение через 5 сек
|
||
setTimeout(connectSSE, 5000);
|
||
};
|
||
}
|
||
|
||
function handleEvent(event) {
|
||
const type = event.type;
|
||
const data = event.data;
|
||
|
||
eventCount++;
|
||
document.getElementById('eventCount').textContent = eventCount;
|
||
|
||
// Вычисляем задержку
|
||
if (webhookTime) {
|
||
const latency = Date.now() - webhookTime;
|
||
document.getElementById('latency').textContent = latency + 'ms';
|
||
webhookTime = null;
|
||
}
|
||
|
||
switch(type) {
|
||
case 'connected':
|
||
log('🔗 ' + data.message);
|
||
break;
|
||
case 'file_created':
|
||
log(`📝 Файл создан: ${data.fileName} в ${data.module} (ID: ${data.recordId})`);
|
||
break;
|
||
case 'file_updated':
|
||
log(`✏️ Файл обновлен: ${data.fileName}`);
|
||
break;
|
||
case 'file_deleted':
|
||
log(`🗑️ Файл удален (ID: ${data.documentId})`);
|
||
break;
|
||
default:
|
||
log(`📨 Событие: ${type}`);
|
||
}
|
||
}
|
||
|
||
function testWebhook(type) {
|
||
log(`🧪 Отправка webhook: ${type}`);
|
||
webhookTime = Date.now();
|
||
|
||
const testData = {
|
||
action: type,
|
||
file_path: 'crm2/CRM_Active_Files/Documents/Project_123/test_file_456.pdf',
|
||
project_id: '123'
|
||
};
|
||
|
||
fetch('/crm_extensions/file_storage/api/nextcloud_webhook_redis.php', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(testData)
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
log(`✅ Webhook ответ: ${data.message || data.status}`);
|
||
})
|
||
.catch(error => {
|
||
log(`❌ Ошибка webhook: ${error.message}`);
|
||
});
|
||
}
|
||
|
||
function clearLog() {
|
||
document.getElementById('log').innerHTML = 'Лог очищен...';
|
||
log('🧹 Лог очищен');
|
||
}
|
||
|
||
// Запуск при загрузке страницы
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
log('🚀 Страница загружена');
|
||
connectSSE();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|
||
|
||
|
||
|