import fs from 'fs' import path from 'path' import { Router } from 'express' import mongoose from 'mongoose' import pkg from '../package.json' import './utils/mongoose' import { ErrorLog } from './models/ErrorLog' const folderPath = path.resolve(__dirname, './routers') const getUrl = (url) => `${process.env.NODE_ENV === 'development' ? '' : '/ms'}${url}` // Определение типов interface EndpointStats { total: number; mock: number; real: number; } interface FileInfo { path: string; type: 'file'; endpoints: EndpointStats; } interface DirectoryInfo { path: string; type: 'directory'; endpoints: EndpointStats; children: (FileInfo | DirectoryInfo)[]; } interface DirScanResult { items: (FileInfo | DirectoryInfo)[]; totalEndpoints: EndpointStats; } // Функция для поиска эндпоинтов в файлах function countEndpoints(filePath) { if (!fs.existsSync(filePath) || !filePath.endsWith('.js') && !filePath.endsWith('.ts')) { return { total: 0, mock: 0, real: 0 }; } try { const content = fs.readFileSync(filePath, 'utf8'); const httpMethods = ['get', 'post', 'put', 'delete', 'patch']; const endpointMatches = []; let totalCount = 0; // Собираем все эндпоинты и их контекст httpMethods.forEach(method => { const regex = new RegExp(`router\\.${method}\\([^{]*{([\\s\\S]*?)(?:}\\s*\\)|},)`, 'gi'); let match; while ((match = regex.exec(content)) !== null) { totalCount++; endpointMatches.push({ method, body: match[1] }); } }); // Проверяем каждый эндпоинт - работает с БД или моковый let mockCount = 0; let realCount = 0; endpointMatches.forEach(endpoint => { const body = endpoint.body; // Признаки работы с базой данных - модели Mongoose и их методы const hasDbInteraction = /\b(find|findOne|findById|create|update|delete|remove|aggregate|count|model)\b.*\(/.test(body) || /\bmongoose\b/.test(body) || /\.[a-zA-Z]+Model\b/.test(body); // Проверка на отправку файлов (считается реальным эндпоинтом) const hasFileServing = /res\.sendFile\(/.test(body); // Проверка на отправку ошибок в JSON (считается реальным эндпоинтом) const hasErrorResponse = /res\.json\(\s*{.*?(error|success\s*:\s*false).*?}\)/.test(body) || /res\.status\(.*?\)\.json\(/.test(body); // Признаки моковых данных - только явный импорт JSON файлов или отправка JSON без ошибок const hasMockJsonImport = /require\s*\(\s*['"`].*\.json['"`]\s*\)/.test(body) || /import\s+.*\s+from\s+['"`].*\.json['"`]/.test(body); // JSON ответ, который не является ошибкой (упрощенная проверка) const hasJsonResponse = /res\.json\(/.test(body) && !hasErrorResponse; // Определяем тип эндпоинта if (hasDbInteraction || hasFileServing || hasErrorResponse) { // Если работает с БД, отправляет файлы или возвращает ошибки - считаем реальным realCount++; } else if (hasMockJsonImport || hasJsonResponse) { // Если импортирует JSON или отправляет JSON без ошибок - считаем моком mockCount++; } else { // По умолчанию считаем реальным realCount++; } }); return { total: totalCount, mock: mockCount, real: realCount }; } catch (err) { return { total: 0, mock: 0, real: 0 }; } } // Функция для рекурсивного обхода директорий function getAllDirs(dir, basePath = ''): DirScanResult { const items: (FileInfo | DirectoryInfo)[] = []; let totalEndpoints = { total: 0, mock: 0, real: 0 }; try { const dirItems = fs.readdirSync(dir); for (const item of dirItems) { const itemPath = path.join(dir, item); const relativePath = path.join(basePath, item); const stat = fs.statSync(itemPath); if (stat.isDirectory()) { const dirResult = getAllDirs(itemPath, relativePath); totalEndpoints.total += dirResult.totalEndpoints.total; totalEndpoints.mock += dirResult.totalEndpoints.mock; totalEndpoints.real += dirResult.totalEndpoints.real; items.push({ path: relativePath, type: 'directory', endpoints: dirResult.totalEndpoints, children: dirResult.items }); } else { const fileEndpoints = countEndpoints(itemPath); totalEndpoints.total += fileEndpoints.total; totalEndpoints.mock += fileEndpoints.mock; totalEndpoints.real += fileEndpoints.real; items.push({ path: relativePath, type: 'file', endpoints: fileEndpoints }); } } } catch (err) { console.error(`Ошибка при чтении директории ${dir}:`, err); } return { items, totalEndpoints }; } // Функция для генерации HTML-списка директорий function generateDirList(dirs: (FileInfo | DirectoryInfo)[], level = 0) { if (dirs.length === 0) return ''; const indent = level * 20; return `
Нет зарегистрированных ошибок
'; } // Создаем JavaScript для клиентской части const clientScript = ` document.addEventListener('DOMContentLoaded', function() { // Директории document.querySelectorAll('.dir-item[data-expandable="true"]').forEach(item => { item.addEventListener('click', function(e) { const subdirectory = this.nextElementSibling; const isExpanded = this.classList.toggle('expanded'); if (isExpanded) { subdirectory.style.display = 'block'; this.querySelector('.expand-icon').textContent = '▼'; } else { subdirectory.style.display = 'none'; this.querySelector('.expand-icon').textContent = '▶'; } e.stopPropagation(); }); }); // Модальное окно const modal = document.getElementById('fileModal'); const closeBtn = document.querySelector('.close-modal'); const fileContent = document.getElementById('fileContent'); const fileLoader = document.getElementById('fileLoader'); const modalFileName = document.getElementById('modalFileName'); // Закрытие модального окна closeBtn.addEventListener('click', function() { modal.style.display = 'none'; }); window.addEventListener('click', function(event) { if (event.target == modal) { modal.style.display = 'none'; } }); // Обработчик для файлов document.querySelectorAll('.file-item').forEach(item => { item.addEventListener('click', async function() { const filePath = this.getAttribute('data-path'); if (!filePath) return; // Показываем модальное окно и лоадер modal.style.display = 'block'; fileContent.style.display = 'none'; fileLoader.style.display = 'block'; modalFileName.textContent = 'Загрузка...'; try { const response = await fetch('${getUrl('/file-content?path=')}' + encodeURIComponent(filePath)); if (!response.ok) { throw new Error('Ошибка при загрузке файла'); } const data = await response.json(); // Отображаем содержимое файла fileLoader.style.display = 'none'; fileContent.style.display = 'block'; fileContent.textContent = data.content; modalFileName.textContent = data.fileName; // Подсветка синтаксиса const extensionMap = { 'js': 'javascript', 'ts': 'typescript', 'json': 'json', 'css': 'css', 'html': 'xml', 'xml': 'xml', 'md': 'markdown', 'yaml': 'yaml', 'yml': 'yaml', 'sh': 'bash', 'bash': 'bash' }; const language = extensionMap[data.extension] || ''; if (language) { fileContent.className = 'language-' + language; hljs.highlightElement(fileContent); } } catch (error) { fileLoader.style.display = 'none'; fileContent.style.display = 'block'; fileContent.textContent = 'Ошибка при загрузке файла: ' + error.message; modalFileName.textContent = 'Ошибка'; } }); }); // Обработчик кнопки очистки ошибок const clearErrorsBtn = document.getElementById('clearErrorsBtn'); const successAction = document.getElementById('successAction'); clearErrorsBtn.addEventListener('click', async function() { try { const response = await fetch('${getUrl('/clear-old-errors')}', { method: 'POST', headers: { 'Content-Type': 'application/json', } }); if (!response.ok) { throw new Error('Ошибка при очистке старых ошибок'); } const data = await response.json(); // Показываем сообщение об успехе successAction.textContent = 'Удалено ' + data.deletedCount + ' записей'; successAction.style.display = 'block'; // Перезагружаем страницу через 2 секунды setTimeout(() => { window.location.reload(); }, 2000); } catch (error) { console.error('Ошибка:', error); alert('Произошла ошибка: ' + error.message); } }); }); `; res.send(`${count} документов