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

428 lines
14 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🚀 Тест синхронизации (Long Polling)</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
color: white;
text-align: center;
margin-bottom: 30px;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.panel {
background: white;
border-radius: 15px;
padding: 30px;
margin-bottom: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.status {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
background: #f8f9fa;
border-radius: 10px;
margin-bottom: 20px;
}
.status-text {
font-size: 1.2em;
font-weight: 600;
}
.connected { color: #28a745; }
.disconnected { color: #dc3545; }
.waiting { color: #ffc107; }
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
}
.stat-value {
font-size: 2em;
font-weight: 700;
margin-bottom: 5px;
}
.stat-label {
font-size: 0.9em;
opacity: 0.9;
}
.log-container {
background: #1e1e1e;
color: #d4d4d4;
padding: 20px;
border-radius: 10px;
height: 400px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 14px;
line-height: 1.6;
}
.log-entry {
margin-bottom: 5px;
padding: 5px;
border-left: 3px solid transparent;
}
.log-info { border-left-color: #3498db; }
.log-success { border-left-color: #2ecc71; }
.log-error { border-left-color: #e74c3c; }
.log-warning { border-left-color: #f39c12; }
.buttons {
display: flex;
gap: 15px;
flex-wrap: wrap;
margin-top: 20px;
}
button {
flex: 1;
min-width: 200px;
padding: 15px 30px;
font-size: 16px;
font-weight: 600;
border: none;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0,0,0,0.2);
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-success {
background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
color: #1e1e1e;
}
.btn-danger {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
color: #1e1e1e;
}
.comparison {
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
margin-top: 20px;
}
.comparison h4 {
margin-bottom: 15px;
color: #333;
}
.comparison-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.comparison-item {
padding: 15px;
background: white;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.comparison-item h5 {
margin-bottom: 10px;
color: #667eea;
}
.comparison-item ul {
list-style: none;
padding-left: 0;
}
.comparison-item li {
padding: 5px 0;
color: #666;
}
.comparison-item li::before {
content: "• ";
color: #667eea;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 Тест синхронизации (Long Polling)</h1>
<div class="panel">
<div class="status">
<span class="status-text" id="status">🟡 Инициализация...</span>
<span id="time"></span>
</div>
<div class="stats">
<div class="stat-card">
<div class="stat-value" id="requestCount">0</div>
<div class="stat-label">Запросов</div>
</div>
<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="avgWait">0s</div>
<div class="stat-label">Среднее ожидание</div>
</div>
</div>
<div class="buttons">
<button class="btn-success" onclick="testWebhook('file_created')">📝 Тест: Файл создан</button>
<button class="btn-success" onclick="testWebhook('file_updated')">✏️ Тест: Файл обновлен</button>
<button class="btn-danger" onclick="testWebhook('file_deleted')">🗑️ Тест: Файл удален</button>
<button class="btn-primary" onclick="clearLog()">🧹 Очистить лог</button>
</div>
</div>
<div class="panel">
<h3>📝 Лог событий</h3>
<div class="log-container" id="log">
Ожидание событий...
</div>
</div>
<div class="panel">
<div class="comparison">
<h4>🔍 Сравнение: Short Polling vs Long Polling</h4>
<div class="comparison-grid">
<div class="comparison-item">
<h5>Short Polling (старый)</h5>
<ul>
<li>Запрос каждые 2 секунды</li>
<li>~30 запросов в минуту</li>
<li>Задержка до 2 секунд</li>
<li>Больше нагрузка на сервер</li>
</ul>
</div>
<div class="comparison-item">
<h5>Long Polling (новый)</h5>
<ul>
<li>Ждет до 30 секунд</li>
<li>~2-3 запроса в минуту</li>
<li>Мгновенный ответ</li>
<li>Меньше нагрузка на сервер</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<script>
let isPolling = false;
let requestCount = 0;
let eventCount = 0;
let totalWaitTime = 0;
function log(message, type = 'info') {
const logContainer = document.getElementById('log');
const time = new Date().toLocaleTimeString('ru-RU');
const entry = document.createElement('div');
entry.className = `log-entry log-${type}`;
entry.textContent = `[${time}] ${message}`;
logContainer.appendChild(entry);
logContainer.scrollTop = logContainer.scrollHeight;
}
function updateStatus(status) {
const statusEl = document.getElementById('status');
switch(status) {
case 'connected':
statusEl.innerHTML = '🟢 <span class="connected">Подключено</span>';
break;
case 'waiting':
statusEl.innerHTML = '🟡 <span class="waiting">Ожидание событий...</span>';
break;
case 'disconnected':
statusEl.innerHTML = '🔴 <span class="disconnected">Отключено</span>';
break;
}
}
function updateStats(waited) {
requestCount++;
totalWaitTime += waited;
document.getElementById('requestCount').textContent = requestCount;
document.getElementById('eventCount').textContent = eventCount;
document.getElementById('avgWait').textContent =
(totalWaitTime / requestCount).toFixed(1) + 's';
}
function startLongPolling() {
if (isPolling) return;
isPolling = true;
log('🔄 Запуск Long Polling...', 'info');
updateStatus('connected');
longPoll();
}
function longPoll() {
if (!isPolling) return;
updateStatus('waiting');
const startTime = Date.now();
fetch('/crm_extensions/file_storage/api/long_poll_events.php')
.then(response => response.json())
.then(data => {
const waited = data.waited || 0;
updateStats(waited);
if (data.events && data.events.length > 0) {
log(`📦 Получено ${data.events.length} событий (ожидание: ${waited}s)`, 'success');
data.events.forEach(event => {
eventCount++;
handleEvent(event);
});
} else {
log(`⏱️ Таймаут (${waited}s), новых событий нет`, 'info');
}
updateStatus('connected');
// Сразу отправляем следующий запрос
setTimeout(longPoll, 100);
})
.catch(error => {
log(`❌ Ошибка: ${error.message}`, 'error');
updateStatus('disconnected');
// Повторяем через 5 секунд при ошибке
setTimeout(longPoll, 5000);
});
}
function handleEvent(event) {
const type = event.type;
const data = event.data;
switch(type) {
case 'file_created':
log(`📝 Файл создан: ${data.fileName} в ${data.module} (ID: ${data.recordId})`, 'success');
break;
case 'file_updated':
log(`✏️ Файл обновлен: ${data.fileName} в ${data.module} (ID: ${data.recordId})`, 'info');
break;
case 'file_deleted':
log(`🗑️ Файл удален (ID: ${data.documentId})`, 'error');
break;
case 'file_renamed':
log(`🔄 Файл переименован (ID: ${data.documentId}) в ${data.newFileName}`, 'info');
break;
default:
log(`❓ Неизвестное событие: ${type}`, 'warning');
}
}
function testWebhook(type) {
log(`🧪 Тестирование webhook: ${type}`, 'info');
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_simple.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(testData)
})
.then(response => response.json())
.then(data => {
log(`✅ Webhook успешно: ${JSON.stringify(data)}`, 'success');
})
.catch(error => {
log(`❌ Ошибка webhook: ${error.message}`, 'error');
});
}
function clearLog() {
document.getElementById('log').innerHTML = 'Лог очищен...';
log('🧹 Лог очищен', 'info');
}
// Запуск при загрузке страницы
document.addEventListener('DOMContentLoaded', function() {
log('🚀 Страница загружена', 'success');
log(' Long Polling: ждет до 30 секунд на каждый запрос', 'info');
startLongPolling();
});
// Обновление времени каждую секунду
setInterval(() => {
document.getElementById('time').textContent = new Date().toLocaleTimeString('ru-RU');
}, 1000);
</script>
</body>
</html>