✨ 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!
295 lines
9.5 KiB
HTML
295 lines
9.5 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>🔴 Redis SSE - Финальный тест</title>
|
||
<style>
|
||
body {
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
max-width: 1200px;
|
||
margin: 20px auto;
|
||
padding: 20px;
|
||
background: #f5f5f5;
|
||
}
|
||
.container {
|
||
background: white;
|
||
border-radius: 10px;
|
||
padding: 30px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
h1 {
|
||
color: #2c3e50;
|
||
margin-bottom: 30px;
|
||
}
|
||
.status {
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
margin-bottom: 20px;
|
||
font-weight: bold;
|
||
}
|
||
.status.connected {
|
||
background: #d4edda;
|
||
border: 2px solid #28a745;
|
||
color: #155724;
|
||
}
|
||
.status.disconnected {
|
||
background: #f8d7da;
|
||
border: 2px solid #dc3545;
|
||
color: #721c24;
|
||
}
|
||
.status.connecting {
|
||
background: #fff3cd;
|
||
border: 2px solid #ffc107;
|
||
color: #856404;
|
||
}
|
||
.controls {
|
||
margin: 20px 0;
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
button {
|
||
padding: 12px 24px;
|
||
font-size: 16px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
button.primary {
|
||
background: #007bff;
|
||
color: white;
|
||
}
|
||
button.primary:hover {
|
||
background: #0056b3;
|
||
}
|
||
button.success {
|
||
background: #28a745;
|
||
color: white;
|
||
}
|
||
button.success:hover {
|
||
background: #1e7e34;
|
||
}
|
||
button.danger {
|
||
background: #dc3545;
|
||
color: white;
|
||
}
|
||
button.danger:hover {
|
||
background: #c82333;
|
||
}
|
||
.events {
|
||
margin-top: 30px;
|
||
}
|
||
.event {
|
||
padding: 15px;
|
||
margin: 10px 0;
|
||
border-radius: 6px;
|
||
border-left: 4px solid;
|
||
background: #f8f9fa;
|
||
}
|
||
.event.test {
|
||
border-left-color: #17a2b8;
|
||
}
|
||
.event.file_created {
|
||
border-left-color: #28a745;
|
||
}
|
||
.event.file_updated {
|
||
border-left-color: #ffc107;
|
||
}
|
||
.event.file_deleted {
|
||
border-left-color: #dc3545;
|
||
}
|
||
.event.connected {
|
||
border-left-color: #007bff;
|
||
}
|
||
.event.heartbeat {
|
||
border-left-color: #6c757d;
|
||
opacity: 0.6;
|
||
}
|
||
.event .time {
|
||
color: #6c757d;
|
||
font-size: 12px;
|
||
float: right;
|
||
}
|
||
.event .type {
|
||
font-weight: bold;
|
||
margin-bottom: 8px;
|
||
}
|
||
.event .data {
|
||
font-family: 'Courier New', monospace;
|
||
background: white;
|
||
padding: 10px;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
}
|
||
.stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 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: 32px;
|
||
font-weight: bold;
|
||
}
|
||
.stat-label {
|
||
font-size: 14px;
|
||
opacity: 0.9;
|
||
margin-top: 5px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>🔴 Redis SSE - Финальный тест</h1>
|
||
|
||
<div id="status" class="status connecting">
|
||
🔄 Подключение к Redis SSE...
|
||
</div>
|
||
|
||
<div class="stats">
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="totalEvents">0</div>
|
||
<div class="stat-label">Всего событий</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="lastEventTime">-</div>
|
||
<div class="stat-label">Последнее событие</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="connectionTime">0s</div>
|
||
<div class="stat-label">Время подключения</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<button class="primary" onclick="reconnect()">🔄 Переподключиться</button>
|
||
<button class="success" onclick="sendTestEvent()">🧪 Тест события</button>
|
||
<button class="danger" onclick="clearEvents()">🗑️ Очистить</button>
|
||
</div>
|
||
|
||
<div class="events">
|
||
<h3>📋 События:</h3>
|
||
<div id="events"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let eventSource = null;
|
||
let totalEvents = 0;
|
||
let connectionStart = Date.now();
|
||
let connectionTimer = null;
|
||
|
||
function connect() {
|
||
const statusEl = document.getElementById('status');
|
||
statusEl.className = 'status connecting';
|
||
statusEl.innerHTML = '🔄 Подключение к Redis SSE...';
|
||
|
||
// Подключаемся к ПРОСТОМУ SSE (без SUBSCRIBE)
|
||
eventSource = new EventSource('/crm_extensions/file_storage/api/redis_sse_simple.php');
|
||
|
||
eventSource.onopen = function() {
|
||
statusEl.className = 'status connected';
|
||
statusEl.innerHTML = '🟢 Подключено к Redis SSE (Predis)';
|
||
|
||
connectionStart = Date.now();
|
||
updateConnectionTime();
|
||
connectionTimer = setInterval(updateConnectionTime, 1000);
|
||
};
|
||
|
||
eventSource.onmessage = function(e) {
|
||
try {
|
||
const event = JSON.parse(e.data);
|
||
addEvent(event);
|
||
totalEvents++;
|
||
document.getElementById('totalEvents').textContent = totalEvents;
|
||
document.getElementById('lastEventTime').textContent = event.time || new Date().toLocaleTimeString('ru-RU');
|
||
} catch (err) {
|
||
console.error('Ошибка парсинга события:', err);
|
||
}
|
||
};
|
||
|
||
eventSource.onerror = function(e) {
|
||
statusEl.className = 'status disconnected';
|
||
statusEl.innerHTML = '🔴 Отключено от Redis SSE';
|
||
|
||
if (connectionTimer) {
|
||
clearInterval(connectionTimer);
|
||
}
|
||
|
||
console.error('SSE error:', e);
|
||
|
||
// Переподключаемся через 3 секунды
|
||
setTimeout(() => {
|
||
console.log('🔄 Переподключение...');
|
||
reconnect();
|
||
}, 3000);
|
||
};
|
||
}
|
||
|
||
function reconnect() {
|
||
if (eventSource) {
|
||
eventSource.close();
|
||
}
|
||
if (connectionTimer) {
|
||
clearInterval(connectionTimer);
|
||
}
|
||
connect();
|
||
}
|
||
|
||
function addEvent(event) {
|
||
const eventsEl = document.getElementById('events');
|
||
const eventEl = document.createElement('div');
|
||
eventEl.className = 'event ' + (event.type || 'unknown');
|
||
|
||
eventEl.innerHTML = `
|
||
<span class="time">${event.time || new Date().toLocaleTimeString('ru-RU')}</span>
|
||
<div class="type">📡 ${event.type || 'unknown'}</div>
|
||
<div class="data">${JSON.stringify(event.data, null, 2)}</div>
|
||
`;
|
||
|
||
eventsEl.insertBefore(eventEl, eventsEl.firstChild);
|
||
|
||
// Ограничиваем количество отображаемых событий
|
||
while (eventsEl.children.length > 20) {
|
||
eventsEl.removeChild(eventsEl.lastChild);
|
||
}
|
||
}
|
||
|
||
function updateConnectionTime() {
|
||
const seconds = Math.floor((Date.now() - connectionStart) / 1000);
|
||
document.getElementById('connectionTime').textContent = seconds + 's';
|
||
}
|
||
|
||
function sendTestEvent() {
|
||
// Отправляем тестовое событие через Redis CLI
|
||
fetch('/crm_extensions/file_storage/api/send_test_event.php')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
console.log('✅ Тестовое событие отправлено:', data);
|
||
})
|
||
.catch(err => {
|
||
console.error('❌ Ошибка отправки:', err);
|
||
});
|
||
}
|
||
|
||
function clearEvents() {
|
||
document.getElementById('events').innerHTML = '';
|
||
totalEvents = 0;
|
||
document.getElementById('totalEvents').textContent = '0';
|
||
}
|
||
|
||
// Автоматическое подключение при загрузке
|
||
connect();
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|