250 lines
8.9 KiB
JavaScript
250 lines
8.9 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
/**
|
|||
|
|
* Nextcloud Activity Monitor
|
|||
|
|
* Мониторит события файлов через Activity API и публикует в Redis
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const https = require('https');
|
|||
|
|
const Redis = require('ioredis');
|
|||
|
|
|
|||
|
|
const CONFIG = {
|
|||
|
|
nextcloud: {
|
|||
|
|
host: 'office.clientright.ru',
|
|||
|
|
port: 8443,
|
|||
|
|
username: 'admin',
|
|||
|
|
password: 'tGHKS-3cC9m-7Hggb-65Awk-zxWQE',
|
|||
|
|
auth: Buffer.from('admin:tGHKS-3cC9m-7Hggb-65Awk-zxWQE').toString('base64')
|
|||
|
|
},
|
|||
|
|
redis: {
|
|||
|
|
host: '147.45.146.17',
|
|||
|
|
port: 6379,
|
|||
|
|
password: 'CRM_Redis_Pass_2025_Secure!'
|
|||
|
|
},
|
|||
|
|
pollInterval: 30000, // 30 секунд
|
|||
|
|
stateKey: 'crm:nextcloud:activity:state'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const redis = new Redis({
|
|||
|
|
host: CONFIG.redis.host,
|
|||
|
|
port: CONFIG.redis.port,
|
|||
|
|
password: CONFIG.redis.password
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Хранилище последнего обработанного activity_id
|
|||
|
|
let lastActivityId = 0;
|
|||
|
|
|
|||
|
|
// Загрузка состояния из Redis
|
|||
|
|
async function loadState() {
|
|||
|
|
try {
|
|||
|
|
const data = await redis.get(CONFIG.stateKey);
|
|||
|
|
if (data) {
|
|||
|
|
const state = JSON.parse(data);
|
|||
|
|
lastActivityId = state.lastActivityId || 0;
|
|||
|
|
console.log(`📥 Последний обработанный activity_id: ${lastActivityId}`);
|
|||
|
|
} else {
|
|||
|
|
console.log('📥 Состояние пустое (первый запуск)');
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('⚠️ Ошибка загрузки состояния:', err.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Сохранение состояния в Redis
|
|||
|
|
async function saveState() {
|
|||
|
|
try {
|
|||
|
|
await redis.set(CONFIG.stateKey, JSON.stringify({ lastActivityId }));
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('⚠️ Ошибка сохранения состояния:', err.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Получение активностей из Nextcloud API
|
|||
|
|
function getActivities(limit = 100) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const options = {
|
|||
|
|
hostname: CONFIG.nextcloud.host,
|
|||
|
|
port: CONFIG.nextcloud.port,
|
|||
|
|
path: `/ocs/v2.php/apps/activity/api/v2/activity?format=json&limit=${limit}`,
|
|||
|
|
method: 'GET',
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': `Basic ${CONFIG.nextcloud.auth}`,
|
|||
|
|
'OCS-APIRequest': 'true'
|
|||
|
|
},
|
|||
|
|
rejectUnauthorized: false
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const req = https.request(options, (res) => {
|
|||
|
|
let data = '';
|
|||
|
|
|
|||
|
|
res.on('data', (chunk) => {
|
|||
|
|
data += chunk;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.on('end', () => {
|
|||
|
|
try {
|
|||
|
|
const json = JSON.parse(data);
|
|||
|
|
resolve(json.ocs.data || []);
|
|||
|
|
} catch (err) {
|
|||
|
|
reject(new Error('Ошибка парсинга JSON: ' + err.message));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
req.on('error', (err) => {
|
|||
|
|
reject(err);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
req.setTimeout(10000, () => {
|
|||
|
|
req.destroy();
|
|||
|
|
reject(new Error('Timeout'));
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
req.end();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Публикация события в Redis
|
|||
|
|
async function publishEvent(event) {
|
|||
|
|
console.log(`\n 📢 ${event.type.toUpperCase()}: ${event.filename}`);
|
|||
|
|
console.log(` 🆔 file_id: ${event.file_id}`);
|
|||
|
|
console.log(` 👤 user: ${event.user}`);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await redis.publish('crm:file:events', JSON.stringify(event));
|
|||
|
|
console.log(` ✅ Опубликовано в Redis`);
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error(` ❌ Ошибка публикации:`, err.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Сканирование новых активностей
|
|||
|
|
async function scanActivities() {
|
|||
|
|
try {
|
|||
|
|
console.log(`\n🔍 Проверка новых событий... (${new Date().toISOString()})`);
|
|||
|
|
|
|||
|
|
const activities = await getActivities(100);
|
|||
|
|
|
|||
|
|
const relevantTypes = ['file_created', 'file_changed', 'file_deleted', 'file_restored'];
|
|||
|
|
let newEvents = 0;
|
|||
|
|
let totalFiles = 0;
|
|||
|
|
|
|||
|
|
// Обрабатываем активности (от новых к старым)
|
|||
|
|
for (const activity of activities) {
|
|||
|
|
// Пропускаем уже обработанные
|
|||
|
|
if (activity.activity_id <= lastActivityId) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Только файловые события
|
|||
|
|
if (!relevantTypes.includes(activity.type)) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
newEvents++;
|
|||
|
|
|
|||
|
|
// РАЗБИВАЕМ агрегированные события на отдельные файлы!
|
|||
|
|
if (activity.objects && typeof activity.objects === 'object') {
|
|||
|
|
// Множественное событие - разбиваем
|
|||
|
|
const fileIds = Object.keys(activity.objects);
|
|||
|
|
totalFiles += fileIds.length;
|
|||
|
|
|
|||
|
|
console.log(` 📦 Агрегированное событие: ${fileIds.length} файлов`);
|
|||
|
|
|
|||
|
|
for (const fileId of fileIds) {
|
|||
|
|
const filePath = activity.objects[fileId];
|
|||
|
|
|
|||
|
|
const event = {
|
|||
|
|
type: activity.type,
|
|||
|
|
source: 'nextcloud_activity',
|
|||
|
|
timestamp: activity.datetime,
|
|||
|
|
file_id: parseInt(fileId),
|
|||
|
|
path: filePath,
|
|||
|
|
filename: filePath ? filePath.split('/').pop().replace(/^\//, '') : null,
|
|||
|
|
user: activity.user,
|
|||
|
|
activity_id: activity.activity_id,
|
|||
|
|
action: activity.type.replace('file_', '')
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
await publishEvent(event);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// Одиночное событие
|
|||
|
|
totalFiles++;
|
|||
|
|
|
|||
|
|
const event = {
|
|||
|
|
type: activity.type,
|
|||
|
|
source: 'nextcloud_activity',
|
|||
|
|
timestamp: activity.datetime,
|
|||
|
|
file_id: activity.object_id,
|
|||
|
|
path: activity.object_name,
|
|||
|
|
filename: activity.object_name ? activity.object_name.split('/').pop().replace(/^\//, '') : null,
|
|||
|
|
user: activity.user,
|
|||
|
|
activity_id: activity.activity_id,
|
|||
|
|
action: activity.type.replace('file_', '')
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
await publishEvent(event);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Обновляем последний ID
|
|||
|
|
if (activity.activity_id > lastActivityId) {
|
|||
|
|
lastActivityId = activity.activity_id;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Сохраняем состояние
|
|||
|
|
await saveState();
|
|||
|
|
|
|||
|
|
console.log(`\n✅ Сканирование завершено:`);
|
|||
|
|
console.log(` 📊 Новых активностей: ${newEvents}`);
|
|||
|
|
console.log(` 📁 Файлов обработано: ${totalFiles}`);
|
|||
|
|
console.log(` 🆔 Последний activity_id: ${lastActivityId}`);
|
|||
|
|
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('❌ Ошибка сканирования:', err.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Запуск
|
|||
|
|
async function start() {
|
|||
|
|
console.log('🚀 Nextcloud Activity Monitor');
|
|||
|
|
console.log('════════════════════════════════════════════════════════════════════════════════');
|
|||
|
|
console.log(`📡 Nextcloud: ${CONFIG.nextcloud.host}:${CONFIG.nextcloud.port}`);
|
|||
|
|
console.log(`📡 Redis: ${CONFIG.redis.host}:${CONFIG.redis.port}`);
|
|||
|
|
console.log(`🔄 Интервал: ${CONFIG.pollInterval / 1000}с`);
|
|||
|
|
console.log('════════════════════════════════════════════════════════════════════════════════\n');
|
|||
|
|
|
|||
|
|
await loadState();
|
|||
|
|
|
|||
|
|
console.log('👂 Начинаем мониторинг...\n');
|
|||
|
|
|
|||
|
|
// Первое сканирование
|
|||
|
|
await scanActivities();
|
|||
|
|
|
|||
|
|
// Периодическое сканирование
|
|||
|
|
setInterval(scanActivities, CONFIG.pollInterval);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Запуск при подключении к Redis
|
|||
|
|
redis.on('connect', () => {
|
|||
|
|
console.log('✅ Подключились к Redis\n');
|
|||
|
|
start();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
redis.on('error', (err) => {
|
|||
|
|
console.error('❌ Redis ошибка:', err.message);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
process.on('SIGINT', () => {
|
|||
|
|
console.log('\n\n⛔ Остановка мониторинга...');
|
|||
|
|
redis.disconnect();
|
|||
|
|
process.exit(0);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
process.on('SIGTERM', () => {
|
|||
|
|
console.log('\n\n⛔ Получен сигнал SIGTERM, останавливаемся...');
|
|||
|
|
redis.disconnect();
|
|||
|
|
process.exit(0);
|
|||
|
|
});
|
|||
|
|
|